Merge "Reduce log spam."
diff --git a/api/current.xml b/api/current.xml
index e3b6a01..c6fc34e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -6268,17 +6268,6 @@
visibility="public"
>
</field>
-<field name="kraken_resource_pad56"
- type="int"
- transient="false"
- volatile="false"
- value="16843465"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="kraken_resource_pad6"
type="int"
transient="false"
@@ -6928,6 +6917,17 @@
visibility="public"
>
</field>
+<field name="loopViews"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843592"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="manageSpaceActivity"
type="int"
transient="false"
@@ -7676,6 +7676,17 @@
visibility="public"
>
</field>
+<field name="popupAnimationStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843465"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="popupBackground"
type="int"
transient="false"
@@ -21014,6 +21025,21 @@
visibility="public"
>
</constructor>
+<method name="addChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
<method name="addTransitionListener"
return="void"
abstract="false"
@@ -21027,36 +21053,6 @@
<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener">
</parameter>
</method>
-<method name="childAdd"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="parent" type="android.view.ViewGroup">
-</parameter>
-<parameter name="child" type="android.view.View">
-</parameter>
-</method>
-<method name="childRemove"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="parent" type="android.view.ViewGroup">
-</parameter>
-<parameter name="child" type="android.view.View">
-</parameter>
-</method>
<method name="getAnimator"
return="android.animation.Animator"
abstract="false"
@@ -21133,6 +21129,36 @@
visibility="public"
>
</method>
+<method name="hideChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<method name="removeChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
<method name="removeTransitionListener"
return="void"
abstract="false"
@@ -21234,6 +21260,21 @@
<parameter name="delay" type="long">
</parameter>
</method>
+<method name="showChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
<field name="APPEARING"
type="int"
transient="false"
@@ -28771,17 +28812,6 @@
visibility="public"
>
</field>
-<field name="COLUMN_ERROR_CODE"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""error_code""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="COLUMN_ID"
type="java.lang.String"
transient="false"
@@ -28826,6 +28856,17 @@
visibility="public"
>
</field>
+<field name="COLUMN_REASON"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""reason""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="COLUMN_STATUS"
type="java.lang.String"
transient="false"
@@ -28980,6 +29021,50 @@
visibility="public"
>
</field>
+<field name="PAUSED_QUEUED_FOR_WIFI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_UNKNOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_FOR_NETWORK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PAUSED_WAITING_TO_RETRY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="STATUS_FAILED"
type="int"
transient="false"
@@ -48234,93 +48319,6 @@
>
</method>
</interface>
-<interface name="IOnPrimaryClipChangedListener"
- abstract="true"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<implements name="android.os.IInterface">
-</implements>
-<method name="dispatchPrimaryClipChanged"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<exception name="RemoteException" type="android.os.RemoteException">
-</exception>
-</method>
-</interface>
-<class name="IOnPrimaryClipChangedListener.Stub"
- extends="android.os.Binder"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<implements name="android.content.IOnPrimaryClipChangedListener">
-</implements>
-<constructor name="IOnPrimaryClipChangedListener.Stub"
- type="android.content.IOnPrimaryClipChangedListener.Stub"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</constructor>
-<method name="asBinder"
- return="android.os.IBinder"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="asInterface"
- return="android.content.IOnPrimaryClipChangedListener"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="obj" type="android.os.IBinder">
-</parameter>
-</method>
-<method name="onTransact"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="code" type="int">
-</parameter>
-<parameter name="data" type="android.os.Parcel">
-</parameter>
-<parameter name="reply" type="android.os.Parcel">
-</parameter>
-<parameter name="flags" type="int">
-</parameter>
-<exception name="RemoteException" type="android.os.RemoteException">
-</exception>
-</method>
-</class>
<class name="Intent"
extends="java.lang.Object"
abstract="false"
@@ -61065,6 +61063,16 @@
visibility="public"
>
</field>
+<field name="filename"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="flags"
type="int"
transient="false"
@@ -134947,6 +134955,17 @@
visibility="public"
>
</method>
+<method name="isExternalStorageRemovable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<field name="DIRECTORY_ALARMS"
type="java.lang.String"
transient="false"
@@ -139818,7 +139837,7 @@
visibility="public"
>
<method name="allowThreadDiskReads"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
abstract="false"
native="false"
synchronized="false"
@@ -139829,7 +139848,7 @@
>
</method>
<method name="allowThreadDiskWrites"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
abstract="false"
native="false"
synchronized="false"
@@ -139840,7 +139859,18 @@
>
</method>
<method name="getThreadPolicy"
- return="int"
+ return="android.os.StrictMode.ThreadPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVmPolicy"
+ return="android.os.StrictMode.VmPolicy"
abstract="false"
native="false"
synchronized="false"
@@ -139860,86 +139890,313 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="policyMask" type="int">
+<parameter name="policy" type="android.os.StrictMode.ThreadPolicy">
</parameter>
</method>
-<field name="DISALLOW_DISK_READ"
- type="int"
+<method name="setVmPolicy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policy" type="android.os.StrictMode.VmPolicy">
+</parameter>
+</method>
+</class>
+<class name="StrictMode.ThreadPolicy"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="LAX"
+ type="android.os.StrictMode.ThreadPolicy"
transient="false"
volatile="false"
- value="2"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="DISALLOW_DISK_WRITE"
- type="int"
+</class>
+<class name="StrictMode.ThreadPolicy.Builder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StrictMode.ThreadPolicy.Builder"
+ type="android.os.StrictMode.ThreadPolicy.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="StrictMode.ThreadPolicy.Builder"
+ type="android.os.StrictMode.ThreadPolicy.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="policy" type="android.os.StrictMode.ThreadPolicy">
+</parameter>
+</constructor>
+<method name="build"
+ return="android.os.StrictMode.ThreadPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectAll"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectDiskReads"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectDiskWrites"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="detectNetwork"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDeath"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDialog"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDropBox"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyLog"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitAll"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitDiskReads"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitDiskWrites"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="permitNetwork"
+ return="android.os.StrictMode.ThreadPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="StrictMode.VmPolicy"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<field name="LAX"
+ type="android.os.StrictMode.VmPolicy"
transient="false"
volatile="false"
- value="1"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
-<field name="DISALLOW_NETWORK"
- type="int"
- transient="false"
- volatile="false"
- value="4"
+</class>
+<class name="StrictMode.VmPolicy.Builder"
+ extends="java.lang.Object"
+ abstract="false"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
-</field>
-<field name="PENALTY_DEATH"
- type="int"
- transient="false"
- volatile="false"
- value="64"
- static="true"
- final="true"
+<constructor name="StrictMode.VmPolicy.Builder"
+ type="android.os.StrictMode.VmPolicy.Builder"
+ static="false"
+ final="false"
deprecated="not deprecated"
visibility="public"
>
-</field>
-<field name="PENALTY_DIALOG"
- type="int"
- transient="false"
- volatile="false"
- value="32"
- static="true"
- final="true"
+</constructor>
+<method name="build"
+ return="android.os.StrictMode.VmPolicy"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
deprecated="not deprecated"
visibility="public"
>
-</field>
-<field name="PENALTY_DROPBOX"
- type="int"
- transient="false"
- volatile="false"
- value="128"
- static="true"
- final="true"
+</method>
+<method name="detectAll"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
deprecated="not deprecated"
visibility="public"
>
-</field>
-<field name="PENALTY_LOG"
- type="int"
- transient="false"
- volatile="false"
- value="16"
- static="true"
- final="true"
+</method>
+<method name="detectLeakedSqlLiteObjects"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
deprecated="not deprecated"
visibility="public"
>
-</field>
+</method>
+<method name="penaltyDeath"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyDropBox"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="penaltyLog"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
</class>
<class name="SystemClock"
extends="java.lang.Object"
@@ -140501,8 +140758,6 @@
</parameter>
<parameter name="listener" type="android.os.storage.OnObbStateChangeListener">
</parameter>
-<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
-</exception>
</method>
<method name="unregisterListener"
return="void"
@@ -189568,6 +189823,219 @@
>
</field>
</class>
+<class name="DragEvent"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAction"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipData"
+ return="android.content.ClipData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getClipDescription"
+ return="android.content.ClipDescription"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="action" type="int">
+</parameter>
+<parameter name="x" type="float">
+</parameter>
+<parameter name="y" type="float">
+</parameter>
+<parameter name="description" type="android.content.ClipDescription">
+</parameter>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
+</method>
+<method name="obtain"
+ return="android.view.DragEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="source" type="android.view.DragEvent">
+</parameter>
+</method>
+<method name="recycle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ 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="ACTION_DRAG_ENDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_ENTERED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_EXITED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_LOCATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DRAG_STARTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DROP"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="FocusFinder"
extends="java.lang.Object"
abstract="false"
@@ -195957,6 +196425,19 @@
<parameter name="y" type="float">
</parameter>
</method>
+<method name="transform"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="matrix" type="android.graphics.Matrix">
+</parameter>
+</method>
<method name="writeToParcel"
return="void"
abstract="false"
@@ -198511,6 +198992,19 @@
<parameter name="hint" type="int">
</parameter>
</method>
+<method name="dispatchDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
<method name="dispatchDraw"
return="void"
abstract="false"
@@ -200476,6 +200970,19 @@
<parameter name="hint" type="int">
</parameter>
</method>
+<method name="onDragEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="event" type="android.view.DragEvent">
+</parameter>
+</method>
<method name="onDraw"
return="void"
abstract="false"
@@ -200489,6 +200996,19 @@
<parameter name="canvas" type="android.graphics.Canvas">
</parameter>
</method>
+<method name="onDrawDragThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
<method name="onDrawScrollBars"
return="void"
abstract="false"
@@ -200682,6 +201202,17 @@
<parameter name="heightMeasureSpec" type="int">
</parameter>
</method>
+<method name="onMeasureDragThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
<method name="onRestoreInstanceState"
return="void"
abstract="false"
@@ -201347,6 +201878,21 @@
<parameter name="contentDescription" type="java.lang.CharSequence">
</parameter>
</method>
+<method name="setDragThumbnailDimension"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="width" type="int">
+</parameter>
+<parameter name="height" type="int">
+</parameter>
+</method>
<method name="setDrawingCacheBackgroundColor"
return="void"
abstract="false"
@@ -202871,6 +203417,53 @@
>
</field>
</class>
+<class name="View.DragThumbnailBuilder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="View.DragThumbnailBuilder"
+ type="android.view.View.DragThumbnailBuilder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</constructor>
+<method name="onDrawThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="onProvideThumbnailMetrics"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="thumbnailSize" type="android.graphics.Point">
+</parameter>
+<parameter name="thumbnailTouchPoint" type="android.graphics.Point">
+</parameter>
+</method>
+</class>
<class name="View.MeasureSpec"
extends="java.lang.Object"
abstract="false"
@@ -223190,7 +223783,7 @@
>
</method>
<method name="getInAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator<?>"
abstract="false"
native="false"
synchronized="false"
@@ -223201,7 +223794,7 @@
>
</method>
<method name="getOutAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator<?>"
abstract="false"
native="false"
synchronized="false"
@@ -223317,7 +223910,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="inAnimation" type="android.view.animation.Animation">
+<parameter name="inAnimation" type="android.animation.ObjectAnimator<?>">
</parameter>
</method>
<method name="setInAnimation"
@@ -223345,7 +223938,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="outAnimation" type="android.view.animation.Animation">
+<parameter name="outAnimation" type="android.animation.ObjectAnimator<?>">
</parameter>
</method>
<method name="setOutAnimation"
@@ -241659,7 +242252,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
</parameter>
</method>
</interface>
@@ -249418,7 +250011,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="n" type="long">
+<parameter name="byteCount" type="long">
</parameter>
<exception name="IOException" type="java.io.IOException">
</exception>
@@ -283808,9 +284401,9 @@
deprecated="not deprecated"
visibility="protected"
>
-<parameter name="url1" type="java.net.URL">
+<parameter name="a" type="java.net.URL">
</parameter>
-<parameter name="url2" type="java.net.URL">
+<parameter name="b" type="java.net.URL">
</parameter>
</method>
<method name="openConnection"
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 39b3a20..37c8ad0 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -34,7 +34,6 @@
private String[] mArgs;
private int mNextArg;
- private String mCurArgData;
public static void main(String[] args) {
try {
@@ -274,6 +273,10 @@
}
private void printRestoreSets(RestoreSet[] sets) {
+ if (sets == null || sets.length == 0) {
+ System.out.println("No restore sets");
+ return;
+ }
for (RestoreSet s : sets) {
System.out.println(" " + Long.toHexString(s.token) + " : " + s.name);
}
diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c
index 60cc521..afa64f8 100644
--- a/cmds/keystore/keystore.c
+++ b/cmds/keystore/keystore.c
@@ -143,15 +143,20 @@
send(the_socket, message, length, 0);
}
-/* Here is the file format. Values are encrypted by AES CBC, and MD5 is used to
- * compute their checksums. To make the files portable, the length is stored in
- * network order. Note that the first four bytes are reserved for future use and
- * are always set to zero in this implementation. */
+/* Here is the file format. There are two parts in blob.value, the secret and
+ * the description. The secret is stored in ciphertext, and its original size
+ * can be found in blob.length. The description is stored after the secret in
+ * plaintext, and its size is specified in blob.info. The total size of the two
+ * parts must be no more than VALUE_SIZE bytes. The first three bytes of the
+ * file are reserved for future use and are always set to zero. Fields other
+ * than blob.info, blob.length, and blob.value are modified by encrypt_blob()
+ * and decrypt_blob(). Thus they should not be accessed from outside. */
static int the_entropy = -1;
static struct __attribute__((packed)) {
- uint32_t reserved;
+ uint8_t reserved[3];
+ uint8_t info;
uint8_t vector[AES_BLOCK_SIZE];
uint8_t encrypted[0];
uint8_t digest[MD5_DIGEST_LENGTH];
@@ -166,13 +171,17 @@
int length;
int fd;
- if (read(the_entropy, vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
+ if (read(the_entropy, blob.vector, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) {
return SYSTEM_ERROR;
}
- length = blob.length + blob.value - blob.encrypted;
+ length = blob.length + (blob.value - blob.encrypted);
length = (length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
+ if (blob.info != 0) {
+ memmove(&blob.encrypted[length], &blob.value[blob.length], blob.info);
+ }
+
blob.length = htonl(blob.length);
MD5(blob.digested, length - (blob.digested - blob.encrypted), blob.digest);
@@ -180,8 +189,8 @@
AES_cbc_encrypt(blob.encrypted, blob.encrypted, length, aes_key, vector,
AES_ENCRYPT);
- blob.reserved = 0;
- length += blob.encrypted - (uint8_t *)&blob;
+ memset(blob.reserved, 0, sizeof(blob.reserved));
+ length += (blob.encrypted - (uint8_t *)&blob) + blob.info;
fd = open(".tmp", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
length -= write(fd, &blob, length);
@@ -200,7 +209,7 @@
length = read(fd, &blob, sizeof(blob));
close(fd);
- length -= blob.encrypted - (uint8_t *)&blob;
+ length -= (blob.encrypted - (uint8_t *)&blob) + blob.info;
if (length < blob.value - blob.encrypted || length % AES_BLOCK_SIZE != 0) {
return VALUE_CORRUPTED;
}
@@ -215,8 +224,13 @@
length -= blob.value - blob.digested;
blob.length = ntohl(blob.length);
- return (blob.length < 0 || blob.length > length) ? VALUE_CORRUPTED :
- NO_ERROR;
+ if (blob.length < 0 || blob.length > length) {
+ return VALUE_CORRUPTED;
+ }
+ if (blob.info != 0) {
+ memmove(&blob.value[blob.length], &blob.value[length], blob.info);
+ }
+ return NO_ERROR;
}
/* Here are the actions. Each of them is a function without arguments. All
@@ -266,6 +280,7 @@
char name[NAME_MAX];
int n = sprintf(name, "%u_", uid);
encode_key(&name[n], params[0].value, params[0].length);
+ blob.info = 0;
blob.length = params[1].length;
memcpy(blob.value, params[1].value, params[1].length);
return encrypt_blob(name, &encryption_key);
@@ -336,56 +351,88 @@
#define MASTER_KEY_FILE ".masterkey"
#define MASTER_KEY_SIZE 16
+#define SALT_SIZE 16
-static void generate_key(uint8_t *key, uint8_t *password, int length)
+static void set_key(uint8_t *key, uint8_t *password, int length, uint8_t *salt)
{
- PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore",
- sizeof("keystore"), 1024, MASTER_KEY_SIZE, key);
+ if (salt) {
+ PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, salt, SALT_SIZE,
+ 8192, MASTER_KEY_SIZE, key);
+ } else {
+ PKCS5_PBKDF2_HMAC_SHA1((char *)password, length, (uint8_t *)"keystore",
+ sizeof("keystore"), 1024, MASTER_KEY_SIZE, key);
+ }
}
+/* Here is the history. To improve the security, the parameters to generate the
+ * master key has been changed. To make a seamless transition, we update the
+ * file using the same password when the user unlock it for the first time. If
+ * any thing goes wrong during the transition, the new file will not overwrite
+ * the old one. This avoids permanent damages of the existing data. */
+
static int8_t password()
{
uint8_t key[MASTER_KEY_SIZE];
AES_KEY aes_key;
- int n;
+ int8_t response = SYSTEM_ERROR;
if (state == UNINITIALIZED) {
- blob.length = MASTER_KEY_SIZE;
if (read(the_entropy, blob.value, MASTER_KEY_SIZE) != MASTER_KEY_SIZE) {
return SYSTEM_ERROR;
}
} else {
- generate_key(key, params[0].value, params[0].length);
+ int fd = open(MASTER_KEY_FILE, O_RDONLY);
+ uint8_t *salt = NULL;
+ if (fd != -1) {
+ int length = read(fd, &blob, sizeof(blob));
+ close(fd);
+ if (length > SALT_SIZE && blob.info == SALT_SIZE) {
+ salt = (uint8_t *)&blob + length - SALT_SIZE;
+ }
+ }
+
+ set_key(key, params[0].value, params[0].length, salt);
AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
- n = decrypt_blob(MASTER_KEY_FILE, &aes_key);
- if (n == SYSTEM_ERROR) {
+ response = decrypt_blob(MASTER_KEY_FILE, &aes_key);
+ if (response == SYSTEM_ERROR) {
return SYSTEM_ERROR;
}
- if (n != NO_ERROR || blob.length != MASTER_KEY_SIZE) {
+ if (response != NO_ERROR || blob.length != MASTER_KEY_SIZE) {
if (retry <= 0) {
reset();
return UNINITIALIZED;
}
return WRONG_PASSWORD + --retry;
}
+
+ if (!salt && params[1].length == -1) {
+ params[1] = params[0];
+ }
}
if (params[1].length == -1) {
memcpy(key, blob.value, MASTER_KEY_SIZE);
} else {
- generate_key(key, params[1].value, params[1].length);
+ uint8_t *salt = &blob.value[MASTER_KEY_SIZE];
+ if (read(the_entropy, salt, SALT_SIZE) != SALT_SIZE) {
+ return SYSTEM_ERROR;
+ }
+
+ set_key(key, params[1].value, params[1].length, salt);
AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &aes_key);
memcpy(key, blob.value, MASTER_KEY_SIZE);
- n = encrypt_blob(MASTER_KEY_FILE, &aes_key);
+ blob.info = SALT_SIZE;
+ blob.length = MASTER_KEY_SIZE;
+ response = encrypt_blob(MASTER_KEY_FILE, &aes_key);
}
- if (n == NO_ERROR) {
+ if (response == NO_ERROR) {
AES_set_encrypt_key(key, MASTER_KEY_SIZE * 8, &encryption_key);
AES_set_decrypt_key(key, MASTER_KEY_SIZE * 8, &decryption_key);
state = NO_ERROR;
retry = MAX_RETRY;
}
- return n;
+ return response;
}
static int8_t lock()
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
index 141f69b..4b4923e 100644
--- a/cmds/keystore/keystore_get.h
+++ b/cmds/keystore/keystore_get.h
@@ -32,7 +32,7 @@
#endif
/* This function is provided for native components to get values from keystore.
- * Users are required to link against libcutils. Keys are values are 8-bit safe.
+ * Users are required to link against libcutils. Keys and values are 8-bit safe.
* The first two arguments are the key and its length. The third argument
* specifies the buffer to store the retrieved value, which must be an array of
* KEYSTORE_MESSAGE_SIZE bytes. This function returns the length of the value or
@@ -65,7 +65,10 @@
}
offset += n;
}
+ } else {
+ length = -1;
}
+
close(sock);
return length;
}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6ce5b86..bc5e10d 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -17,32 +17,21 @@
#include <unistd.h>
#include <fcntl.h>
-#include <utils/Log.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-
#include <binder/IMemory.h>
-#include <surfaceflinger/ISurfaceComposer.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
using namespace android;
int main(int argc, char** argv)
{
- const String16 name("SurfaceFlinger");
- sp<ISurfaceComposer> composer;
- if (getService(name, &composer) != NO_ERROR)
+ ScreenshotClient screenshot;
+ if (screenshot.update() != NO_ERROR)
return 0;
- sp<IMemoryHeap> heap;
- uint32_t w, h;
- PixelFormat f;
- status_t err = composer->captureScreen(0, &heap, &w, &h, &f);
- if (err != NO_ERROR)
- return 0;
-
- uint8_t* base = (uint8_t*)heap->getBase();
+ void const* base = screenshot.getPixels();
+ uint32_t w = screenshot.getWidth();
+ uint32_t h = screenshot.getHeight();
+ uint32_t f = screenshot.getFormat();
int fd = dup(STDOUT_FILENO);
write(fd, &w, 4);
write(fd, &h, 4);
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 9a97284..cbdf119 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -53,6 +53,31 @@
LOCAL_SRC_FILES:= \
SineSource.cpp \
+ recordvideo.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright liblog libutils libbinder
+
+LOCAL_C_INCLUDES:= \
+ $(JNI_H_INCLUDE) \
+ frameworks/base/media/libstagefright \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_MODULE:= recordvideo
+
+include $(BUILD_EXECUTABLE)
+
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ SineSource.cpp \
audioloop.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp
new file mode 100644
index 0000000..330fbc2
--- /dev/null
+++ b/cmds/stagefright/recordvideo.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SineSource.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/MediaPlayerInterface.h>
+
+using namespace android;
+
+// print usage showing how to use this utility to record videos
+static void usage(const char *me) {
+ fprintf(stderr, "usage: %s\n", me);
+ fprintf(stderr, " -h(elp)\n");
+ fprintf(stderr, " -b bit rate in bits per second (default 300000)\n");
+ fprintf(stderr, " -c YUV420 color format: [0] semi planar or [1] planar (default 1)\n");
+ fprintf(stderr, " -f frame rate in frames per second (default 30)\n");
+ fprintf(stderr, " -i I frame interval in seconds (default 1)\n");
+ fprintf(stderr, " -n number of frames to be recorded (default 300)\n");
+ fprintf(stderr, " -w width in pixels (default 176)\n");
+ fprintf(stderr, " -t height in pixels (default 144)\n");
+ fprintf(stderr, " -v video codec: [0] AVC [1] M4V [2] H263 (default 0)\n");
+ exit(1);
+}
+
+class DummySource : public MediaSource {
+
+public:
+ DummySource(int width, int height, int nFrames, int fps, int colorFormat)
+ : mWidth(width),
+ mHeight(height),
+ mMaxNumFrames(nFrames),
+ mFrameRate(fps),
+ mColorFormat(colorFormat),
+ mSize((width * height * 3) / 2) {
+ mGroup.add_buffer(new MediaBuffer(mSize));
+
+ // Check the color format to make sure
+ // that the buffer size mSize it set correctly above.
+ CHECK(colorFormat == OMX_COLOR_FormatYUV420SemiPlanar ||
+ colorFormat == OMX_COLOR_FormatYUV420Planar);
+ }
+
+ virtual sp<MetaData> getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setInt32(kKeyWidth, mWidth);
+ meta->setInt32(kKeyHeight, mHeight);
+ meta->setInt32(kKeyColorFormat, mColorFormat);
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
+
+ return meta;
+ }
+
+ virtual status_t start(MetaData *params) {
+ mNumFramesOutput = 0;
+ return OK;
+ }
+
+ virtual status_t stop() {
+ return OK;
+ }
+
+ virtual status_t read(
+ MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+
+ if (mNumFramesOutput % 10 == 0) {
+ fprintf(stderr, ".");
+ }
+ if (mNumFramesOutput == mMaxNumFrames) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ status_t err = mGroup.acquire_buffer(buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ char x = (char)((double)rand() / RAND_MAX * 255);
+ memset((*buffer)->data(), x, mSize);
+ (*buffer)->set_range(0, mSize);
+ (*buffer)->meta_data()->clear();
+ (*buffer)->meta_data()->setInt64(
+ kKeyTime, (mNumFramesOutput * 1000000) / mFrameRate);
+ ++mNumFramesOutput;
+
+ return OK;
+ }
+
+protected:
+ virtual ~DummySource() {}
+
+private:
+ MediaBufferGroup mGroup;
+ int mWidth, mHeight;
+ int mMaxNumFrames;
+ int mFrameRate;
+ int mColorFormat;
+ size_t mSize;
+ int64_t mNumFramesOutput;;
+
+ DummySource(const DummySource &);
+ DummySource &operator=(const DummySource &);
+};
+
+sp<MediaSource> createSource(const char *filename) {
+ sp<MediaSource> source;
+
+ sp<MediaExtractor> extractor =
+ MediaExtractor::Create(new FileSource(filename));
+ if (extractor == NULL) {
+ return NULL;
+ }
+
+ size_t num_tracks = extractor->countTracks();
+
+ sp<MetaData> meta;
+ for (size_t i = 0; i < num_tracks; ++i) {
+ meta = extractor->getTrackMetaData(i);
+ CHECK(meta.get() != NULL);
+
+ const char *mime;
+ if (!meta->findCString(kKeyMIMEType, &mime)) {
+ continue;
+ }
+
+ if (strncasecmp(mime, "video/", 6)) {
+ continue;
+ }
+
+ source = extractor->getTrack(i);
+ break;
+ }
+
+ return source;
+}
+
+enum {
+ kYUV420SP = 0,
+ kYUV420P = 1,
+};
+
+// returns -1 if mapping of the given color is unsuccessful
+// returns an omx color enum value otherwise
+static int translateColorToOmxEnumValue(int color) {
+ switch (color) {
+ case kYUV420SP:
+ return OMX_COLOR_FormatYUV420SemiPlanar;
+ case kYUV420P:
+ return OMX_COLOR_FormatYUV420Planar;
+ default:
+ fprintf(stderr, "Unsupported color: %d\n", color);
+ return -1;
+ }
+}
+
+int main(int argc, char **argv) {
+
+ // Default values for the program if not overwritten
+ int frameRateFps = 30;
+ int width = 176;
+ int height = 144;
+ int bitRateBps = 300000;
+ int iFramesIntervalSeconds = 1;
+ int colorFormat = OMX_COLOR_FormatYUV420Planar;
+ int nFrames = 300;
+ int codec = 0;
+ const char *fileName = "/sdcard/output.mp4";
+
+ android::ProcessState::self()->startThreadPool();
+ int res;
+ while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:v:o:h")) >= 0) {
+ switch (res) {
+ case 'b':
+ {
+ bitRateBps = atoi(optarg);
+ break;
+ }
+
+ case 'c':
+ {
+ colorFormat = translateColorToOmxEnumValue(atoi(optarg));
+ if (colorFormat == -1) {
+ usage(argv[0]);
+ }
+ break;
+ }
+
+ case 'f':
+ {
+ frameRateFps = atoi(optarg);
+ break;
+ }
+
+ case 'i':
+ {
+ iFramesIntervalSeconds = atoi(optarg);
+ break;
+ }
+
+ case 'n':
+ {
+ nFrames = atoi(optarg);
+ break;
+ }
+
+ case 'w':
+ {
+ width = atoi(optarg);
+ break;
+ }
+
+ case 't':
+ {
+ height = atoi(optarg);
+ break;
+ }
+
+ case 'v':
+ {
+ codec = atoi(optarg);
+ if (codec < 0 || codec > 2) {
+ usage(argv[0]);
+ }
+ break;
+ }
+
+ case 'h':
+ default:
+ {
+ usage(argv[0]);
+ break;
+ }
+ }
+ }
+
+ OMXClient client;
+ CHECK_EQ(client.connect(), OK);
+
+ status_t err = OK;
+ sp<MediaSource> decoder = new DummySource(width, height, nFrames, frameRateFps, colorFormat);
+
+ sp<MetaData> enc_meta = new MetaData;
+ switch (codec) {
+ case 1:
+ enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+ break;
+ case 2:
+ enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
+ break;
+ default:
+ enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+ break;
+ }
+ enc_meta->setInt32(kKeyWidth, width);
+ enc_meta->setInt32(kKeyHeight, height);
+ enc_meta->setInt32(kKeySampleRate, frameRateFps);
+ enc_meta->setInt32(kKeyBitRate, bitRateBps);
+ enc_meta->setInt32(kKeyStride, width);
+ enc_meta->setInt32(kKeySliceHeight, height);
+ enc_meta->setInt32(kKeyIFramesInterval, iFramesIntervalSeconds);
+ enc_meta->setInt32(kKeyColorFormat, colorFormat);
+
+ sp<MediaSource> encoder =
+ OMXCodec::Create(
+ client.interface(), enc_meta, true /* createEncoder */, decoder);
+
+ sp<MPEG4Writer> writer = new MPEG4Writer(fileName);
+ writer->addSource(encoder);
+ int64_t start = systemTime();
+ CHECK_EQ(OK, writer->start());
+ while (!writer->reachedEOS()) {
+ }
+ err = writer->stop();
+ int64_t end = systemTime();
+
+ printf("$\n");
+ client.disconnect();
+
+ if (err != OK && err != ERROR_END_OF_STREAM) {
+ fprintf(stderr, "record failed: %d\n", err);
+ return 1;
+ }
+ fprintf(stderr, "encoding %d frames in %lld us\n", nFrames, (end-start)/1000);
+ fprintf(stderr, "encoding speed is: %.2f fps\n", (nFrames * 1E9) / (end-start));
+ return 0;
+}
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 69ad67e..52f0f16 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -459,21 +459,22 @@
* @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
* {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
* duration is being set.
- * @param animator The animation being assigned.
+ * @param animator The animation being assigned. A value of <code>null</code> means that no
+ * animation will be run for the specified transitionType.
*/
public void setAnimator(int transitionType, Animator animator) {
switch (transitionType) {
case CHANGE_APPEARING:
- mChangingAppearingAnim = (animator != null) ? animator : defaultChangeIn;
+ mChangingAppearingAnim = animator;
break;
case CHANGE_DISAPPEARING:
- mChangingDisappearingAnim = (animator != null) ? animator : defaultChangeOut;
+ mChangingDisappearingAnim = animator;
break;
case APPEARING:
- mAppearingAnim = (animator != null) ? animator : defaultFadeIn;
+ mAppearingAnim = animator;
break;
case DISAPPEARING:
- mDisappearingAnim = (animator != null) ? animator : defaultFadeOut;
+ mDisappearingAnim = animator;
break;
}
}
@@ -516,6 +517,14 @@
* transition is occuring because an item is being added to or removed from the parent.
*/
private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
+
+ Animator baseAnimator = (changeReason == APPEARING) ?
+ mChangingAppearingAnim : mChangingDisappearingAnim;
+ // If the animation is null, there's nothing to do
+ if (baseAnimator == null) {
+ return;
+ }
+
// reset the inter-animation delay, in case we use it later
staggerDelay = 0;
@@ -540,9 +549,10 @@
}
// Make a copy of the appropriate animation
- final Animator anim = (changeReason == APPEARING) ?
- mChangingAppearingAnim.clone() :
- mChangingDisappearingAnim.clone();
+ final Animator anim = baseAnimator.clone();
+
+ // Cache the animation in case we need to cancel it later
+ currentAnimations.put(child, anim);
// Set the target object for the animation
anim.setTarget(child);
@@ -553,13 +563,10 @@
// Add a listener to track layout changes on this view. If we don't get a callback,
// then there's nothing to animate.
- View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Cache the animation in case we need to cancel it later
- currentAnimations.put(child, anim);
-
// Tell the animation to extract end values from the changed object
anim.setupEndValues();
@@ -577,19 +584,6 @@
anim.setStartDelay(startDelay);
anim.setDuration(duration);
- // Remove the animation from the cache when it ends
- anim.addListener(new AnimatorListenerAdapter() {
- private boolean canceled = false;
- public void onAnimationCancel(Animator animator) {
- // we remove canceled animations immediately, not here
- canceled = true;
- }
- public void onAnimationEnd(Animator animator) {
- if (!canceled) {
- currentAnimations.remove(child);
- }
- }
- });
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
@@ -601,6 +595,22 @@
layoutChangeListenerMap.remove(child);
}
};
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+ private boolean canceled = false;
+ public void onAnimationCancel(Animator animator) {
+ // we remove canceled animations immediately, not here
+ canceled = true;
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+ public void onAnimationEnd(Animator animator) {
+ if (!canceled) {
+ currentAnimations.remove(child);
+ }
+ }
+ });
+
child.addOnLayoutChangeListener(listener);
// cache the listener for later removal
layoutChangeListenerMap.put(child, listener);
@@ -630,6 +640,14 @@
* @param child The View being added to the ViewGroup.
*/
private void runAppearingTransition(final ViewGroup parent, final View child) {
+ if (mAppearingAnim == null) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ return;
+ }
Animator anim = mAppearingAnim.clone();
anim.setTarget(child);
anim.setStartDelay(mAppearingDelay);
@@ -656,13 +674,22 @@
* @param child The View being removed from the ViewGroup.
*/
private void runDisappearingTransition(final ViewGroup parent, final View child) {
+ if (mDisappearingAnim == null) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ return;
+ }
Animator anim = mDisappearingAnim.clone();
anim.setStartDelay(mDisappearingDelay);
anim.setDuration(mDisappearingDuration);
anim.setTarget(child);
if (mListeners != null) {
anim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
for (TransitionListener listener : mListeners) {
listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
}
@@ -684,7 +711,7 @@
* @param parent The ViewGroup to which the View is being added.
* @param child The View being added to the ViewGroup.
*/
- public void childAdd(ViewGroup parent, View child) {
+ public void addChild(ViewGroup parent, View child) {
if (mListeners != null) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, APPEARING);
@@ -695,6 +722,19 @@
}
/**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ public void showChild(ViewGroup parent, View child) {
+ addChild(parent, child);
+ }
+
+ /**
* This method is called by ViewGroup when a child view is about to be removed from the
* container. This callback starts the process of a transition; we grab the starting
* values, listen for changes to all of the children of the container, and start appropriate
@@ -703,7 +743,7 @@
* @param parent The ViewGroup from which the View is being removed.
* @param child The View being removed from the ViewGroup.
*/
- public void childRemove(ViewGroup parent, View child) {
+ public void removeChild(ViewGroup parent, View child) {
if (mListeners != null) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, DISAPPEARING);
@@ -714,6 +754,19 @@
}
/**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ public void hideChild(ViewGroup parent, View child) {
+ removeChild(parent, child);
+ }
+
+ /**
* Add a listener that will be called when the bounds of the view change due to
* layout processing.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b8bbc88..df18ce7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3352,7 +3352,7 @@
while (i.hasNext()) {
ProviderInfo cpi = i.next();
StringBuilder buf = new StringBuilder(128);
- buf.append("Publishing provider ");
+ buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 69c99cc..013032c 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -47,6 +47,10 @@
* Instances of this class should be obtained through
* {@link android.content.Context#getSystemService(String)} by passing
* {@link android.content.Context#DOWNLOAD_SERVICE}.
+ *
+ * Apps that request downloads through this API should register a broadcast receiver for
+ * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
+ * download in a notification or from the downloads UI.
*/
public class DownloadManager {
/**
@@ -100,16 +104,23 @@
public final static String COLUMN_STATUS = "status";
/**
- * Indicates the type of error that occurred, when {@link #COLUMN_STATUS} is
- * {@link #STATUS_FAILED}. If an HTTP error occurred, this will hold the HTTP status code as
- * defined in RFC 2616. Otherwise, it will hold one of the ERROR_* constants.
+ * Provides more detail on the status of the download. Its meaning depends on the value of
+ * {@link #COLUMN_STATUS}.
*
- * If {@link #COLUMN_STATUS} is not {@link #STATUS_FAILED}, this column's value is undefined.
+ * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
+ * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
+ * 2616. Otherwise, it will hold one of the ERROR_* constants.
+ *
+ * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
+ * paused. It will hold one of the PAUSED_* constants.
+ *
+ * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
+ * column's value is undefined.
*
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
* status codes</a>
*/
- public final static String COLUMN_ERROR_CODE = "error_code";
+ public final static String COLUMN_REASON = "reason";
/**
* Number of bytes download so far.
@@ -156,61 +167,84 @@
public final static int ERROR_UNKNOWN = 1000;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when a storage issue arises which doesn't fit under any
+ * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
* other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
* {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
*/
public final static int ERROR_FILE_ERROR = 1001;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when an HTTP code was received that download manager
+ * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
* can't handle.
*/
public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when an error receiving or processing data occurred at
+ * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
* the HTTP level.
*/
public final static int ERROR_HTTP_DATA_ERROR = 1004;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when there were too many redirects.
+ * Value of {@link #COLUMN_REASON} when there were too many redirects.
*/
public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when there was insufficient storage space. Typically,
+ * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
* this is because the SD card is full.
*/
public final static int ERROR_INSUFFICIENT_SPACE = 1006;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when no external storage device was found. Typically,
+ * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
* this is because the SD card is not mounted.
*/
public final static int ERROR_DEVICE_NOT_FOUND = 1007;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't
+ * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
* resume the download.
*/
public final static int ERROR_CANNOT_RESUME = 1008;
/**
- * Value of {@link #COLUMN_ERROR_CODE} when the requested destination file already exists (the
+ * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
* download manager will not overwrite an existing file).
*/
public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
/**
+ * Value of {@link #COLUMN_REASON} when the download is paused because some network error
+ * occurred and the download manager is waiting before retrying the request.
+ */
+ public final static int PAUSED_WAITING_TO_RETRY = 1;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
+ * proceed.
+ */
+ public final static int PAUSED_WAITING_FOR_NETWORK = 2;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
+ * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
+ */
+ public final static int PAUSED_QUEUED_FOR_WIFI = 3;
+
+ /**
+ * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
+ */
+ public final static int PAUSED_UNKNOWN = 4;
+
+ /**
* Broadcast intent action sent by the download manager when a download completes.
*/
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
/**
- * Broadcast intent action sent by the download manager when a running download notification is
- * clicked.
+ * Broadcast intent action sent by the download manager when the user clicks on a running
+ * download, either from a system notification or from the downloads UI.
*/
public final static String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
@@ -236,7 +270,7 @@
COLUMN_TOTAL_SIZE_BYTES,
COLUMN_LOCAL_URI,
COLUMN_STATUS,
- COLUMN_ERROR_CODE,
+ COLUMN_REASON,
COLUMN_BYTES_DOWNLOADED_SO_FAR,
COLUMN_LAST_MODIFIED_TIMESTAMP
};
@@ -258,7 +292,7 @@
};
private static final Set<String> LONG_COLUMNS = new HashSet<String>(
- Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_ERROR_CODE,
+ Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_REASON,
COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP));
/**
@@ -383,7 +417,9 @@
}
/**
- * Set the title of this download, to be displayed in notifications (if enabled)
+ * Set the title of this download, to be displayed in notifications (if enabled). If no
+ * title is given, a default one will be assigned based on the download filename, once the
+ * download starts.
* @return this object
*/
public Request setTitle(CharSequence title) {
@@ -617,8 +653,10 @@
parts.add(statusClause("=", Downloads.STATUS_RUNNING));
}
if ((mStatusFlags & STATUS_PAUSED) != 0) {
- parts.add(statusClause("=", Downloads.STATUS_PENDING_PAUSED));
- parts.add(statusClause("=", Downloads.STATUS_RUNNING_PAUSED));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
+ parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
}
if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
@@ -891,7 +929,11 @@
if (destinationType == Downloads.Impl.DESTINATION_EXTERNAL) {
// return stored destination for legacy external download
- return Uri.fromFile(new File(getUnderlyingString(Downloads.Impl._DATA))).toString();
+ String localPath = getUnderlyingString(Downloads.Impl._DATA);
+ if (localPath == null) {
+ return null;
+ }
+ return Uri.fromFile(new File(localPath)).toString();
}
// return content URI for cache download
@@ -914,8 +956,8 @@
if (column.equals(COLUMN_STATUS)) {
return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
- if (column.equals(COLUMN_ERROR_CODE)) {
- return translateErrorCode((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
+ if (column.equals(COLUMN_REASON)) {
+ return getReason((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
@@ -924,10 +966,36 @@
return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
}
- private long translateErrorCode(int status) {
- if (translateStatus(status) != STATUS_FAILED) {
- return 0; // arbitrary value when status is not an error
+ private long getReason(int status) {
+ switch (translateStatus(status)) {
+ case STATUS_FAILED:
+ return getErrorCode(status);
+
+ case STATUS_PAUSED:
+ return getPausedReason(status);
+
+ default:
+ return 0; // arbitrary value when status is not an error
}
+ }
+
+ private long getPausedReason(int status) {
+ switch (status) {
+ case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+ return PAUSED_WAITING_TO_RETRY;
+
+ case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+ return PAUSED_WAITING_FOR_NETWORK;
+
+ case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
+ return PAUSED_QUEUED_FOR_WIFI;
+
+ default:
+ return PAUSED_UNKNOWN;
+ }
+ }
+
+ private long getErrorCode(int status) {
if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
|| (500 <= status && status < 600)) {
// HTTP status code
@@ -973,7 +1041,7 @@
return super.getString(super.getColumnIndex(column));
}
- private long translateStatus(int status) {
+ private int translateStatus(int status) {
switch (status) {
case Downloads.STATUS_PENDING:
return STATUS_PENDING;
@@ -981,8 +1049,10 @@
case Downloads.STATUS_RUNNING:
return STATUS_RUNNING;
- case Downloads.STATUS_PENDING_PAUSED:
- case Downloads.STATUS_RUNNING_PAUSED:
+ case Downloads.Impl.STATUS_PAUSED_BY_APP:
+ case Downloads.Impl.STATUS_WAITING_TO_RETRY:
+ case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
+ case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return STATUS_PAUSED;
case Downloads.STATUS_SUCCESS:
diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java
index 638be2d..375a6dd 100644
--- a/core/java/android/bluetooth/AtCommandResult.java
+++ b/core/java/android/bluetooth/AtCommandResult.java
@@ -16,8 +16,6 @@
package android.bluetooth;
-import java.util.*;
-
/**
* The result of execution of an single AT command.<p>
*
diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java
index 1ea3150..328fb2b 100644
--- a/core/java/android/bluetooth/AtParser.java
+++ b/core/java/android/bluetooth/AtParser.java
@@ -16,16 +16,13 @@
package android.bluetooth;
-import android.bluetooth.AtCommandHandler;
-import android.bluetooth.AtCommandResult;
-
import java.util.*;
/**
* An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
* <p>
*
- * Conforment with the subset of V.250 required for implementation of the
+ * Conformant with the subset of V.250 required for implementation of the
* Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
* specifications. Also implements some V.250 features not required by
* Bluetooth - such as chained commands.<p>
@@ -48,7 +45,7 @@
* are no arguments for get commands.
* <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
* there is a single integer argument in this case. In the general case then
- * can be zero or more arguments (comma deliminated) each of integer or string
+ * can be zero or more arguments (comma delimited) each of integer or string
* form.
* <li>Test Command. For example "AT+VGM=?. No arguments.
* </ul>
@@ -60,7 +57,7 @@
* headset/handsfree use this is acceptable, because they only use the basic
* commands ATA and ATD, which are not allowed to be chained. For general V.250
* use we would need to improve this class to allow Basic command chaining -
- * however its tricky to get right becuase there is no deliminator for Basic
+ * however it's tricky to get right because there is no delimiter for Basic
* command chaining.<p>
*
* Extended commands can be chained. For example:<p>
@@ -71,7 +68,7 @@
* AT+CIMI
* Except that only one final result code is return (although several
* intermediate responses may be returned), and as soon as one command in the
- * chain fails the rest are abandonded.<p>
+ * chain fails the rest are abandoned.<p>
*
* Handlers are registered by there command name via register(Char c, ...) or
* register(String s, ...). Handlers for Basic command should be registered by
@@ -80,7 +77,7 @@
*
* Refer to:<ul>
* <li>ITU-T Recommendation V.250
- * <li>ETSI TS 127.007 (AT Comannd set for User Equipment, 3GPP TS 27.007)
+ * <li>ETSI TS 127.007 (AT Command set for User Equipment, 3GPP TS 27.007)
* <li>Bluetooth Headset Profile Spec (K6)
* <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
* </ul>
@@ -188,7 +185,7 @@
}
/**
- * Break an argument string into individual arguments (comma deliminated).
+ * Break an argument string into individual arguments (comma delimited).
* Integer arguments are turned into Integer objects. Otherwise a String
* object is used.
*/
@@ -212,7 +209,7 @@
}
/**
- * Return the index of the end of character after the last characeter in
+ * Return the index of the end of character after the last character in
* the extended command name. Uses the V.250 spec for allowed command
* names.
*/
@@ -244,7 +241,7 @@
* Processes an incoming AT command line.<p>
* This method will invoke zero or one command handler methods for each
* command in the command line.<p>
- * @param raw_input The AT input, without EOL deliminator (e.g. <CR>).
+ * @param raw_input The AT input, without EOL delimiter (e.g. <CR>).
* @return Result object for this command line. This can be
* converted to a String[] response with toStrings().
*/
@@ -297,8 +294,8 @@
if (c == '+') {
// Option 2: Extended Command
- // Search for first non-name character. Shortcircuit if we dont
- // handle this command name.
+ // Search for first non-name character. Short-circuit if
+ // we don't handle this command name.
int i = findEndExtendedName(input, index + 1);
String commandName = input.substring(index, i);
if (!mExtHandlers.containsKey(commandName)) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 21a4bd6..c66b2de 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -341,7 +341,7 @@
private static final int ADDRESS_LENGTH = 17;
/**
- * Lazyily initialized singleton. Guaranteed final after first object
+ * Lazily initialized singleton. Guaranteed final after first object
* constructed.
*/
private static BluetoothAdapter sAdapter;
@@ -466,7 +466,7 @@
* user action to turn off Bluetooth.
* <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
* system services, and powers down the underlying Bluetooth hardware.
- * <p class="caution"><strong>Bluetooth should never be disbled without
+ * <p class="caution"><strong>Bluetooth should never be disabled without
* direct user consent</strong>. The {@link #disable()} method is
* provided only for applications that include a user interface for changing
* system settings, such as a "power manager" app.</p>
@@ -932,8 +932,8 @@
public Pair<byte[], byte[]> readOutOfBandData() {
if (getState() != STATE_ON) return null;
try {
- byte[] hash = new byte[16];
- byte[] randomizer = new byte[16];
+ byte[] hash;
+ byte[] randomizer;
byte[] ret = mService.readOutOfBandData();
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
index bc32060..9351393 100644
--- a/core/java/android/bluetooth/BluetoothAudioGateway.java
+++ b/core/java/android/bluetooth/BluetoothAudioGateway.java
@@ -23,7 +23,7 @@
import android.util.Log;
/**
- * Listen's for incoming RFCOMM connection for the headset / handsfree service.
+ * Listens for incoming RFCOMM connection for the headset / handsfree service.
*
* TODO: Use the new generic BluetoothSocket class instead of this legacy code
*
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index c8381c9..e604e6b 100644
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -34,8 +34,8 @@
* Bluetooth profiles or services are actually supported by a device. Accurate
* service discovery is done through SDP requests, which are automatically
* performed when creating an RFCOMM socket with {@link
- * BluetoothDevice#createRfcommSocketToServiceRecord(UUID)} and {@link
- * BluetoothAdapter#listenUsingRfcommWithServiceRecord(String,UUID)}</p>
+ * BluetoothDevice#createRfcommSocketToServiceRecord} and {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord}</p>
*
* <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for
* a remote device.
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e577ec4..ada3c24 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -32,7 +32,7 @@
/**
* Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you
- * create a connection with the repective device or query information about
+ * create a connection with the respective device or query information about
* it, such as the name, address, class, and bonding state.
*
* <p>This class is really just a thin wrapper for a Bluetooth hardware
@@ -48,7 +48,7 @@
* {@link BluetoothAdapter}) or get one from the set of bonded devices
* returned by {@link BluetoothAdapter#getBondedDevices()
* BluetoothAdapter.getBondedDevices()}. You can then open a
- * {@link BluetoothSocket} for communciation with the remote device, using
+ * {@link BluetoothSocket} for communication with the remote device, using
* {@link #createRfcommSocketToServiceRecord(UUID)}.
*
* <p class="note"><strong>Note:</strong>
@@ -226,8 +226,8 @@
* <p>A shared link keys exists locally for the remote device, so
* communication can be authenticated and encrypted.
* <p><i>Being bonded (paired) with a remote device does not necessarily
- * mean the device is currently connected. It just means that the ponding
- * procedure was compeleted at some earlier time, and the link key is still
+ * mean the device is currently connected. It just means that the pending
+ * procedure was completed at some earlier time, and the link key is still
* stored locally, ready to use on the next connection.
* </i>
*/
@@ -283,7 +283,7 @@
* not respond to pin request in time
* @hide */
public static final int UNBOND_REASON_AUTH_FAILED = 1;
- /** A bond attempt failed because the other side explicilty rejected
+ /** A bond attempt failed because the other side explicitly rejected
* bonding
* @hide */
public static final int UNBOND_REASON_AUTH_REJECTED = 2;
@@ -515,7 +515,7 @@
* Cancel an in-progress bonding request started with {@link #createBond}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
- * @return true on sucess, false on error
+ * @return true on success, false on error
* @hide
*/
public boolean cancelBondProcess() {
@@ -532,7 +532,7 @@
* authentication and encryption.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
- * @return true on sucess, false on error
+ * @return true on success, false on error
* @hide
*/
public boolean removeBond() {
@@ -617,7 +617,7 @@
* with the UUIDs supported by the remote end. If there is an error
* in getting the SDP records or if the process takes a long time,
* an Intent is sent with the UUIDs that is currently present in the
- * cache. Clients should use the {@link getUuids} to get UUIDs
+ * cache. Clients should use the {@link #getUuids} to get UUIDs
* is SDP is not to be performed.
*
* @return False if the sanity check fails, True if the process
@@ -693,7 +693,7 @@
* outgoing connection to this remote device on given channel.
* <p>The remote device will be authenticated and communication on this
* socket will be encrypted.
- * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
+ * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection.
* <p>Valid RFCOMM channels are in range 1 to 30.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
@@ -715,7 +715,7 @@
* <p>This is designed to be used with {@link
* BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer
* Bluetooth applications.
- * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
+ * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection. This will also perform an SDP lookup of the given uuid to
* determine which channel to connect to.
* <p>The remote device will be authenticated and communication on this
@@ -772,9 +772,9 @@
/**
* Check that a pin is valid and convert to byte array.
*
- * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
+ * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters.
* @param pin pin as java String
- * @return the pin code as a UTF8 byte array, or null if it is an invalid
+ * @return the pin code as a UTF-8 byte array, or null if it is an invalid
* Bluetooth pin.
* @hide
*/
@@ -784,9 +784,9 @@
}
byte[] pinBytes;
try {
- pinBytes = pin.getBytes("UTF8");
+ pinBytes = pin.getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
- Log.e(TAG, "UTF8 not supported?!?"); // this should not happen
+ Log.e(TAG, "UTF-8 not supported?!?"); // this should not happen
return null;
}
if (pinBytes.length <= 0 || pinBytes.length > 16) {
diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java
index 7415721..c794be2 100644
--- a/core/java/android/bluetooth/BluetoothDevicePicker.java
+++ b/core/java/android/bluetooth/BluetoothDevicePicker.java
@@ -36,7 +36,8 @@
/**
* Broadcast when one BT device is selected from BT device picker screen.
- * Selected BT device address is contained in extra string {@link BluetoothIntent}
+ * Selected {@link BluetoothDevice} is returned in extra data named
+ * {@link BluetoothDevice#EXTRA_DEVICE}.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_SELECTED =
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index b48f48e..4be077c 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -197,7 +197,7 @@
/**
* Disconnects the current Pbap client (PCE). Currently this call blocks,
- * it may soon be made asynchornous. Returns false if this proxy object is
+ * it may soon be made asynchronous. Returns false if this proxy object is
* not currently connected to the Pbap service.
*/
public boolean disconnect() {
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index c9c6c0a..83e59e2 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -29,14 +29,14 @@
* side, use a {@link BluetoothServerSocket} to create a listening server
* socket. When a connection is accepted by the {@link BluetoothServerSocket},
* it will return a new {@link BluetoothSocket} to manage the connection.
- * On the client side, use a single {@link BluetoothSocket} to both intiate
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
* an outgoing connection and to manage the connection.
*
* <p>The most common type of Bluetooth socket is RFCOMM, which is the type
* supported by the Android APIs. RFCOMM is a connection-oriented, streaming
* transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
*
- * <p>To create a listenting {@link BluetoothServerSocket} that's ready for
+ * <p>To create a listening {@link BluetoothServerSocket} that's ready for
* incoming connections, use
* {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord
* BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. Then call
@@ -70,7 +70,7 @@
* @param encrypt require the connection to be encrypted
* @param port remote port
* @throws IOException On error, for example Bluetooth not available, or
- * insufficient priveleges
+ * insufficient privileges
*/
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
throws IOException {
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index ad03399..719d730 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -35,7 +35,7 @@
* side, use a {@link BluetoothServerSocket} to create a listening server
* socket. When a connection is accepted by the {@link BluetoothServerSocket},
* it will return a new {@link BluetoothSocket} to manage the connection.
- * On the client side, use a single {@link BluetoothSocket} to both intiate
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
* an outgoing connection and to manage the connection.
*
* <p>The most common type of Bluetooth socket is RFCOMM, which is the type
@@ -113,7 +113,7 @@
* @param port remote port
* @param uuid SDP uuid
* @throws IOException On error, for example Bluetooth not available, or
- * insufficient priveleges
+ * insufficient privileges
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
@@ -158,7 +158,7 @@
* @param address remote device that this socket can connect to
* @param port remote port
* @throws IOException On error, for example Bluetooth not available, or
- * insufficient priveleges
+ * insufficient privileges
*/
private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
int port) throws IOException {
@@ -226,7 +226,7 @@
}
// all native calls are guaranteed to immediately return after
- // abortNative(), so this lock should immediatley acquire
+ // abortNative(), so this lock should immediately acquire
mLock.writeLock().lock();
try {
mClosed = true;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index cb6b708c..7346561 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -27,8 +27,6 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Environment;
-import android.os.StatFs;
import android.util.AndroidException;
import android.util.DisplayMetrics;
@@ -1388,11 +1386,18 @@
* {@link Intent#resolveActivity} finds an activity if a class has not
* been explicitly specified.
*
+ * <p><em>Note: if using an implicit Intent (without an explicit ComponentName
+ * specified), be sure to consider whether to set the {@link #MATCH_DEFAULT_ONLY}
+ * only flag. You need to do so to resolve the activity in the same way
+ * that {@link android.content.Context#startActivity(Intent)} and
+ * {@link android.content.Intent#resolveActivity(PackageManager)
+ * Intent.resolveActivity(PackageManager)} do.</p>
+ *
* @param intent An intent containing all of the desired specification
* (action, data, type, category, and/or component).
* @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
+ * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only
+ * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}.
*
* @return Returns a ResolveInfo containing the final activity intent that
* was determined to be the best action. Returns null if no
@@ -1411,13 +1416,13 @@
*
* @param intent The desired intent as per resolveActivity().
* @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
+ * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only
+ * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}.
*
- * @return A List<ResolveInfo> containing one entry for each matching
+ * @return A List<ResolveInfo> containing one entry for each matching
* Activity. These are ordered from best to worst match -- that
* is, the first item in the list is what is returned by
- * resolveActivity(). If there are no matching activities, an empty
+ * {@link #resolveActivity}. If there are no matching activities, an empty
* list is returned.
*
* @see #MATCH_DEFAULT_ONLY
@@ -1442,10 +1447,10 @@
* first specific results. Can be null.
* @param intent The desired intent as per resolveActivity().
* @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
+ * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only
+ * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}.
*
- * @return A List<ResolveInfo> containing one entry for each matching
+ * @return A List<ResolveInfo> containing one entry for each matching
* Activity. These are ordered first by all of the intents resolved
* in <var>specifics</var> and then any additional activities that
* can handle <var>intent</var> but did not get included by one of
@@ -1463,11 +1468,9 @@
* Retrieve all receivers that can handle a broadcast of the given intent.
*
* @param intent The desired intent as per resolveActivity().
- * @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
+ * @param flags Additional option flags.
*
- * @return A List<ResolveInfo> containing one entry for each matching
+ * @return A List<ResolveInfo> containing one entry for each matching
* Receiver. These are ordered from first to last in priority. If
* there are no matching receivers, an empty list is returned.
*
@@ -1500,7 +1503,7 @@
* @param intent The desired intent as per resolveService().
* @param flags Additional option flags.
*
- * @return A List<ResolveInfo> containing one entry for each matching
+ * @return A List<ResolveInfo> containing one entry for each matching
* ServiceInfo. These are ordered from best to worst match -- that
* is, the first item in the list is what is returned by
* resolveService(). If there are no matching services, an empty
@@ -1537,7 +1540,7 @@
* uid owning the requested content providers.
* @param flags Additional option flags. Currently should always be 0.
*
- * @return A List<ContentProviderInfo> containing one entry for each
+ * @return A List<ContentProviderInfo> containing one entry for each
* content provider either patching <var>processName</var> or, if
* <var>processName</var> is null, all known content providers.
* <em>If there are no matching providers, null is returned.</em>
@@ -1573,7 +1576,7 @@
* returned.
* @param flags Additional option flags. Currently should always be 0.
*
- * @return A List<InstrumentationInfo> containing one entry for each
+ * @return A List<InstrumentationInfo> containing one entry for each
* matching available Instrumentation. Returns an empty list if
* there is no instrumentation available for the given package.
*/
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8ef639b..eaf1e33 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1730,6 +1730,11 @@
XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("uses-package")) {
+ // Dependencies for app installers; we don't currently try to
+ // enforce this.
+ XmlUtils.skipCurrentTag(parser);
+
} else {
if (!RIGID_PARSER) {
Log.w(TAG, "Unknown element under <application>: " + tagName
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d0ba590..406b091 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -18,6 +18,7 @@
import android.content.pm.ApplicationInfo;
import android.graphics.Canvas;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.DisplayMetrics;
@@ -363,6 +364,17 @@
}
/**
+ * Translate a Point in screen coordinates into the app window's coordinates.
+ */
+ public void translatePointInScreenToAppWindow(PointF point) {
+ final float scale = applicationInvertedScale;
+ if (scale != 1.0f) {
+ point.x *= scale;
+ point.y *= scale;
+ }
+ }
+
+ /**
* Translate the location of the sub window.
* @param params
*/
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
index 7b962e5..5d6ed44 100644
--- a/core/java/android/content/res/ObbInfo.java
+++ b/core/java/android/content/res/ObbInfo.java
@@ -29,6 +29,11 @@
public static final int OBB_OVERLAY = 1 << 0;
/**
+ * The canonical filename of the OBB.
+ */
+ public String filename;
+
+ /**
* The name of the package to which the OBB file belongs.
*/
public String packageName;
@@ -66,6 +71,7 @@
}
public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(filename);
dest.writeString(packageName);
dest.writeInt(version);
dest.writeInt(flags);
@@ -83,6 +89,7 @@
};
private ObbInfo(Parcel source) {
+ filename = source.readString();
packageName = source.readString();
version = source.readInt();
flags = source.readInt();
diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java
index a3f141e..1b38eea 100644
--- a/core/java/android/content/res/ObbScanner.java
+++ b/core/java/android/content/res/ObbScanner.java
@@ -42,12 +42,17 @@
final File obbFile = new File(filePath);
if (!obbFile.exists()) {
- throw new IllegalArgumentException("OBB file does nto exist: " + filePath);
+ throw new IllegalArgumentException("OBB file does not exist: " + filePath);
}
+ /*
+ * XXX This will fail to find the real canonical path if bind mounts are
+ * used, but we don't use any bind mounts right now.
+ */
final String canonicalFilePath = obbFile.getCanonicalPath();
ObbInfo obbInfo = new ObbInfo();
+ obbInfo.filename = canonicalFilePath;
getObbInfo_native(canonicalFilePath, obbInfo);
return obbInfo;
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 6ed1a90e..a7ad757 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.os.StrictMode;
import android.util.Log;
/**
@@ -106,11 +107,13 @@
// but if the database itself is not closed and is GC'ed, then
// all sub-objects attached to the database could end up getting GC'ed too.
// in that case, don't print any warning.
- if (mInUse) {
+ if (mInUse && StrictMode.vmSqliteObjectLeaksEnabled()) {
int len = mSqlStmt.length();
- Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
- "that you explicitly call close() on your cursor: " +
- mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
+ StrictMode.onSqliteObjectLeaked(
+ "Releasing statement in a finalizer. Please ensure " +
+ "that you explicitly call close() on your cursor: " +
+ mSqlStmt.substring(0, (len > 100) ? 100 : len),
+ mStackTrace);
}
releaseSqlStatement();
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index fa7763d..89e8ab7 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -25,6 +25,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.StrictMode;
import android.util.Config;
import android.util.Log;
@@ -505,11 +506,14 @@
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
- int len = mQuery.mSql.length();
- Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
+ if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+ int len = mQuery.mSql.length();
+ StrictMode.onSqliteObjectLeaked(
+ "Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable +
", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
mStackTrace);
+ }
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0d8228c..7b930d5 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -181,6 +181,7 @@
* value should be 90.
*
* @see #setDisplayOrientation(int)
+ * @see #setRotation(int)
*/
public int orientation;
};
@@ -1716,23 +1717,46 @@
}
/**
- * Sets the orientation of the device in degrees. For example, suppose
- * the natural position of the device is landscape. If the user takes a
- * picture in landscape mode in 2048x1536 resolution, the rotation
- * should be set to 0. If the user rotates the phone 90 degrees
- * clockwise, the rotation should be set to 90. Applications can use
- * {@link android.view.OrientationEventListener} to set this parameter.
+ * Sets the rotation angle in degrees relative to the orientation of
+ * the camera. This affects the pictures returned from JPEG {@link
+ * PictureCallback}. The camera driver may set orientation in the
+ * EXIF header without rotating the picture. Or the driver may rotate
+ * the picture and the EXIF thumbnail. If the Jpeg picture is rotated,
+ * the orientation in the EXIF header will be missing or 1 (row #0 is
+ * top and column #0 is left side).
*
- * The camera driver may set orientation in the EXIF header without
- * rotating the picture. Or the driver may rotate the picture and
- * the EXIF thumbnail. If the Jpeg picture is rotated, the orientation
- * in the EXIF header will be missing or 1 (row #0 is top and column #0
- * is left side).
+ * If appplications want to rotate the picture to match the
+ * orientation of what users see, apps should use {@link
+ * android.view.OrientationEventListener} and {@link CameraInfo}.
+ * The value from OrientationEventListener is relative to the natural
+ * orientation of the device. CameraInfo.mOrientation is the angle
+ * between camera orientation and natural device orientation. The sum
+ * of the two is the angle for rotation.
*
- * @param rotation The orientation of the device in degrees. Rotation
- * can only be 0, 90, 180 or 270.
+ * For example, suppose the natural orientation of the device is
+ * portrait. The device is rotated 270 degrees clockwise, so the device
+ * orientation is 270. Suppose the camera sensor is mounted in landscape
+ * and the top side of the camera sensor is aligned with the right edge
+ * of the display in natural orientation. So the camera orientation is
+ * 90. The rotation should be set to 0 (270 + 90).
+ *
+ * The reference code is as follows.
+ *
+ * public void public void onOrientationChanged(int orientation) {
+ * if (orientation == ORIENTATION_UNKNOWN) return;
+ * android.hardware.Camera.CameraInfo info =
+ * new android.hardware.Camera.CameraInfo();
+ * android.hardware.Camera.getCameraInfo(cameraId, info);
+ * orientation = (orientation + 45) / 90 * 90;
+ * mParameters.setRotation((orientation + info.mOrientation) % 360);
+ * }
+ *
+ * @param rotation The rotation angle in degrees relative to the
+ * orientation of the camera. Rotation can only be 0,
+ * 90, 180 or 270.
* @throws IllegalArgumentException if rotation value is invalid.
* @see android.view.OrientationEventListener
+ * @see #getCameraInfo(int, CameraInfo)
*/
public void setRotation(int rotation) {
if (rotation == 0 || rotation == 90 || rotation == 180
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 3df8ec0..ad7289d 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -45,7 +45,7 @@
public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private Phone.DataState mMobileDataState;
private ITelephony mPhoneService;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index c7cbed6..e8ae7e6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@
import java.io.File;
+import android.content.res.Resources;
import android.os.storage.IMountService;
/**
@@ -116,6 +117,19 @@
* happened. You can determine its current state with
* {@link #getExternalStorageState()}.
*
+ * <p><em>Note: don't be confused by the word "external" here. This
+ * directory can better be thought as media/shared storage. It is a
+ * filesystem that can hold a relatively large amount of data and that
+ * is shared across all applications (does not enforce permissions).
+ * Traditionally this is an SD card, but it may also be implemented as
+ * built-in storage in a device that is distinct from the protected
+ * internal storage and can be mounted as a filesystem on a computer.</em></p>
+ *
+ * <p>In devices with multiple "external" storage directories (such as
+ * both secure app storage and mountable shared storage), this directory
+ * represents the "primary" external storage that the user will interact
+ * with.</p>
+ *
* <p>Applications should not directly use this top-level directory, in
* order to avoid polluting the user's root namespace. Any files that are
* private to the application should be placed in a directory returned
@@ -130,6 +144,9 @@
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* monitor_storage}
+ *
+ * @see #getExternalStorageState()
+ * @see #isExternalStorageRemovable()
*/
public static File getExternalStorageDirectory() {
return EXTERNAL_STORAGE_DIRECTORY;
@@ -359,11 +376,9 @@
public static final String MEDIA_UNMOUNTABLE = "unmountable";
/**
- * Gets the current state of the external storage device.
- * Note: This call should be deprecated as it doesn't support
- * multiple volumes.
+ * Gets the current state of the primary "external" storage device.
*
- * <p>See {@link #getExternalStorageDirectory()} for an example of its use.
+ * <p>See {@link #getExternalStorageDirectory()} for more information.
*/
public static String getExternalStorageState() {
try {
@@ -377,6 +392,19 @@
}
}
+ /**
+ * Returns whether the primary "external" storage device is removable.
+ * If true is returned, this device is for example an SD card that the
+ * user can remove. If false is returned, the storage is built into
+ * the device and can not be physically removed.
+ *
+ * <p>See {@link #getExternalStorageDirectory()} for more information.
+ */
+ public static boolean isExternalStorageRemovable() {
+ return Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_externalStorageRemovable);
+ }
+
static File getDirectory(String variableName, String defaultPath) {
String path = System.getenv(variableName);
return path == null ? new File(defaultPath) : new File(path);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index e56e257..6d19f41 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -68,6 +68,7 @@
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
+ private static String LAST_LOG_FILENAME = "last_log";
// Length limits for reading files.
private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
@@ -399,9 +400,10 @@
Log.e(TAG, "Error reading recovery log", e);
}
- // Delete everything in RECOVERY_DIR
+ // Delete everything in RECOVERY_DIR except LAST_LOG_FILENAME
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
+ if (names[i].equals(LAST_LOG_FILENAME)) continue;
File f = new File(RECOVERY_DIR, names[i]);
if (!f.delete()) {
Log.e(TAG, "Can't delete: " + f);
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 3ddaad9..9494a06 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -30,8 +30,9 @@
import java.util.HashMap;
/**
- * <p>StrictMode is a developer tool which lets you impose stricter
- * rules under which your application runs.
+ * <p>StrictMode is a developer tool which detects things you might be
+ * doing by accident and brings them to your attention so you can fix
+ * them.
*
* <p>StrictMode is most commonly used to catch accidental disk or
* network access on the application's main thread, where UI
@@ -55,24 +56,33 @@
* <pre>
* public void onCreate() {
* if (DEVELOPER_MODE) {
- * StrictMode.setThreadPolicy(StrictMode.DISALLOW_DISK_WRITE |
- * StrictMode.DISALLOW_DISK_READ |
- * StrictMode.DISALLOW_NETWORK |
- * StrictMode.PENALTY_LOG);
+ * StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
+ * .detectDiskReads()
+ * .detectDiskWrites()
+ * .detectNetwork() // or .detectAll() for all detectable problems
+ * .penaltyLog()
+ * .build());
+ * StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
+ * .detectLeakedSqlLiteCursors()
+ * .penaltyLog()
+ * .penaltyDeath()
+ * .build());
* }
* super.onCreate();
* }
* </pre>
*
- * <p>Then you can watch the output of <code>adb logcat</code> while you
- * use your application.
+ * <p>You can decide what should happen when a violation is detected.
+ * For example, using {@link ThreadPolicy.Builder#penaltyLog} you can
+ * watch the output of <code>adb logcat</code> while you use your
+ * application to see the violations as they happen.
*
* <p>If you find violations that you feel are problematic, there are
* a variety of tools to help solve them: threads, {@link android.os.Handler},
* {@link android.os.AsyncTask}, {@link android.app.IntentService}, etc.
* But don't feel compelled to fix everything that StrictMode finds. In particular,
- * a lot of disk accesses are often necessary during the normal activity lifecycle. Use
- * StrictMode to find things you did on accident. Network requests on the UI thread
+ * many cases of disk access are often necessary during the normal activity lifecycle. Use
+ * StrictMode to find things you did by accident. Network requests on the UI thread
* are almost always a problem, though.
*
* <p class="note">StrictMode is not a security mechanism and is not
@@ -94,55 +104,50 @@
// Only show an annoying dialog at most every 30 seconds
private static final long MIN_DIALOG_INTERVAL_MS = 30000;
- private StrictMode() {}
+ // Thread-policy:
/**
- * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
- * thread to write to disk.
+ * @hide
*/
- public static final int DISALLOW_DISK_WRITE = 0x01;
+ public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
/**
- * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
- * thread to read from disk.
+ * @hide
*/
- public static final int DISALLOW_DISK_READ = 0x02;
+ public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
/**
- * Flag for {@link #setThreadPolicy} to signal that you don't intend for this
- * thread to access the network.
+ * @hide
*/
- public static final int DISALLOW_NETWORK = 0x04;
+ public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
- /** @hide */
- public static final int DISALLOW_MASK =
- DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
+ // Process-policy:
/**
- * Penalty flag for {@link #setThreadPolicy} to log violations to
- * the system log, visible with <code>adb logcat</code>.
+ * Note, a "VM_" bit, not thread.
+ * @hide
+ */
+ public static final int DETECT_VM_CURSOR_LEAKS = 0x200; // for ProcessPolicy
+
+ /**
+ * @hide
*/
public static final int PENALTY_LOG = 0x10; // normal android.util.Log
+ // Used for both process and thread policy:
+
/**
- * Penalty flag for {@link #setThreadPolicy} to show an annoying
- * dialog to the developer, rate-limited to be only a little
- * annoying.
+ * @hide
*/
public static final int PENALTY_DIALOG = 0x20;
/**
- * Penalty flag for {@link #setThreadPolicy} to crash hard if
- * policy is violated.
+ * @hide
*/
public static final int PENALTY_DEATH = 0x40;
/**
- * Penalty flag for {@link #setThreadPolicy} to log a stacktrace
- * and timing data to the
- * {@link android.os.DropBoxManager DropBox} on policy violation.
- * Intended mostly for platform integrators doing beta user field
- * data collection.
+ * @hide
*/
public static final int PENALTY_DROPBOX = 0x80;
@@ -159,10 +164,321 @@
*/
public static final int PENALTY_GATHER = 0x100;
- /** @hide */
- public static final int PENALTY_MASK =
- PENALTY_LOG | PENALTY_DIALOG |
- PENALTY_DROPBOX | PENALTY_DEATH;
+ /**
+ * The current VmPolicy in effect.
+ */
+ private static volatile int sVmPolicyMask = 0;
+
+ private StrictMode() {}
+
+ /**
+ * {@link StrictMode} policy applied to a certain thread.
+ *
+ * <p>The policy is enabled by {@link #setThreadPolicy}. The current policy
+ * can be retrieved with {@link #getThreadPolicy}.
+ *
+ * <p>Note that multiple penalties may be provided and they're run
+ * in order from least to most severe (logging before process
+ * death, for example). There's currently no mechanism to choose
+ * different penalties for different detected actions.
+ */
+ public static final class ThreadPolicy {
+ /**
+ * The default, lax policy which doesn't catch anything.
+ */
+ public static final ThreadPolicy LAX = new ThreadPolicy(0);
+
+ final int mask;
+
+ private ThreadPolicy(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public String toString() {
+ return "[StrictMode.ThreadPolicy; mask=" + mask + "]";
+ }
+
+ /**
+ * Creates ThreadPolicy instances. Methods whose names start
+ * with {@code detect} specify what problems we should look
+ * for. Methods whose names start with {@code penalty} specify what
+ * we should do when we detect a problem.
+ *
+ * <p>You can call as many {@code detect} and {@code penalty}
+ * methods as you like. Currently order is insignificant: all
+ * penalties apply to all detected problems.
+ *
+ * <p>For example, detect everything and log anything that's found:
+ * <pre>
+ * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
+ * .detectAll()
+ * .penaltyLog()
+ * .build();
+ * StrictMode.setVmPolicy(policy);
+ * </pre>
+ */
+ public static final class Builder {
+ private int mMask = 0;
+
+ /**
+ * Create a Builder that detects nothing and has no
+ * violations. (but note that {@link #build} will default
+ * to enabling {@link #penaltyLog} if no other penalties
+ * are specified)
+ */
+ public Builder() {
+ mMask = 0;
+ }
+
+ /**
+ * Initialize a Builder from an existing ThreadPolicy.
+ */
+ public Builder(ThreadPolicy policy) {
+ mMask = policy.mask;
+ }
+
+ /**
+ * Detect everything that's potentially suspect.
+ *
+ * <p>As of the Gingerbread release this includes network and
+ * disk operations but will likely expand in future releases.
+ */
+ public Builder detectAll() {
+ return enable(DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK);
+ }
+
+ /**
+ * Disable the detection of everything.
+ */
+ public Builder permitAll() {
+ return disable(DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK);
+ }
+
+ /**
+ * Enable detection of network operations.
+ */
+ public Builder detectNetwork() {
+ return enable(DETECT_NETWORK);
+ }
+
+ /**
+ * Disable detection of network operations.
+ */
+ public Builder permitNetwork() {
+ return disable(DETECT_NETWORK);
+ }
+
+ /**
+ * Enable detection of disk reads.
+ */
+ public Builder detectDiskReads() {
+ return enable(DETECT_DISK_READ);
+ }
+
+ /**
+ * Disable detection of disk reads.
+ */
+ public Builder permitDiskReads() {
+ return disable(DETECT_DISK_READ);
+ }
+
+ /**
+ * Enable detection of disk writes.
+ */
+ public Builder detectDiskWrites() {
+ return enable(DETECT_DISK_WRITE);
+ }
+
+ /**
+ * Disable detection of disk writes.
+ */
+ public Builder permitDiskWrites() {
+ return disable(DETECT_DISK_WRITE);
+ }
+
+ /**
+ * Show an annoying dialog to the developer on detected
+ * violations, rate-limited to be only a little annoying.
+ */
+ public Builder penaltyDialog() {
+ return enable(PENALTY_DIALOG);
+ }
+
+ /**
+ * Crash the whole process on violation. This penalty runs at
+ * the end of all enabled penalties so you'll still get
+ * see logging or other violations before the process dies.
+ */
+ public Builder penaltyDeath() {
+ return enable(PENALTY_DEATH);
+ }
+
+ /**
+ * Log detected violations to the system log.
+ */
+ public Builder penaltyLog() {
+ return enable(PENALTY_LOG);
+ }
+
+ /**
+ * Enable detected violations log a stacktrace and timing data
+ * to the {@link android.os.DropBoxManager DropBox} on policy
+ * violation. Intended mostly for platform integrators doing
+ * beta user field data collection.
+ */
+ public Builder penaltyDropBox() {
+ return enable(PENALTY_DROPBOX);
+ }
+
+ private Builder enable(int bit) {
+ mMask |= bit;
+ return this;
+ }
+
+ private Builder disable(int bit) {
+ mMask &= ~bit;
+ return this;
+ }
+
+ /**
+ * Construct the ThreadPolicy instance.
+ *
+ * <p>Note: if no penalties are enabled before calling
+ * <code>build</code>, {@link #penaltyLog} is implicitly
+ * set.
+ */
+ public ThreadPolicy build() {
+ // If there are detection bits set but no violation bits
+ // set, enable simple logging.
+ if (mMask != 0 &&
+ (mMask & (PENALTY_DEATH | PENALTY_LOG |
+ PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+ penaltyLog();
+ }
+ return new ThreadPolicy(mMask);
+ }
+ }
+ }
+
+ /**
+ * {@link StrictMode} policy applied to all threads in the virtual machine's process.
+ *
+ * <p>The policy is enabled by {@link #setVmPolicy}.
+ */
+ public static final class VmPolicy {
+ /**
+ * The default, lax policy which doesn't catch anything.
+ */
+ public static final VmPolicy LAX = new VmPolicy(0);
+
+ final int mask;
+
+ private VmPolicy(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public String toString() {
+ return "[StrictMode.VmPolicy; mask=" + mask + "]";
+ }
+
+ /**
+ * Creates {@link VmPolicy} instances. Methods whose names start
+ * with {@code detect} specify what problems we should look
+ * for. Methods whose names start with {@code penalty} specify what
+ * we should do when we detect a problem.
+ *
+ * <p>You can call as many {@code detect} and {@code penalty}
+ * methods as you like. Currently order is insignificant: all
+ * penalties apply to all detected problems.
+ *
+ * <p>For example, detect everything and log anything that's found:
+ * <pre>
+ * StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
+ * .detectAll()
+ * .penaltyLog()
+ * .build();
+ * StrictMode.setVmPolicy(policy);
+ * </pre>
+ */
+ public static final class Builder {
+ private int mMask;
+
+ /**
+ * Detect everything that's potentially suspect.
+ *
+ * <p>As of the Gingerbread release this only includes
+ * SQLite cursor leaks but will likely expand in future
+ * releases.
+ */
+ public Builder detectAll() {
+ return enable(DETECT_VM_CURSOR_LEAKS);
+ }
+
+ /**
+ * Detect when an
+ * {@link android.database.sqlite.SQLiteCursor} or other
+ * SQLite object is finalized without having been closed.
+ *
+ * <p>You always want to explicitly close your SQLite
+ * cursors to avoid unnecessary database contention and
+ * temporary memory leaks.
+ */
+ public Builder detectLeakedSqlLiteObjects() {
+ return enable(DETECT_VM_CURSOR_LEAKS);
+ }
+
+ /**
+ * Crashes the whole process on violation. This penalty runs at
+ * the end of all enabled penalties so yo you'll still get
+ * your logging or other violations before the process dies.
+ */
+ public Builder penaltyDeath() {
+ return enable(PENALTY_DEATH);
+ }
+
+ /**
+ * Log detected violations to the system log.
+ */
+ public Builder penaltyLog() {
+ return enable(PENALTY_LOG);
+ }
+
+ /**
+ * Enable detected violations log a stacktrace and timing data
+ * to the {@link android.os.DropBoxManager DropBox} on policy
+ * violation. Intended mostly for platform integrators doing
+ * beta user field data collection.
+ */
+ public Builder penaltyDropBox() {
+ return enable(PENALTY_DROPBOX);
+ }
+
+ private Builder enable(int bit) {
+ mMask |= bit;
+ return this;
+ }
+
+ /**
+ * Construct the VmPolicy instance.
+ *
+ * <p>Note: if no penalties are enabled before calling
+ * <code>build</code>, {@link #penaltyLog} is implicitly
+ * set.
+ */
+ public VmPolicy build() {
+ // If there are detection bits set but no violation bits
+ // set, enable simple logging.
+ if (mMask != 0 &&
+ (mMask & (PENALTY_DEATH | PENALTY_LOG |
+ PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
+ penaltyLog();
+ }
+ return new VmPolicy(mMask);
+ }
+ }
+ }
/**
* Log of strict mode violation stack traces that have occurred
@@ -181,19 +497,21 @@
};
/**
- * Sets the policy for what actions the current thread isn't
- * expected to do, as well as the penalty if it does.
+ * Sets the policy for what actions on the current thread should
+ * be detected, as well as the penalty if such actions occur.
*
- * <p>Internally this sets a thread-local integer which is
+ * <p>Internally this sets a thread-local variable which is
* propagated across cross-process IPC calls, meaning you can
* catch violations when a system service or another process
* accesses the disk or network on your behalf.
*
- * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values,
- * e.g. {@link #DISALLOW_DISK_READ}, {@link #DISALLOW_DISK_WRITE},
- * {@link #DISALLOW_NETWORK}, {@link #PENALTY_LOG}.
+ * @param policy the policy to put into place
*/
- public static void setThreadPolicy(final int policyMask) {
+ public static void setThreadPolicy(final ThreadPolicy policy) {
+ setThreadPolicyMask(policy.mask);
+ }
+
+ private static void setThreadPolicyMask(final int policyMask) {
// In addition to the Java-level thread-local in Dalvik's
// BlockGuard, we also need to keep a native thread-local in
// Binder in order to propagate the value across Binder calls,
@@ -222,65 +540,76 @@
private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException {
public StrictModeNetworkViolation(int policyMask) {
- super(policyMask, DISALLOW_NETWORK);
+ super(policyMask, DETECT_NETWORK);
}
}
private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException {
public StrictModeDiskReadViolation(int policyMask) {
- super(policyMask, DISALLOW_DISK_READ);
+ super(policyMask, DETECT_DISK_READ);
}
}
private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException {
public StrictModeDiskWriteViolation(int policyMask) {
- super(policyMask, DISALLOW_DISK_WRITE);
+ super(policyMask, DETECT_DISK_WRITE);
}
}
/**
* Returns the bitmask of the current thread's policy.
*
- * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
+ * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
+ *
+ * @hide
*/
- public static int getThreadPolicy() {
+ public static int getThreadPolicyMask() {
return BlockGuard.getThreadPolicy().getPolicyMask();
}
/**
- * A convenience wrapper around {@link #getThreadPolicy} and
- * {@link #setThreadPolicy}. Updates the current thread's policy
- * mask to allow both reading & writing to disk, returning the
- * old policy so you can restore it at the end of a block.
- *
- * @return the old policy mask, to be passed to setThreadPolicy to
- * restore the policy.
+ * Returns the current thread's policy.
*/
- public static int allowThreadDiskWrites() {
- int oldPolicy = getThreadPolicy();
- int newPolicy = oldPolicy & ~(DISALLOW_DISK_WRITE | DISALLOW_DISK_READ);
- if (newPolicy != oldPolicy) {
- setThreadPolicy(newPolicy);
- }
- return oldPolicy;
+ public static ThreadPolicy getThreadPolicy() {
+ return new ThreadPolicy(getThreadPolicyMask());
}
/**
- * A convenience wrapper around {@link #getThreadPolicy} and
- * {@link #setThreadPolicy}. Updates the current thread's policy
- * mask to allow reading from disk, returning the old
- * policy so you can restore it at the end of a block.
+ * A convenience wrapper that takes the current
+ * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
+ * to permit both disk reads & writes, and sets the new policy
+ * with {@link #setThreadPolicy}, returning the old policy so you
+ * can restore it at the end of a block.
*
- * @return the old policy mask, to be passed to setThreadPolicy to
+ * @return the old policy, to be passed to {@link #setThreadPolicy} to
+ * restore the policy at the end of a block
+ */
+ public static ThreadPolicy allowThreadDiskWrites() {
+ int oldPolicyMask = getThreadPolicyMask();
+ int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_WRITE | DETECT_DISK_READ);
+ if (newPolicyMask != oldPolicyMask) {
+ setThreadPolicyMask(newPolicyMask);
+ }
+ return new ThreadPolicy(oldPolicyMask);
+ }
+
+ /**
+ * A convenience wrapper that takes the current
+ * {@link ThreadPolicy} from {@link #getThreadPolicy}, modifies it
+ * to permit disk reads, and sets the new policy
+ * with {@link #setThreadPolicy}, returning the old policy so you
+ * can restore it at the end of a block.
+ *
+ * @return the old policy, to be passed to setThreadPolicy to
* restore the policy.
*/
- public static int allowThreadDiskReads() {
- int oldPolicy = getThreadPolicy();
- int newPolicy = oldPolicy & ~(DISALLOW_DISK_READ);
- if (newPolicy != oldPolicy) {
- setThreadPolicy(newPolicy);
+ public static ThreadPolicy allowThreadDiskReads() {
+ int oldPolicyMask = getThreadPolicyMask();
+ int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_READ);
+ if (newPolicyMask != oldPolicyMask) {
+ setThreadPolicyMask(newPolicyMask);
}
- return oldPolicy;
+ return new ThreadPolicy(oldPolicyMask);
}
/**
@@ -294,11 +623,14 @@
if ("user".equals(Build.TYPE)) {
return false;
}
- StrictMode.setThreadPolicy(
- StrictMode.DISALLOW_DISK_WRITE |
- StrictMode.DISALLOW_DISK_READ |
- StrictMode.DISALLOW_NETWORK |
+ StrictMode.setThreadPolicyMask(
+ StrictMode.DETECT_DISK_WRITE |
+ StrictMode.DETECT_DISK_READ |
+ StrictMode.DETECT_NETWORK |
StrictMode.PENALTY_DROPBOX);
+ sVmPolicyMask = StrictMode.DETECT_VM_CURSOR_LEAKS |
+ StrictMode.PENALTY_DROPBOX |
+ StrictMode.PENALTY_LOG;
return true;
}
@@ -372,7 +704,7 @@
// Part of BlockGuard.Policy interface:
public void onWriteToDisk() {
- if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
+ if ((mPolicyMask & DETECT_DISK_WRITE) == 0) {
return;
}
BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
@@ -382,7 +714,7 @@
// Part of BlockGuard.Policy interface:
public void onReadFromDisk() {
- if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
+ if ((mPolicyMask & DETECT_DISK_READ) == 0) {
return;
}
BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
@@ -392,7 +724,7 @@
// Part of BlockGuard.Policy interface:
public void onNetwork() {
- if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
+ if ((mPolicyMask & DETECT_NETWORK) == 0) {
return;
}
BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
@@ -547,13 +879,13 @@
if (violationMaskSubset != 0) {
int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
violationMaskSubset |= violationBit;
- final int savedPolicy = getThreadPolicy();
+ final int savedPolicyMask = getThreadPolicyMask();
try {
// First, remove any policy before we call into the Activity Manager,
// otherwise we'll infinite recurse as we try to log policy violations
// to disk, thus violating policy, thus requiring logging, etc...
// We restore the current policy below, in the finally block.
- setThreadPolicy(0);
+ setThreadPolicyMask(0);
ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
RuntimeInit.getApplicationObject(),
@@ -563,7 +895,7 @@
Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
} finally {
// Restore the policy.
- setThreadPolicy(savedPolicy);
+ setThreadPolicyMask(savedPolicyMask);
}
}
@@ -592,6 +924,74 @@
}
/**
+ * Sets the policy for what actions in the VM process (on any
+ * thread) should be detected, as well as the penalty if such
+ * actions occur.
+ *
+ * @param policy the policy to put into place
+ */
+ public static void setVmPolicy(final VmPolicy policy) {
+ sVmPolicyMask = policy.mask;
+ }
+
+ /**
+ * Gets the current VM policy.
+ */
+ public static VmPolicy getVmPolicy() {
+ return new VmPolicy(sVmPolicyMask);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean vmSqliteObjectLeaksEnabled() {
+ return (sVmPolicyMask & DETECT_VM_CURSOR_LEAKS) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public static void onSqliteObjectLeaked(String message, Throwable originStack) {
+ if ((sVmPolicyMask & PENALTY_LOG) != 0) {
+ Log.e(TAG, message, originStack);
+ }
+
+ if ((sVmPolicyMask & PENALTY_DROPBOX) != 0) {
+ final ViolationInfo info = new ViolationInfo(originStack, sVmPolicyMask);
+
+ // The violationMask, passed to ActivityManager, is a
+ // subset of the original StrictMode policy bitmask, with
+ // only the bit violated and penalty bits to be executed
+ // by the ActivityManagerService remaining set.
+ int violationMaskSubset = PENALTY_DROPBOX | DETECT_VM_CURSOR_LEAKS;
+ final int savedPolicyMask = getThreadPolicyMask();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicyMask(0);
+
+ ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(),
+ violationMaskSubset,
+ info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
+ } finally {
+ // Restore the policy.
+ setThreadPolicyMask(savedPolicyMask);
+ }
+ }
+
+ if ((sVmPolicyMask & PENALTY_DEATH) != 0) {
+ System.err.println("StrictMode VmPolicy violation with POLICY_DEATH; shutting down.");
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ }
+ }
+
+ /**
* Called from Parcel.writeNoException()
*/
/* package */ static void writeGatheredViolationsToParcel(Parcel p) {
@@ -621,7 +1021,7 @@
new LogStackTrace().printStackTrace(new PrintWriter(sw));
String ourStack = sw.toString();
- int policyMask = getThreadPolicy();
+ int policyMask = getThreadPolicyMask();
boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
int numViolations = p.readInt();
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
index 447cd1f..da27db5 100644
--- a/core/java/android/os/SystemService.java
+++ b/core/java/android/os/SystemService.java
@@ -28,4 +28,9 @@
public static void stop(String name) {
SystemProperties.set("ctl.stop", name);
}
+
+ /** Request that the init daemon restart a named service. */
+ public static void restart(String name) {
+ SystemProperties.set("ctl.restart", name);
+ }
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4268618..8554ece 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -424,7 +424,6 @@
* @param key secret used to encrypt the OBB; may be <code>null</code> if no
* encryption was used on the OBB.
* @return whether the mount call was successfully queued or not
- * @throws IllegalArgumentException when the OBB is already mounted
*/
public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
try {
@@ -458,10 +457,8 @@
* @param force whether to kill any programs using this in order to unmount
* it
* @return whether the unmount call was successfully queued or not
- * @throws IllegalArgumentException when OBB is not already mounted
*/
- public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener)
- throws IllegalArgumentException {
+ public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
try {
mObbActionListener.addListener(listener);
mMountService.unmountObb(filename, force, mObbActionListener);
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 1e358c9..8fd0e0a 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -378,16 +378,6 @@
}
/**
- * Returns whether the download is suspended. (i.e. whether the download
- * won't complete without some action from outside the download
- * manager).
- * @hide
- */
- public static boolean isStatusSuspended(int status) {
- return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
- }
-
- /**
* Returns whether the status is a success (i.e. 2xx).
* @hide
*/
@@ -435,24 +425,12 @@
public static final int STATUS_PENDING = 190;
/**
- * This download hasn't stated yet and is paused
- * @hide
- */
- public static final int STATUS_PENDING_PAUSED = 191;
-
- /**
* This download has started
* @hide
*/
public static final int STATUS_RUNNING = 192;
/**
- * This download has started and is paused
- * @hide
- */
- public static final int STATUS_RUNNING_PAUSED = 193;
-
- /**
* This download has successfully completed.
* Warning: there might be other status values that indicate success
* in the future.
@@ -980,15 +958,6 @@
}
/**
- * Returns whether the download is suspended. (i.e. whether the download
- * won't complete without some action from outside the download
- * manager).
- */
- public static boolean isStatusSuspended(int status) {
- return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
- }
-
- /**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
@@ -1030,19 +999,30 @@
public static final int STATUS_PENDING = 190;
/**
- * This download hasn't stated yet and is paused
- */
- public static final int STATUS_PENDING_PAUSED = 191;
-
- /**
* This download has started
*/
public static final int STATUS_RUNNING = 192;
/**
- * This download has started and is paused
+ * This download has been paused by the owning app.
*/
- public static final int STATUS_RUNNING_PAUSED = 193;
+ public static final int STATUS_PAUSED_BY_APP = 193;
+
+ /**
+ * This download encountered some network error and is waiting before retrying the request.
+ */
+ public static final int STATUS_WAITING_TO_RETRY = 194;
+
+ /**
+ * This download is waiting for network connectivity to proceed.
+ */
+ public static final int STATUS_WAITING_FOR_NETWORK = 195;
+
+ /**
+ * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
+ * connection to proceed.
+ */
+ public static final int STATUS_QUEUED_FOR_WIFI = 196;
/**
* This download has successfully completed.
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 05cbeff..4edc01f 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -39,7 +39,7 @@
/**
* TODO: Move this to
* java/services/com/android/server/BluetoothEventLoop.java
- * and make the contructor package private again.
+ * and make the constructor package private again.
*
* @hide
*/
@@ -252,10 +252,10 @@
}
String name = propValues[0];
if (name.equals("Name")) {
+ mBluetoothService.setProperty(name, propValues[1]);
Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- mBluetoothService.setProperty(name, propValues[1]);
} else if (name.equals("Pairable") || name.equals("Discoverable")) {
String pairable = name.equals("Pairable") ? propValues[1] :
mBluetoothService.getPropertyInternal("Pairable");
@@ -266,6 +266,7 @@
if (pairable == null || discoverable == null)
return;
+ mBluetoothService.setProperty(name, propValues[1]);
int mode = BluetoothService.bluezStringToScanMode(
pairable.equals("true"),
discoverable.equals("true"));
@@ -275,9 +276,9 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- mBluetoothService.setProperty(name, propValues[1]);
} else if (name.equals("Discovering")) {
Intent intent;
+ mBluetoothService.setProperty(name, propValues[1]);
if (propValues[1].equals("true")) {
mBluetoothService.setIsDiscovering(true);
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
@@ -288,7 +289,6 @@
intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
}
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- mBluetoothService.setProperty(name, propValues[1]);
} else if (name.equals("Devices")) {
String value = null;
int len = Integer.valueOf(propValues[1]);
@@ -322,19 +322,20 @@
}
BluetoothDevice device = mAdapter.getRemoteDevice(address);
if (name.equals("Name")) {
+ mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
} else if (name.equals("Class")) {
+ mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothDevice.EXTRA_CLASS,
new BluetoothClass(Integer.valueOf(propValues[1])));
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
} else if (name.equals("Connected")) {
+ mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
Intent intent = null;
if (propValues[1].equals("true")) {
intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
@@ -348,7 +349,6 @@
}
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
} else if (name.equals("UUIDs")) {
String uuid = null;
int len = Integer.valueOf(propValues[1]);
@@ -754,7 +754,7 @@
private void onRestartRequired() {
if (mBluetoothService.isEnabled()) {
- Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
+ Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
"restarting Bluetooth ***");
mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index bd105a7..f8caa2c 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -500,7 +500,7 @@
// forked multiple times in a row, probably because there is
// some race in sdptool or bluez when operated in parallel.
// As a workaround, delay 500ms between each fork of sdptool.
- // TODO: Don't fork sdptool in order to regsiter service
+ // TODO: Don't fork sdptool in order to register service
// records, use a DBUS call instead.
switch (msg.arg1) {
case 1:
@@ -713,7 +713,7 @@
/** local cache of bonding state.
/* we keep our own state to track the intermediate state BONDING, which
/* bluez does not track.
- * All addreses must be passed in upper case.
+ * All addresses must be passed in upper case.
*/
public class BondState {
private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
@@ -976,7 +976,7 @@
}
}
- // This function adds a bluetooth address to the auto pairing blacklis
+ // This function adds a bluetooth address to the auto pairing blacklist
// file. These addresses are added to DynamicAddressBlacklistSection
private void updateAutoPairingData(String address) {
BufferedWriter out = null;
@@ -1106,7 +1106,7 @@
* a device discoverable; you need to call setMode() to make the device
* explicitly discoverable.
*
- * @param timeout_s The discoverable timeout in seconds.
+ * @param timeout The discoverable timeout in seconds.
*/
public synchronized boolean setDiscoverableTimeout(int timeout) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
@@ -2565,12 +2565,12 @@
/*package*/ String getAddressFromObjectPath(String objectPath) {
String adapterObjectPath = getPropertyInternal("ObjectPath");
if (adapterObjectPath == null || objectPath == null) {
- Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
+ Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
" or deviceObjectPath:" + objectPath + " is null");
return null;
}
if (!objectPath.startsWith(adapterObjectPath)) {
- Log.e(TAG, "getAddressFromObjectPath: AdpaterObjectPath:" + adapterObjectPath +
+ Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
" is not a prefix of deviceObjectPath:" + objectPath +
"bluetoothd crashed ?");
return null;
diff --git a/core/java/android/util/CalendarUtils.java b/core/java/android/util/CalendarUtils.java
index 3d340d9..1b2a894 100644
--- a/core/java/android/util/CalendarUtils.java
+++ b/core/java/android/util/CalendarUtils.java
@@ -146,6 +146,9 @@
* This formats a date/time range using Calendar's time zone and the
* local conventions for the region of the device.
*
+ * If the {@link DateUtils#FORMAT_UTC} flag is used it will pass in
+ * the UTC time zone instead.
+ *
* @param context the context is required only if the time is shown
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
@@ -156,10 +159,16 @@
public String formatDateRange(Context context, long startMillis,
long endMillis, int flags) {
String date;
+ String tz;
+ if ((flags & DateUtils.FORMAT_UTC) != 0) {
+ tz = Time.TIMEZONE_UTC;
+ } else {
+ tz = getTimeZone(context, null);
+ }
synchronized (mSB) {
mSB.setLength(0);
date = DateUtils.formatDateRange(context, mF, startMillis, endMillis, flags,
- getTimeZone(context, null)).toString();
+ tz).toString();
}
return date;
}
diff --git a/core/java/android/view/DragEvent.aidl b/core/java/android/view/DragEvent.aidl
new file mode 100644
index 0000000..f08943f
--- /dev/null
+++ b/core/java/android/view/DragEvent.aidl
@@ -0,0 +1,19 @@
+/*
+** 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.
+*/
+
+package android.view;
+
+parcelable DragEvent;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
new file mode 100644
index 0000000..93598cd
--- /dev/null
+++ b/core/java/android/view/DragEvent.java
@@ -0,0 +1,191 @@
+/*
+ * 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.view;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** !!! TODO: real docs */
+public class DragEvent implements Parcelable {
+ private static final boolean TRACK_RECYCLED_LOCATION = false;
+
+ int mAction;
+ float mX, mY;
+ ClipDescription mClipDescription;
+ ClipData mClipData;
+
+ private DragEvent mNext;
+ private RuntimeException mRecycledLocation;
+ private boolean mRecycled;
+
+ private static final int MAX_RECYCLED = 10;
+ private static final Object gRecyclerLock = new Object();
+ private static int gRecyclerUsed = 0;
+ private static DragEvent gRecyclerTop = null;
+
+ /**
+ * action constants for DragEvent dispatch
+ */
+ public static final int ACTION_DRAG_STARTED = 1;
+ public static final int ACTION_DRAG_LOCATION = 2;
+ public static final int ACTION_DROP = 3;
+ public static final int ACTION_DRAG_ENDED = 4;
+ public static final int ACTION_DRAG_ENTERED = 5;
+ public static final int ACTION_DRAG_EXITED = 6;
+
+ /* hide the constructor behind package scope */
+ private DragEvent() {
+ }
+
+ static DragEvent obtain() {
+ return DragEvent.obtain(0, 0f, 0f, null, null);
+ }
+
+ public static DragEvent obtain(int action, float x, float y,
+ ClipDescription description, ClipData data) {
+ final DragEvent ev;
+ synchronized (gRecyclerLock) {
+ if (gRecyclerTop == null) {
+ return new DragEvent();
+ }
+ ev = gRecyclerTop;
+ gRecyclerTop = ev.mNext;
+ gRecyclerUsed -= 1;
+ }
+ ev.mRecycledLocation = null;
+ ev.mRecycled = false;
+ ev.mNext = null;
+
+ ev.mAction = action;
+ ev.mX = x;
+ ev.mY = y;
+ ev.mClipDescription = description;
+ ev.mClipData = data;
+
+ return ev;
+ }
+
+ public static DragEvent obtain(DragEvent source) {
+ return obtain(source.mAction, source.mX, source.mY,
+ source.mClipDescription, source.mClipData);
+ }
+
+ public int getAction() {
+ return mAction;
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public ClipData getClipData() {
+ return mClipData;
+ }
+
+ public ClipDescription getClipDescription() {
+ return mClipDescription;
+ }
+
+ /**
+ * Recycle the DragEvent, to be re-used by a later caller. After calling
+ * this function you must never touch the event again.
+ */
+ public final void recycle() {
+ // Ensure recycle is only called once!
+ if (TRACK_RECYCLED_LOCATION) {
+ if (mRecycledLocation != null) {
+ throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
+ }
+ mRecycledLocation = new RuntimeException("Last recycled here");
+ } else {
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+ mRecycled = true;
+ }
+
+ mClipData = null;
+ mClipDescription = null;
+
+ synchronized (gRecyclerLock) {
+ if (gRecyclerUsed < MAX_RECYCLED) {
+ gRecyclerUsed++;
+ mNext = gRecyclerTop;
+ gRecyclerTop = this;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
+ + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
+ + " data=" + mClipData
+ + "}";
+ }
+
+ /* Parcelable interface */
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAction);
+ dest.writeFloat(mX);
+ dest.writeFloat(mY);
+ if (mClipData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mClipData.writeToParcel(dest, flags);
+ }
+ if (mClipDescription == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mClipDescription.writeToParcel(dest, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<DragEvent> CREATOR =
+ new Parcelable.Creator<DragEvent>() {
+ public DragEvent createFromParcel(Parcel in) {
+ DragEvent event = DragEvent.obtain();
+ event.mAction = in.readInt();
+ event.mX = in.readFloat();
+ event.mY = in.readFloat();
+ if (in.readInt() != 0) {
+ event.mClipData = ClipData.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
+ }
+ return event;
+ }
+
+ public DragEvent[] newArray(int size) {
+ return new DragEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 921018a..85a8c1a 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -64,4 +65,9 @@
void dispatchWallpaperCommand(String action, int x, int y,
int z, in Bundle extras, boolean sync);
+
+ /**
+ * Drag/drop events
+ */
+ void dispatchDragEvent(in DragEvent event);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7f10b76..79ea5b6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -17,6 +17,7 @@
package android.view;
+import android.content.ClipData;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
@@ -116,6 +117,30 @@
boolean performHapticFeedback(IWindow window, int effectId, boolean always);
/**
+ * Allocate the drag's thumbnail surface. Also assigns a token that identifies
+ * the drag to the OS and passes that as the return value. A return value of
+ * null indicates failure.
+ */
+ IBinder prepareDrag(IWindow window, boolean localOnly,
+ int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
+
+ /**
+ * Initiate the drag operation itself
+ */
+ boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
+ float thumbCenterX, float thumbCenterY, in ClipData data);
+
+ /**
+ * Tell the OS that we've just dragged into a View that is willing to accept the drop
+ */
+ void dragRecipientEntered(IWindow window);
+
+ /**
+ * Tell the OS that we've just dragged *off* of a View that was willing to accept the drop
+ */
+ void dragRecipientExited(IWindow window);
+
+ /**
* For windows with the wallpaper behind them, and the wallpaper is
* larger than the screen, set the offset within the screen.
* For multi screen launcher type applications, xstep and ystep indicate
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6705596..dfbe65c 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import android.graphics.Matrix;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -347,6 +348,8 @@
private RuntimeException mRecycledLocation;
private boolean mRecycled;
+ private native void nativeTransform(Matrix matrix);
+
private MotionEvent(int pointerCount, int sampleCount) {
mPointerIdentifiers = new int[pointerCount];
mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
@@ -1413,6 +1416,19 @@
mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y];
}
+ /**
+ * Applies a transformation matrix to all of the points in the event.
+ *
+ * @param matrix The transformation matrix to apply.
+ */
+ public final void transform(Matrix matrix) {
+ if (matrix == null) {
+ throw new IllegalArgumentException("matrix must not be null");
+ }
+
+ nativeTransform(matrix);
+ }
+
private final void getPointerCoordsAtSampleIndex(int sampleIndex,
PointerCoords outPointerCoords) {
final float[] dataSamples = mDataSamples;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3b10437..63450de 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
package android.view;
+import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -1681,7 +1682,7 @@
* The transform matrix for the View. This transform is calculated internally
* based on the rotation, scaleX, and scaleY properties. The identity matrix
* is used by default. Do *not* use this variable directly; instead call
- * getMatrix(), which will automatically recalculate the matrix if necessary
+ * getInverseMatrix(), which will automatically recalculate the matrix if necessary
* to get the correct matrix based on the latest rotation and scale properties.
*/
private Matrix mInverseMatrix;
@@ -1704,7 +1705,8 @@
* A variable that tracks whether we need to recalculate the
* transform matrix, based on whether the rotation or scaleX/Y properties
* have changed since the matrix was last calculated. This variable
- * is only valid after a call to getMatrix().
+ * is only valid after a call to updateMatrix() or to a function that
+ * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
*/
private boolean mMatrixIsIdentity = true;
@@ -2030,6 +2032,14 @@
private int mTouchSlop;
/**
+ * Cache drag/drop state
+ *
+ */
+ boolean mCanAcceptDrop;
+ private int mThumbnailWidth;
+ private int mThumbnailHeight;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -3946,6 +3956,12 @@
return true;
}
+ /** Gets the ViewRoot, or null if not attached. */
+ /*package*/ ViewRoot getViewRoot() {
+ View root = getRootView();
+ return root != null ? (ViewRoot)root.getParent() : null;
+ }
+
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
* special variant of {@link #requestFocus() } that will allow views that are not focuable in
@@ -3959,12 +3975,9 @@
public final boolean requestFocusFromTouch() {
// Leave touch mode if we need to
if (isInTouchMode()) {
- View root = getRootView();
- if (root != null) {
- ViewRoot viewRoot = (ViewRoot)root.getParent();
- if (viewRoot != null) {
- viewRoot.ensureTouchMode(false);
- }
+ ViewRoot viewRoot = getViewRoot();
+ if (viewRoot != null) {
+ viewRoot.ensureTouchMode(false);
}
}
return requestFocus(View.FOCUS_DOWN);
@@ -4894,6 +4907,9 @@
}
if ((changed & VISIBILITY_MASK) != 0) {
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup)mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
+ }
dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
}
@@ -5108,7 +5124,7 @@
* @return The current transform matrix for the view
*/
public Matrix getMatrix() {
- hasIdentityMatrix();
+ updateMatrix();
return mMatrix;
}
@@ -5123,11 +5139,20 @@
}
/**
- * Recomputes the transform matrix if necessary.
+ * Returns true if the transform matrix is the identity matrix.
+ * Recomputes the matrix if necessary.
*
* @return True if the transform matrix is the identity matrix, false otherwise.
*/
- boolean hasIdentityMatrix() {
+ final boolean hasIdentityMatrix() {
+ updateMatrix();
+ return mMatrixIsIdentity;
+ }
+
+ /**
+ * Recomputes the transform matrix if necessary.
+ */
+ private void updateMatrix() {
if (mMatrixDirty) {
// transform-related properties have changed since the last time someone
// asked for the matrix; recalculate it with the current values
@@ -5166,7 +5191,6 @@
mMatrixIsIdentity = mMatrix.isIdentity();
mInverseMatrixDirty = true;
}
- return mMatrixIsIdentity;
}
/**
@@ -5176,7 +5200,8 @@
*
* @return The inverse of the current matrix of this view.
*/
- Matrix getInverseMatrix() {
+ final Matrix getInverseMatrix() {
+ updateMatrix();
if (mInverseMatrixDirty) {
if (mInverseMatrix == null) {
mInverseMatrix = new Matrix();
@@ -5209,11 +5234,11 @@
public void setRotation(float rotation) {
if (mRotation != rotation) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mRotation = rotation;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5240,11 +5265,11 @@
public void setRotationY(float rotationY) {
if (mRotationY != rotationY) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mRotationY = rotationY;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5271,11 +5296,11 @@
public void setRotationX(float rotationX) {
if (mRotationX != rotationX) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mRotationX = rotationX;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5304,11 +5329,11 @@
public void setScaleX(float scaleX) {
if (mScaleX != scaleX) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mScaleX = scaleX;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5337,11 +5362,11 @@
public void setScaleY(float scaleY) {
if (mScaleY != scaleY) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mScaleY = scaleY;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5376,11 +5401,11 @@
mPrivateFlags |= PIVOT_EXPLICITLY_SET;
if (mPivotX != pivotX) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mPivotX = pivotX;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5414,11 +5439,11 @@
mPrivateFlags |= PIVOT_EXPLICITLY_SET;
if (mPivotY != pivotY) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mPivotY = pivotY;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5441,8 +5466,12 @@
*/
public void setAlpha(float alpha) {
mAlpha = alpha;
- onSetAlpha((int) (alpha * 255));
- invalidate();
+ if (onSetAlpha((int) (alpha * 255))) {
+ // subclass is handling alpha - don't optimize rendering cache invalidation
+ invalidate();
+ } else {
+ invalidate(false);
+ }
}
/**
@@ -5464,7 +5493,8 @@
*/
public final void setTop(int top) {
if (top != mTop) {
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5485,8 +5515,13 @@
invalidate();
}
+ int width = mRight - mLeft;
+ int oldHeight = mBottom - mTop;
+
mTop = top;
+ onSizeChanged(width, mBottom - mTop, width, oldHeight);
+
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
invalidate();
@@ -5513,7 +5548,8 @@
*/
public final void setBottom(int bottom) {
if (bottom != mBottom) {
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5531,8 +5567,13 @@
invalidate();
}
+ int width = mRight - mLeft;
+ int oldHeight = mBottom - mTop;
+
mBottom = bottom;
+ onSizeChanged(width, mBottom - mTop, width, oldHeight);
+
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
invalidate();
@@ -5559,8 +5600,8 @@
*/
public final void setLeft(int left) {
if (left != mLeft) {
- System.out.println("view " + this + " left = " + left);
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5581,12 +5622,18 @@
invalidate();
}
+ int oldWidth = mRight - mLeft;
+ int height = mBottom - mTop;
+
mLeft = left;
+ onSizeChanged(mRight - mLeft, height, oldWidth, height);
+
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
invalidate();
}
+
}
}
@@ -5609,7 +5656,8 @@
*/
public final void setRight(int right) {
if (right != mRight) {
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5627,8 +5675,13 @@
invalidate();
}
+ int oldWidth = mRight - mLeft;
+ int height = mBottom - mTop;
+
mRight = right;
+ onSizeChanged(mRight - mLeft, height, oldWidth, height);
+
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
invalidate();
@@ -5703,11 +5756,11 @@
public void setTranslationX(float translationX) {
if (mTranslationX != translationX) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mTranslationX = translationX;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5734,11 +5787,11 @@
public void setTranslationY(float translationY) {
if (mTranslationY != translationY) {
// Double-invalidation is necessary to capture view's old and new areas
- invalidate();
+ invalidate(false);
mTranslationY = translationY;
mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
@@ -5748,26 +5801,34 @@
* @param outRect The hit rectangle of the view.
*/
public void getHitRect(Rect outRect) {
- if (hasIdentityMatrix() || mAttachInfo == null) {
+ updateMatrix();
+ if (mMatrixIsIdentity || mAttachInfo == null) {
outRect.set(mLeft, mTop, mRight, mBottom);
} else {
- Matrix m = getMatrix();
final RectF tmpRect = mAttachInfo.mTmpTransformRect;
tmpRect.set(-mPivotX, -mPivotY, getWidth() - mPivotX, getHeight() - mPivotY);
- m.mapRect(tmpRect);
+ mMatrix.mapRect(tmpRect);
outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
(int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
}
}
/**
+ * Determines whether the given point, in local coordinates is inside the view.
+ */
+ /*package*/ final boolean pointInView(float localX, float localY) {
+ return localX >= 0 && localX < (mRight - mLeft)
+ && localY >= 0 && localY < (mBottom - mTop);
+ }
+
+ /**
* Utility method to determine whether the given point, in local coordinates,
* is inside the view, where the area of the view is expanded by the slop factor.
* This method is called while processing touch-move events to determine if the event
* is still within the view.
*/
private boolean pointInView(float localX, float localY, float slop) {
- return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) &&
+ return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
@@ -5832,7 +5893,8 @@
*/
public void offsetTopAndBottom(int offset) {
if (offset != 0) {
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5852,7 +5914,7 @@
p.invalidateChild(this, r);
}
} else {
- invalidate();
+ invalidate(false);
}
mTop += offset;
@@ -5860,7 +5922,7 @@
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
}
@@ -5872,7 +5934,8 @@
*/
public void offsetLeftAndRight(int offset) {
if (offset != 0) {
- if (hasIdentityMatrix()) {
+ updateMatrix();
+ if (mMatrixIsIdentity) {
final ViewParent p = mParent;
if (p != null && mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
@@ -5889,7 +5952,7 @@
p.invalidateChild(this, r);
}
} else {
- invalidate();
+ invalidate(false);
}
mLeft += offset;
@@ -5897,7 +5960,7 @@
if (!mMatrixIsIdentity) {
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate();
+ invalidate(false);
}
}
}
@@ -6144,7 +6207,8 @@
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
@@ -6175,7 +6239,8 @@
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
@@ -6195,12 +6260,31 @@
* UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
*/
public void invalidate() {
+ invalidate(true);
+ }
+
+ /**
+ * This is where the invalidate() work actually happens. A full invalidate()
+ * causes the drawing cache to be invalidated, but this function can be called with
+ * invalidateCache set to false to skip that invalidation step for cases that do not
+ * need it (for example, a component that remains at the same dimensions with the same
+ * content).
+ *
+ * @param invalidateCache Whether the drawing cache for this view should be invalidated as
+ * well. This is usually true for a full invalidate, but may be set to false if the
+ * View's contents or dimensions have not changed.
+ */
+ private void invalidate(boolean invalidateCache) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
- mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID)) {
+ mPrivateFlags &= ~DRAWN;
+ if (invalidateCache) {
+ mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ }
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
if (p != null && ai != null) {
@@ -7346,8 +7430,7 @@
final int restoreCount = canvas.save();
- mPrivateFlags |= DRAWN;
- mPrivateFlags |= DRAWING_CACHE_VALID;
+ mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
@@ -9754,6 +9837,199 @@
}
/**
+ * !!! TODO: real docs
+ *
+ * The base class implementation makes the thumbnail the same size and appearance
+ * as the view itself, and positions it with its center at the touch point.
+ */
+ public class DragThumbnailBuilder {
+ private View mView;
+
+ /**
+ * Construct a thumbnail builder object for use with the given view.
+ * @param view
+ */
+ public DragThumbnailBuilder(View view) {
+ mView = view;
+ }
+
+ /**
+ * Provide the draggable-thumbnail metrics for the operation: the dimensions of
+ * the thumbnail image itself, and the point within that thumbnail that should
+ * be centered under the touch location while dragging.
+ * <p>
+ * The default implementation sets the dimensions of the thumbnail to be the
+ * same as the dimensions of the View itself and centers the thumbnail under
+ * the touch point.
+ *
+ * @param thumbnailSize The application should set the {@code x} member of this
+ * parameter to the desired thumbnail width, and the {@code y} member to
+ * the desired height.
+ * @param thumbnailTouchPoint The application should set this point to be the
+ * location within the thumbnail that should track directly underneath
+ * the touch point on the screen during a drag.
+ */
+ public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) {
+ thumbnailSize.set(mView.getWidth(), mView.getHeight());
+ thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+ }
+
+ /**
+ * Draw the thumbnail image for the upcoming drag. The thumbnail canvas was
+ * created with the dimensions supplied by the onProvideThumbnailMetrics()
+ * callback.
+ *
+ * @param canvas
+ */
+ public void onDrawThumbnail(Canvas canvas) {
+ mView.draw(canvas);
+ }
+ }
+
+ /**
+ * Drag and drop. App calls startDrag(), then callbacks to onMeasureDragThumbnail()
+ * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
+ * OS.
+ * !!! TODO: real docs
+ * @hide
+ */
+ public final boolean startDrag(ClipData data, DragThumbnailBuilder thumbBuilder,
+ boolean myWindowOnly) {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " local=" + myWindowOnly);
+ }
+ boolean okay = false;
+
+ Point thumbSize = new Point();
+ Point thumbTouchPoint = new Point();
+ thumbBuilder.onProvideThumbnailMetrics(thumbSize, thumbTouchPoint);
+
+ if ((thumbSize.x < 0) || (thumbSize.y < 0) ||
+ (thumbTouchPoint.x < 0) || (thumbTouchPoint.y < 0)) {
+ throw new IllegalStateException("Drag thumb dimensions must not be negative");
+ }
+
+ Surface surface = new Surface();
+ try {
+ IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+ myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+ + " surface=" + surface);
+ if (token != null) {
+ Canvas canvas = surface.lockCanvas(null);
+ try {
+ thumbBuilder.onDrawThumbnail(canvas);
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
+
+ // repurpose 'thumbSize' for the last touch point
+ getViewRoot().getLastTouchPoint(thumbSize);
+
+ okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+ (float) thumbSize.x, (float) thumbSize.y,
+ (float) thumbTouchPoint.x, (float) thumbTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+ }
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
+ surface.destroy();
+ }
+
+ return okay;
+ }
+
+ private void measureThumbnail() {
+ mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+
+ onMeasureDragThumbnail();
+
+ // flag not set, setDragThumbnailDimension() was not invoked, we raise
+ // an exception to warn the developer
+ if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+ throw new IllegalStateException("onMeasureDragThumbnail() did not set the"
+ + " measured dimension by calling setDragThumbnailDimension()");
+ }
+
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+ + " h=" + mThumbnailHeight);
+ }
+ }
+
+ /**
+ * The View must call this method from onMeasureDragThumbnail() in order to
+ * specify the dimensions of the drag thumbnail image.
+ *
+ * @param width The desired thumbnail width.
+ * @param height The desired thumbnail height.
+ */
+ protected final void setDragThumbnailDimension(int width, int height) {
+ mPrivateFlags |= MEASURED_DIMENSION_SET;
+ mThumbnailWidth = width;
+ mThumbnailHeight = height;
+ }
+
+ /**
+ * The default implementation specifies a drag thumbnail that matches the
+ * View's current size and appearance.
+ */
+ protected void onMeasureDragThumbnail() {
+ setDragThumbnailDimension(getWidth(), getHeight());
+ }
+
+ /**
+ * The default implementation just draws the current View appearance as the thumbnail
+ * @param canvas
+ */
+ protected void onDrawDragThumbnail(Canvas canvas) {
+ draw(canvas);
+ }
+
+ /**
+ * Drag-and-drop event dispatch. The event.getAction() verb is one of the DragEvent
+ * constants DRAG_STARTED_EVENT, DRAG_EVENT, DROP_EVENT, and DRAG_ENDED_EVENT.
+ *
+ * For DRAG_STARTED_EVENT, event.getClipDescription() describes the content
+ * being dragged. onDragEvent() should return 'true' if the view can handle
+ * a drop of that content. A view that returns 'false' here will receive no
+ * further calls to onDragEvent() about the drag/drop operation.
+ *
+ * For DRAG_ENTERED, event.getClipDescription() describes the content being
+ * dragged. This will be the same content description passed in the
+ * DRAG_STARTED_EVENT invocation.
+ *
+ * For DRAG_EXITED, event.getClipDescription() describes the content being
+ * dragged. This will be the same content description passed in the
+ * DRAG_STARTED_EVENT invocation. The view should return to its approriate
+ * drag-acceptance visual state.
+ *
+ * For DRAG_LOCATION_EVENT, event.getX() and event.getY() give the location in View
+ * coordinates of the current drag point. The view must return 'true' if it
+ * can accept a drop of the current drag content, false otherwise.
+ *
+ * For DROP_EVENT, event.getX() and event.getY() give the location of the drop
+ * within the view; also, event.getClipData() returns the full data payload
+ * being dropped. The view should return 'true' if it consumed the dropped
+ * content, 'false' if it did not.
+ *
+ * For DRAG_ENDED_EVENT, the 'event' argument may be null. The view should return
+ * to its normal visual state.
+ */
+ protected boolean onDragEvent(DragEvent event) {
+ return false;
+ }
+
+ /**
+ * Views typically don't need to override dispatchDragEvent(); it just calls
+ * onDragEvent(what, event) and passes the result up appropriately.
+ *
+ */
+ public boolean dispatchDragEvent(DragEvent event) {
+ return onDragEvent(event);
+ }
+
+ /**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
* @hide
@@ -9821,30 +10097,6 @@
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
- private static int[] stateSetUnion(final int[] stateSet1, final int[] stateSet2) {
- final int stateSet1Length = stateSet1.length;
- final int stateSet2Length = stateSet2.length;
- final int[] newSet = new int[stateSet1Length + stateSet2Length];
- int k = 0;
- int i = 0;
- int j = 0;
- // This is a merge of the two input state sets and assumes that the
- // input sets are sorted by the order imposed by ViewDrawableStates.
- for (int viewState : R.styleable.ViewDrawableStates) {
- if (i < stateSet1Length && stateSet1[i] == viewState) {
- newSet[k++] = viewState;
- i++;
- } else if (j < stateSet2Length && stateSet2[j] == viewState) {
- newSet[k++] = viewState;
- j++;
- }
- if (k > 1) {
- assert(newSet[k - 1] > newSet[k - 2]);
- }
- }
- return newSet;
- }
-
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index b1d5272..727cf17 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -136,6 +136,12 @@
public static final boolean DEBUG_SHOW_FPS = false;
/**
+ * Enables detailed logging of drag/drop operations.
+ * @hide
+ */
+ public static final boolean DEBUG_DRAG = true;
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
* view consistency checks happen only if {@link android.util.Config#DEBUG} is set
* to true. The value of this property can be configured externally in one of the
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 570e288..e71a4d6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,8 @@
import android.animation.LayoutTransition;
import com.android.internal.R;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -26,6 +28,7 @@
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@@ -105,16 +108,27 @@
*/
private Transformation mInvalidationTransformation;
- // Target of Motion events
- private View mMotionTarget;
+ // View currently under an ongoing drag
+ private View mCurrentDragView;
- // Targets of MotionEvents in split mode
- private SplitMotionTargets mSplitMotionTargets;
+ // Does this group have a child that can accept the current drag payload?
+ private boolean mChildAcceptsDrag;
+
+ // Used during drag dispatch
+ private final PointF mLocalPoint = new PointF();
// Layout animation
private LayoutAnimationController mLayoutAnimationController;
private Animation.AnimationListener mAnimationListener;
+ // First touch target in the linked list of touch targets.
+ private TouchTarget mFirstTouchTarget;
+
+ // Temporary arrays for splitting pointers.
+ private int[] mTmpPointerIndexMap;
+ private int[] mTmpPointerIds;
+ private MotionEvent.PointerCoords[] mTmpPointerCoords;
+
/**
* Internal flags.
*
@@ -307,6 +321,10 @@
// being animated.
private ArrayList<View> mTransitioningViews;
+ // List of children changing visibility. This is used to potentially keep rendering
+ // views during a transition when they otherwise would have become gone/invisible
+ private ArrayList<View> mVisibilityChangingChildren;
+
public ViewGroup(Context context) {
super(context);
initViewGroup();
@@ -742,6 +760,32 @@
}
/**
+ * @hide
+ * @param child
+ * @param visibility
+ */
+ void onChildVisibilityChanged(View child, int visibility) {
+ if (mTransition != null) {
+ if (visibility == VISIBLE) {
+ mTransition.showChild(this, child);
+ } else {
+ mTransition.hideChild(this, child);
+ }
+ if (visibility != VISIBLE) {
+ // Only track this on disappearing views - appearing views are already visible
+ // and don't need special handling during drawChild()
+ if (mVisibilityChangingChildren == null) {
+ mVisibilityChangingChildren = new ArrayList<View>();
+ }
+ mVisibilityChangingChildren.add(child);
+ if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+ addDisappearingView(child);
+ }
+ }
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -813,6 +857,125 @@
/**
* {@inheritDoc}
+ *
+ * !!! TODO: write real docs
+ */
+ @Override
+ public boolean dispatchDragEvent(DragEvent event) {
+ boolean retval = false;
+ final float tx = event.mX;
+ final float ty = event.mY;
+
+ ViewRoot root = getViewRoot();
+
+ // Dispatch down the view hierarchy
+ switch (event.mAction) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ // clear state to recalculate which views we drag over
+ root.setDragFocus(event, null);
+
+ // Now dispatch down to our children, caching the responses
+ mChildAcceptsDrag = false;
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if (child.getVisibility() == VISIBLE) {
+ final boolean handled = children[i].dispatchDragEvent(event);
+ children[i].mCanAcceptDrop = handled;
+ if (handled) {
+ mChildAcceptsDrag = true;
+ }
+ }
+ }
+
+ // Return HANDLED if one of our children can accept the drag
+ if (mChildAcceptsDrag) {
+ retval = true;
+ }
+ } break;
+
+ case DragEvent.ACTION_DRAG_ENDED: {
+ // Notify all of our children that the drag is over
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ final View child = children[i];
+ if (child.getVisibility() == VISIBLE) {
+ child.dispatchDragEvent(event);
+ }
+ }
+ // We consider drag-ended to have been handled if one of our children
+ // had offered to handle the drag.
+ if (mChildAcceptsDrag) {
+ retval = true;
+ }
+ } break;
+
+ case DragEvent.ACTION_DRAG_LOCATION: {
+ // Find the [possibly new] drag target
+ final View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+
+ // If we've changed apparent drag target, tell the view root which view
+ // we're over now. This will in turn send out DRAG_ENTERED / DRAG_EXITED
+ // notifications as appropriate.
+ if (mCurrentDragView != target) {
+ root.setDragFocus(event, target);
+ mCurrentDragView = target;
+ }
+
+ // Dispatch the actual drag location notice, localized into its coordinates
+ if (target != null) {
+ event.mX = mLocalPoint.x;
+ event.mY = mLocalPoint.y;
+
+ retval = target.dispatchDragEvent(event);
+
+ event.mX = tx;
+ event.mY = ty;
+ }
+ } break;
+
+ case DragEvent.ACTION_DROP: {
+ if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event);
+ View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
+ if (target != null) {
+ event.mX = mLocalPoint.x;
+ event.mY = mLocalPoint.y;
+ retval = target.dispatchDragEvent(event);
+ event.mX = tx;
+ event.mY = ty;
+ }
+ } break;
+ }
+
+ // If none of our children could handle the event, try here
+ if (!retval) {
+ retval = onDragEvent(event);
+ }
+ return retval;
+ }
+
+ // Find the frontmost child view that lies under the given point, and calculate
+ // the position within its own local coordinate system.
+ View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (child.mCanAcceptDrop == false) {
+ continue;
+ }
+
+ if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
*/
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
@@ -872,150 +1035,255 @@
return false;
}
- if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS) {
- if (mSplitMotionTargets == null) {
- mSplitMotionTargets = new SplitMotionTargets();
- }
- return dispatchSplitTouchEvent(ev);
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
}
- final int action = ev.getAction();
- final float xf = ev.getX();
- final float yf = ev.getY();
- final float scrolledXFloat = xf + mScrollX;
- final float scrolledYFloat = yf + mScrollY;
-
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
- // this is weird, we got a pen down, but we thought it was
- // already down!
- // XXX: We should probably send an ACTION_UP to the current
- // target.
- mMotionTarget = null;
+ // Check for interception.
+ final boolean intercepted;
+ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
+ } else {
+ intercepted = false;
}
- // If we're disallowing intercept or if we're allowing and we didn't
- // intercept
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- // reset this event's action (just to protect ourselves)
- ev.setAction(MotionEvent.ACTION_DOWN);
- // We know we want to dispatch the event down, find a child
- // who can handle it, start with the front-most child.
- final View[] children = mChildren;
- final int count = mChildrenCount;
+ } else {
+ intercepted = true;
+ }
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- // Single dispatch always picks its target based on the initial down
- // event's position - index 0
- if (dispatchTouchEventIfInView(child, ev, 0)) {
- mMotionTarget = child;
- return true;
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
+
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
+
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ // Find a child that can receive the event. Scan children from front to back.
+ final View[] children = mChildren;
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
+ && child.getAnimation() == null) {
+ // Skip invisible child unless it is animating.
+ continue;
+ }
+
+ if (!isTransformedTouchPointInView(x, y, child, null)) {
+ // New pointer is out of child's bounds.
+ continue;
+ }
+
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
+
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
}
}
}
+
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ }
}
}
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
-
- if (isUpOrCancel) {
- // Note, we've already copied the previous state to our local
- // variable, so this takes effect on the next event
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
-
- // The event wasn't an ACTION_DOWN, dispatch it to our target if
- // we have one.
- final View target = mMotionTarget;
- if (target == null) {
- // We don't have a target, this means we're handling the
- // event as a regular view.
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- return super.dispatchTouchEvent(ev);
- }
-
- // Calculate the offset point into the target's local coordinates
- float xc = scrolledXFloat - (float) target.mLeft;
- float yc = scrolledYFloat - (float) target.mTop;
- if (!target.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's coordinates
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = xc;
- localXY[1] = yc;
- target.getInverseMatrix().mapPoints(localXY);
- xc = localXY[0];
- yc = localXY[1];
- }
-
- // if have a target, see if we're allowed to and want to intercept its
- // events
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- // clear the target
- mMotionTarget = null;
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
-
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
-
- // finally offset the event to the target's coordinate system and
- // dispatch the event.
- ev.setLocation(xc, yc);
-
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- mMotionTarget = null;
- }
-
- if (target.dispatchTouchEvent(ev)) {
- return true;
+ // Dispatch to touch targets.
+ boolean handled = false;
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
} else {
- ev.setLocation(xf, yf);
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+ handled = true;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
+ }
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled || actionMasked == MotionEvent.ACTION_UP) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
+ }
+
+ return handled;
+ }
+
+ /* Resets all touch state in preparation for a new cycle. */
+ private final void resetTouchState() {
+ clearTouchTargets();
+ resetCancelNextUpFlag(this);
+ mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+ }
+
+ /* Resets the cancel next up flag.
+ * Returns true if the flag was previously set. */
+ private final boolean resetCancelNextUpFlag(View view) {
+ if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
+ view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ return true;
}
return false;
}
- /**
- * This method detects whether the pointer location at <code>pointerIndex</code> within
- * <code>ev</code> is inside the specified view. If so, the transformed event is dispatched to
- * <code>child</code>.
- *
- * @param child View to hit test against
- * @param ev MotionEvent to test
- * @param pointerIndex Index of the pointer within <code>ev</code> to test
- * @return <code>false</code> if the hit test failed, or the result of
- * <code>child.dispatchTouchEvent</code>
- */
- private boolean dispatchTouchEventIfInView(View child, MotionEvent ev, int pointerIndex) {
- final float x = ev.getX(pointerIndex);
- final float y = ev.getY(pointerIndex);
- final float scrolledX = x + mScrollX;
- final float scrolledY = y + mScrollY;
- float localX = scrolledX - child.mLeft;
- float localY = scrolledY - child.mTop;
- if (!child.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's coordinates
+ /* Clears all touch targets. */
+ private final void clearTouchTargets() {
+ TouchTarget target = mFirstTouchTarget;
+ if (target != null) {
+ do {
+ TouchTarget next = target.next;
+ target.recycle();
+ target = next;
+ } while (target != null);
+ mFirstTouchTarget = null;
+ }
+ }
+
+ /* Cancels and clears all touch targets. */
+ private final void cancelAndClearTouchTargets(MotionEvent event) {
+ if (mFirstTouchTarget != null) {
+ boolean syntheticEvent = false;
+ if (event == null) {
+ final long now = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ syntheticEvent = true;
+ }
+
+ for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+ resetCancelNextUpFlag(target.child);
+ dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
+ }
+ clearTouchTargets();
+
+ if (syntheticEvent) {
+ event.recycle();
+ }
+ }
+ }
+
+ /* Gets the touch target for specified child view.
+ * Returns null if not found. */
+ private final TouchTarget getTouchTarget(View child) {
+ for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
+ if (target.child == child) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ /* Adds a touch target for specified child to the beginning of the list.
+ * Assumes the target child is not already present. */
+ private final TouchTarget addTouchTarget(View child, int pointerIdBits) {
+ TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
+ target.next = mFirstTouchTarget;
+ mFirstTouchTarget = target;
+ return target;
+ }
+
+ /* Removes the pointer ids from consideration. */
+ private final void removePointersFromTouchTargets(int pointerIdBits) {
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if ((target.pointerIdBits & pointerIdBits) != 0) {
+ target.pointerIdBits &= ~pointerIdBits;
+ if (target.pointerIdBits == 0) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
+ }
+ }
+ predecessor = target;
+ target = next;
+ }
+ }
+
+ /* Returns true if a child view contains the specified point when transformed
+ * into its coordinate space.
+ * Child must not be null. */
+ private final boolean isTransformedTouchPointInView(float x, float y, View child,
+ PointF outLocalPoint) {
+ float localX = x + mScrollX - child.mLeft;
+ float localY = y + mScrollY - child.mTop;
+ if (! child.hasIdentityMatrix() && mAttachInfo != null) {
final float[] localXY = mAttachInfo.mTmpTransformLocation;
localXY[0] = localX;
localXY[1] = localY;
@@ -1023,224 +1291,219 @@
localX = localXY[0];
localY = localXY[1];
}
- if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) &&
- localY < (child.mBottom - child.mTop)) {
- // It would be safer to clone the event here but we don't for performance.
- // There are many subtle interactions in touch event dispatch; change at your own risk.
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.offsetLocation(localX - x, localY - y);
- if (child.dispatchTouchEvent(ev)) {
- return true;
- } else {
- ev.offsetLocation(x - localX, y - localY);
- return false;
- }
+ final boolean isInView = child.pointInView(localX, localY);
+ if (isInView && outLocalPoint != null) {
+ outLocalPoint.set(localX, localY);
}
- return false;
+ return isInView;
}
- private boolean dispatchSplitTouchEvent(MotionEvent ev) {
- final SplitMotionTargets targets = mSplitMotionTargets;
- final int action = ev.getAction();
- final int maskedAction = ev.getActionMasked();
- float xf = ev.getX();
- float yf = ev.getY();
- float scrolledXFloat = xf + mScrollX;
- float scrolledYFloat = yf + mScrollY;
+ /* Transforms a motion event into the coordinate space of a particular child view,
+ * filters out irrelevant pointer ids, and overrides its action if necessary.
+ * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
+ private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+ View child, int desiredPointerIdBits) {
+ final boolean handled;
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ // Canceling motions is a special case. We don't need to perform any transformations
+ // or filtering. The important part is the action, not the contents.
+ final int oldAction = event.getAction();
+ if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ if (child == null) {
+ handled = super.dispatchTouchEvent(event);
+ } else {
+ handled = child.dispatchTouchEvent(event);
+ }
+ event.setAction(oldAction);
+ return handled;
+ }
- if (maskedAction == MotionEvent.ACTION_DOWN ||
- maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
- final int actionIndex = ev.getActionIndex();
- final int actionId = ev.getPointerId(actionIndex);
+ // Calculate the number of pointers to deliver.
+ final int oldPointerCount = event.getPointerCount();
+ int newPointerCount = 0;
+ if (desiredPointerIdBits == TouchTarget.ALL_POINTER_IDS) {
+ newPointerCount = oldPointerCount;
+ } else {
+ for (int i = 0; i < oldPointerCount; i++) {
+ final int pointerId = event.getPointerId(i);
+ final int pointerIdBit = 1 << pointerId;
+ if ((pointerIdBit & desiredPointerIdBits) != 0) {
+ newPointerCount += 1;
+ }
+ }
+ }
- // Clear out any current target for this ID.
- // XXX: We should probably send an ACTION_UP to the current
- // target if present.
- targets.removeById(actionId);
+ // If for some reason we ended up in an inconsistent state where it looks like we
+ // might produce a motion event with no pointers in it, then drop the event.
+ if (newPointerCount == 0) {
+ return false;
+ }
- // If we're disallowing intercept or if we're allowing and we didn't
- // intercept
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- // reset this event's action (just to protect ourselves)
- ev.setAction(action);
- // We know we want to dispatch the event down, try to find a child
- // who can handle it, start with the front-most child.
- final long downTime = ev.getEventTime();
- final View[] children = mChildren;
- final int count = mChildrenCount;
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- final MotionEvent childEvent =
- targets.filterMotionEventForChild(ev, child, downTime);
- if (childEvent != null) {
- try {
- final int childActionIndex = childEvent.findPointerIndex(actionId);
- if (dispatchTouchEventIfInView(child, childEvent,
- childActionIndex)) {
- targets.add(actionId, child, downTime);
+ // If the number of pointers is the same and we don't need to perform any fancy
+ // irreversible transformations, then we can reuse the motion event for this
+ // dispatch as long as we are careful to revert any changes we make.
+ final boolean reuse = newPointerCount == oldPointerCount
+ && (child == null || child.hasIdentityMatrix());
+ if (reuse) {
+ if (child == null) {
+ handled = super.dispatchTouchEvent(event);
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ event.offsetLocation(offsetX, offsetY);
- return true;
- }
- } finally {
- childEvent.recycle();
+ handled = child.dispatchTouchEvent(event);
+
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return handled;
+ }
+
+ // Make a copy of the event.
+ // If the number of pointers is different, then we need to filter out irrelevant pointers
+ // as we make a copy of the motion event.
+ MotionEvent transformedEvent;
+ if (newPointerCount == oldPointerCount) {
+ transformedEvent = MotionEvent.obtain(event);
+ } else {
+ growTmpPointerArrays(newPointerCount);
+ final int[] newPointerIndexMap = mTmpPointerIndexMap;
+ final int[] newPointerIds = mTmpPointerIds;
+ final MotionEvent.PointerCoords[] newPointerCoords = mTmpPointerCoords;
+
+ int newPointerIndex = 0;
+ int oldPointerIndex = 0;
+ while (newPointerIndex < newPointerCount) {
+ final int pointerId = event.getPointerId(oldPointerIndex);
+ final int pointerIdBits = 1 << pointerId;
+ if ((pointerIdBits & desiredPointerIdBits) != 0) {
+ newPointerIndexMap[newPointerIndex] = oldPointerIndex;
+ newPointerIds[newPointerIndex] = pointerId;
+ if (newPointerCoords[newPointerIndex] == null) {
+ newPointerCoords[newPointerIndex] = new MotionEvent.PointerCoords();
+ }
+
+ newPointerIndex += 1;
+ }
+ oldPointerIndex += 1;
+ }
+
+ final int newAction;
+ if (cancel) {
+ newAction = MotionEvent.ACTION_CANCEL;
+ } else {
+ final int oldMaskedAction = oldAction & MotionEvent.ACTION_MASK;
+ if (oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+ || oldMaskedAction == MotionEvent.ACTION_POINTER_UP) {
+ final int changedPointerId = event.getPointerId(
+ (oldAction & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ final int changedPointerIdBits = 1 << changedPointerId;
+ if ((changedPointerIdBits & desiredPointerIdBits) != 0) {
+ if (newPointerCount == 1) {
+ // The first/last pointer went down/up.
+ newAction = oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN
+ ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ int newChangedPointerIndex = 0;
+ while (newPointerIds[newChangedPointerIndex] != changedPointerId) {
+ newChangedPointerIndex += 1;
}
+ newAction = oldMaskedAction | (newChangedPointerIndex
+ << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
+ } else {
+ // An unrelated pointer changed.
+ newAction = MotionEvent.ACTION_MOVE;
+ }
+ } else {
+ // Simple up/down/cancel/move motion action.
+ newAction = oldMaskedAction;
+ }
+ }
+
+ transformedEvent = null;
+ final int historySize = event.getHistorySize();
+ for (int historyIndex = 0; historyIndex <= historySize; historyIndex++) {
+ for (newPointerIndex = 0; newPointerIndex < newPointerCount; newPointerIndex++) {
+ final MotionEvent.PointerCoords c = newPointerCoords[newPointerIndex];
+ oldPointerIndex = newPointerIndexMap[newPointerIndex];
+ if (historyIndex != historySize) {
+ event.getHistoricalPointerCoords(oldPointerIndex, historyIndex, c);
+ } else {
+ event.getPointerCoords(oldPointerIndex, c);
}
}
- // Didn't find a new target. Do we have a "primary" target to send to?
- final SplitMotionTargets.TargetInfo primaryTargetInfo = targets.getPrimaryTarget();
- if (primaryTargetInfo != null) {
- final View primaryTarget = primaryTargetInfo.view;
- final MotionEvent childEvent = targets.filterMotionEventForChild(ev,
- primaryTarget, primaryTargetInfo.downTime);
- if (childEvent != null) {
- try {
- // Calculate the offset point into the target's local coordinates
- float xc = scrolledXFloat - (float) primaryTarget.mLeft;
- float yc = scrolledYFloat - (float) primaryTarget.mTop;
- if (!primaryTarget.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's
- // coordinates
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = xc;
- localXY[1] = yc;
- primaryTarget.getInverseMatrix().mapPoints(localXY);
- xc = localXY[0];
- yc = localXY[1];
- }
- childEvent.setLocation(xc, yc);
- if (primaryTarget.dispatchTouchEvent(childEvent)) {
- targets.add(actionId, primaryTarget, primaryTargetInfo.downTime);
- return true;
- }
- } finally {
- childEvent.recycle();
- }
- }
+ final long eventTime;
+ if (historyIndex != historySize) {
+ eventTime = event.getHistoricalEventTime(historyIndex);
+ } else {
+ eventTime = event.getEventTime();
+ }
+
+ if (transformedEvent == null) {
+ transformedEvent = MotionEvent.obtain(
+ event.getDownTime(), eventTime, newAction,
+ newPointerCount, newPointerIds, newPointerCoords,
+ event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
+ event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
+ event.getFlags());
+ } else {
+ transformedEvent.addBatch(eventTime, newPointerCoords, 0);
}
}
}
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
-
- if (isUpOrCancel) {
- // Note, we've already copied the previous state to our local
- // variable, so this takes effect on the next event
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
-
- if (targets.isEmpty()) {
- // We don't have any targets, this means we're handling the
- // event as a regular view.
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- return super.dispatchTouchEvent(ev);
- }
-
- // if we have targets, see if we're allowed to and want to intercept their
- // events
- int uniqueTargetCount = targets.getUniqueTargetCount();
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-
- for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) {
- final View target = targets.getUniqueTargetAt(uniqueIndex).view;
-
- // Calculate the offset point into the target's local coordinates
- float xc = scrolledXFloat - (float) target.mLeft;
- float yc = scrolledYFloat - (float) target.mTop;
- if (!target.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's coordinates
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = xc;
- localXY[1] = yc;
- target.getInverseMatrix().mapPoints(localXY);
- xc = localXY[0];
- yc = localXY[1];
- }
-
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- }
- targets.clear();
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
-
- boolean handled = false;
- for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) {
- final SplitMotionTargets.TargetInfo targetInfo = targets.getUniqueTargetAt(uniqueIndex);
- final View target = targetInfo.view;
-
- final MotionEvent targetEvent =
- targets.filterMotionEventForChild(ev, target, targetInfo.downTime);
- if (targetEvent == null) {
- continue;
+ // Perform any necessary transformations and dispatch.
+ if (child == null) {
+ handled = super.dispatchTouchEvent(transformedEvent);
+ } else {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+ transformedEvent.offsetLocation(offsetX, offsetY);
+ if (! child.hasIdentityMatrix()) {
+ transformedEvent.transform(child.getInverseMatrix());
}
- try {
- // Calculate the offset point into the target's local coordinates
- xf = targetEvent.getX();
- yf = targetEvent.getY();
- scrolledXFloat = xf + mScrollX;
- scrolledYFloat = yf + mScrollY;
- float xc = scrolledXFloat - (float) target.mLeft;
- float yc = scrolledYFloat - (float) target.mTop;
- if (!target.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's coordinates
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = xc;
- localXY[1] = yc;
- target.getInverseMatrix().mapPoints(localXY);
- xc = localXY[0];
- yc = localXY[1];
- }
-
- // finally offset the event to the target's coordinate system and
- // dispatch the event.
- targetEvent.setLocation(xc, yc);
-
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- targetEvent.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- targets.removeView(target);
- uniqueIndex--;
- uniqueTargetCount--;
- }
-
- handled |= target.dispatchTouchEvent(targetEvent);
- } finally {
- targetEvent.recycle();
- }
+ handled = child.dispatchTouchEvent(transformedEvent);
}
- if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
- final int removeId = ev.getPointerId(ev.getActionIndex());
- targets.removeById(removeId);
- }
-
- if (isUpOrCancel) {
- targets.clear();
- }
-
+ // Done.
+ transformedEvent.recycle();
return handled;
}
+ /* Enlarge the temporary pointer arrays for splitting pointers.
+ * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */
+ private final void growTmpPointerArrays(int desiredCapacity) {
+ final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
+ int capacity;
+ if (oldTmpPointerCoords != null) {
+ capacity = oldTmpPointerCoords.length;
+ if (desiredCapacity <= capacity) {
+ return;
+ }
+ } else {
+ capacity = 4;
+ }
+
+ while (capacity < desiredCapacity) {
+ capacity *= 2;
+ }
+
+ mTmpPointerIndexMap = new int[capacity];
+ mTmpPointerIds = new int[capacity];
+ mTmpPointerCoords = new MotionEvent.PointerCoords[capacity];
+
+ if (oldTmpPointerCoords != null) {
+ System.arraycopy(oldTmpPointerCoords, 0, mTmpPointerCoords, 0,
+ oldTmpPointerCoords.length);
+ }
+ }
+
/**
* Enable or disable the splitting of MotionEvents to multiple children during touch event
* dispatch. This behavior is disabled by default.
@@ -1262,7 +1525,6 @@
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
} else {
mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
- mSplitMotionTargets = null;
}
}
@@ -1473,19 +1735,12 @@
*/
@Override
void dispatchDetachedFromWindow() {
- // If we still have a motion target, we are still in the process of
+ // If we still have a touch target, we are still in the process of
// dispatching motion events to a child; we need to get rid of that
// child to avoid dispatching events to it after the window is torn
// down. To make sure we keep the child in a consistent state, we
// first send it an ACTION_CANCEL motion event.
- if (mMotionTarget != null) {
- final long now = SystemClock.uptimeMillis();
- final MotionEvent event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- mMotionTarget.dispatchTouchEvent(event);
- event.recycle();
- mMotionTarget = null;
- }
+ cancelAndClearTouchTargets(null);
final int count = mChildrenCount;
final View[] children = mChildren;
@@ -2365,7 +2620,7 @@
}
if (mTransition != null) {
- mTransition.childAdd(this, child);
+ mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
@@ -2584,7 +2839,7 @@
private void removeViewInternal(int index, View view) {
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
boolean clearChildFocus = false;
@@ -2660,7 +2915,7 @@
final View view = children[i];
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
if (view == focused) {
@@ -2729,7 +2984,7 @@
final View view = children[i];
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
if (view == focused) {
@@ -2772,7 +3027,7 @@
*/
protected void removeDetachedView(View child, boolean animate) {
if (mTransition != null) {
- mTransition.childRemove(this, child);
+ mTransition.removeChild(this, child);
}
if (child == mFocused) {
@@ -3792,11 +4047,16 @@
final ArrayList<View> disappearingChildren = mDisappearingChildren;
if (disappearingChildren != null && disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
- if (view.mAttachInfo != null) {
- view.dispatchDetachedFromWindow();
- }
- if (view.mParent != null) {
- view.mParent = null;
+ if (mVisibilityChangingChildren != null &&
+ mVisibilityChangingChildren.contains(view)) {
+ mVisibilityChangingChildren.remove(view);
+ } else {
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ if (view.mParent != null) {
+ view.mParent = null;
+ }
}
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
}
@@ -4287,290 +4547,57 @@
}
}
- private static class SplitMotionTargets {
- private SparseArray<View> mTargets;
- private TargetInfo[] mUniqueTargets;
- private int mUniqueTargetCount;
- private MotionEvent.PointerCoords[] mPointerCoords;
- private int[] mPointerIds;
+ /* Describes a touched view and the ids of the pointers that it has captured.
+ *
+ * This code assumes that pointer ids are always in the range 0..31 such that
+ * it can use a bitfield to track which pointer ids are present.
+ * As it happens, the lower layers of the input dispatch pipeline also use the
+ * same trick so the assumption should be safe here...
+ */
+ private static final class TouchTarget {
+ private static final int MAX_RECYCLED = 32;
+ private static final Object sRecycleLock = new Object();
+ private static TouchTarget sRecycleBin;
+ private static int sRecycledCount;
- private static final int INITIAL_UNIQUE_MOTION_TARGETS_SIZE = 5;
- private static final int INITIAL_BUCKET_SIZE = 5;
+ public static final int ALL_POINTER_IDS = -1; // all ones
- public SplitMotionTargets() {
- mTargets = new SparseArray<View>();
- mUniqueTargets = new TargetInfo[INITIAL_UNIQUE_MOTION_TARGETS_SIZE];
- mPointerIds = new int[INITIAL_BUCKET_SIZE];
- mPointerCoords = new MotionEvent.PointerCoords[INITIAL_BUCKET_SIZE];
- for (int i = 0; i < INITIAL_BUCKET_SIZE; i++) {
- mPointerCoords[i] = new MotionEvent.PointerCoords();
- }
+ // The touched child view.
+ public View child;
+
+ // The combined bit mask of pointer ids for all pointers captured by the target.
+ public int pointerIdBits;
+
+ // The next target in the target list.
+ public TouchTarget next;
+
+ private TouchTarget() {
}
- public void clear() {
- mTargets.clear();
- final int count = mUniqueTargetCount;
- for (int i = 0; i < count; i++) {
- mUniqueTargets[i].recycle();
- mUniqueTargets[i] = null;
- }
- mUniqueTargetCount = 0;
- }
-
- public void add(int pointerId, View target, long downTime) {
- mTargets.put(pointerId, target);
-
- final int uniqueCount = mUniqueTargetCount;
- boolean addUnique = true;
- for (int i = 0; i < uniqueCount; i++) {
- if (mUniqueTargets[i].view == target) {
- addUnique = false;
- }
- }
- if (addUnique) {
- if (mUniqueTargets.length == uniqueCount) {
- TargetInfo[] newTargets =
- new TargetInfo[uniqueCount + INITIAL_UNIQUE_MOTION_TARGETS_SIZE];
- System.arraycopy(mUniqueTargets, 0, newTargets, 0, uniqueCount);
- mUniqueTargets = newTargets;
- }
- mUniqueTargets[uniqueCount] = TargetInfo.obtain(target, downTime);
- mUniqueTargetCount++;
- }
- }
-
- public int getIdCount() {
- return mTargets.size();
- }
-
- public int getUniqueTargetCount() {
- return mUniqueTargetCount;
- }
-
- public TargetInfo getUniqueTargetAt(int index) {
- return mUniqueTargets[index];
- }
-
- public View get(int id) {
- return mTargets.get(id);
- }
-
- public int indexOfTarget(View target) {
- return mTargets.indexOfValue(target);
- }
-
- public View targetAt(int index) {
- return mTargets.valueAt(index);
- }
-
- public TargetInfo getPrimaryTarget() {
- if (!isEmpty()) {
- // Find the longest-lived target
- long firstTime = Long.MAX_VALUE;
- int firstIndex = 0;
- final int uniqueCount = mUniqueTargetCount;
- for (int i = 0; i < uniqueCount; i++) {
- TargetInfo info = mUniqueTargets[i];
- if (info.downTime < firstTime) {
- firstTime = info.downTime;
- firstIndex = i;
- }
- }
- return mUniqueTargets[firstIndex];
- }
- return null;
- }
-
- public boolean isEmpty() {
- return mUniqueTargetCount == 0;
- }
-
- public void removeById(int id) {
- final int index = mTargets.indexOfKey(id);
- removeAt(index);
- }
-
- public void removeView(View view) {
- int i = 0;
- while (i < mTargets.size()) {
- if (mTargets.valueAt(i) == view) {
- mTargets.removeAt(i);
- } else {
- i++;
- }
- }
- removeUnique(view);
- }
-
- public void removeAt(int index) {
- if (index < 0 || index >= mTargets.size()) {
- return;
- }
-
- final View removeView = mTargets.valueAt(index);
- mTargets.removeAt(index);
- if (mTargets.indexOfValue(removeView) < 0) {
- removeUnique(removeView);
- }
- }
-
- private void removeUnique(View removeView) {
- TargetInfo[] unique = mUniqueTargets;
- int uniqueCount = mUniqueTargetCount;
- for (int i = 0; i < uniqueCount; i++) {
- if (unique[i].view == removeView) {
- unique[i].recycle();
- unique[i] = unique[--uniqueCount];
- unique[uniqueCount] = null;
- break;
- }
- }
-
- mUniqueTargetCount = uniqueCount;
- }
-
- /**
- * Return a new (obtain()ed) MotionEvent containing only data for pointers that should
- * be dispatched to child. Don't forget to recycle it!
- */
- public MotionEvent filterMotionEventForChild(MotionEvent ev, View child, long downTime) {
- int action = ev.getAction();
- final int maskedAction = action & MotionEvent.ACTION_MASK;
-
- // Only send pointer up events if this child was the target. Drop it otherwise.
- if (maskedAction == MotionEvent.ACTION_POINTER_UP &&
- get(ev.getPointerId(ev.getActionIndex())) != child) {
- return null;
- }
-
- int pointerCount = 0;
- final int idCount = getIdCount();
- for (int i = 0; i < idCount; i++) {
- if (targetAt(i) == child) {
- pointerCount++;
- }
- }
-
- int actionId = -1;
- boolean needsNewIndex = false; // True if we should fill in the action's masked index
-
- // If we have a down event, it wasn't counted above.
- if (maskedAction == MotionEvent.ACTION_DOWN) {
- pointerCount++;
- actionId = ev.getPointerId(0);
- } else if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) {
- pointerCount++;
-
- actionId = ev.getPointerId(ev.getActionIndex());
-
- if (indexOfTarget(child) < 0) {
- // The new action should be ACTION_DOWN if this child isn't currently getting
- // any events.
- action = MotionEvent.ACTION_DOWN;
- } else {
- // Fill in the index portion of the action later.
- needsNewIndex = true;
- }
- } else if (maskedAction == MotionEvent.ACTION_POINTER_UP) {
- actionId = ev.getPointerId(ev.getActionIndex());
- if (pointerCount == 1) {
- // The new action should be ACTION_UP if there's only one pointer left for
- // this target.
- action = MotionEvent.ACTION_UP;
- } else {
- // Fill in the index portion of the action later.
- needsNewIndex = true;
- }
- }
-
- if (pointerCount == 0) {
- return null;
- }
-
- // Fill the buckets with pointer data!
- final int eventPointerCount = ev.getPointerCount();
- int bucketIndex = 0;
- int newActionIndex = -1;
- for (int evp = 0; evp < eventPointerCount; evp++) {
- final int id = ev.getPointerId(evp);
-
- // Add this pointer to the bucket if it is new or targeted at child
- if (id == actionId || get(id) == child) {
- // Expand scratch arrays if needed
- if (mPointerCoords.length <= bucketIndex) {
- int[] pointerIds = new int[pointerCount];
- MotionEvent.PointerCoords[] pointerCoords =
- new MotionEvent.PointerCoords[pointerCount];
- for (int i = mPointerCoords.length; i < pointerCoords.length; i++) {
- pointerCoords[i] = new MotionEvent.PointerCoords();
- }
-
- System.arraycopy(mPointerCoords, 0,
- pointerCoords, 0, mPointerCoords.length);
- System.arraycopy(mPointerIds, 0, pointerIds, 0, mPointerIds.length);
-
- mPointerCoords = pointerCoords;
- mPointerIds = pointerIds;
- }
-
- mPointerIds[bucketIndex] = id;
- ev.getPointerCoords(evp, mPointerCoords[bucketIndex]);
-
- if (needsNewIndex && id == actionId) {
- newActionIndex = bucketIndex;
- }
-
- bucketIndex++;
- }
- }
-
- // Encode the new action index if we have one
- if (newActionIndex >= 0) {
- action = (action & MotionEvent.ACTION_MASK) |
- (newActionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
- }
-
- return MotionEvent.obtain(downTime, ev.getEventTime(),
- action, pointerCount, mPointerIds, mPointerCoords, ev.getMetaState(),
- ev.getXPrecision(), ev.getYPrecision(), ev.getDeviceId(), ev.getEdgeFlags(),
- ev.getSource(), ev.getFlags());
- }
-
- static class TargetInfo {
- public View view;
- public long downTime;
-
- private TargetInfo mNextRecycled;
-
- private static TargetInfo sRecycleBin;
- private static int sRecycledCount;
-
- private static int MAX_RECYCLED = 15;
-
- private TargetInfo() {
- }
-
- public static TargetInfo obtain(View v, long time) {
- TargetInfo info;
+ public static TouchTarget obtain(View child, int pointerIdBits) {
+ final TouchTarget target;
+ synchronized (sRecycleLock) {
if (sRecycleBin == null) {
- info = new TargetInfo();
+ target = new TouchTarget();
} else {
- info = sRecycleBin;
- sRecycleBin = info.mNextRecycled;
- sRecycledCount--;
+ target = sRecycleBin;
+ sRecycleBin = target.next;
+ sRecycledCount--;
+ target.next = null;
}
- info.view = v;
- info.downTime = time;
- return info;
}
+ target.child = child;
+ target.pointerIdBits = pointerIdBits;
+ return target;
+ }
- public void recycle() {
- if (sRecycledCount >= MAX_RECYCLED) {
- return;
+ public void recycle() {
+ synchronized (sRecycleLock) {
+ if (sRecycledCount < MAX_RECYCLED) {
+ next = sRecycleBin;
+ sRecycleBin = this;
+ sRecycledCount += 1;
}
- mNextRecycled = sRecycleBin;
- sRecycleBin = this;
- sRecycledCount++;
}
}
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 77ba6fe..155122f 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -24,6 +24,8 @@
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.*;
@@ -45,6 +47,8 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.app.ActivityManagerNative;
@@ -198,6 +202,12 @@
final ViewConfiguration mViewConfiguration;
+ /* Drag/drop */
+ ClipDescription mDragDescription;
+ View mCurrentDragView;
+ final PointF mDragPoint = new PointF();
+ final PointF mLastTouchPoint = new PointF();
+
/**
* see {@link #playSoundEffect(int)}
*/
@@ -1670,6 +1680,7 @@
public final static int FINISH_INPUT_CONNECTION = 1012;
public final static int CHECK_FOCUS = 1013;
public final static int CLOSE_SYSTEM_DIALOGS = 1014;
+ public final static int DISPATCH_DRAG_EVENT = 1015;
@Override
public void handleMessage(Message msg) {
@@ -1845,6 +1856,9 @@
mView.onCloseSystemDialogs((String)msg.obj);
}
} break;
+ case DISPATCH_DRAG_EVENT: {
+ handleDragEvent((DragEvent)msg.obj);
+ } break;
}
}
@@ -2012,6 +2026,9 @@
if (MEASURE_LATENCY) {
lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
}
+ // cache for possible drag-initiation
+ mLastTouchPoint.x = event.getRawX();
+ mLastTouchPoint.y = event.getRawY();
handled = mView.dispatchTouchEvent(event);
if (MEASURE_LATENCY) {
lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
@@ -2434,6 +2451,92 @@
}
}
+ /* drag/drop */
+ private void handleDragEvent(DragEvent event) {
+ // From the root, only drag start/end/location are dispatched. entered/exited
+ // are determined and dispatched by the viewgroup hierarchy, who then report
+ // that back here for ultimate reporting back to the framework.
+ if (mView != null && mAdded) {
+ final int what = event.mAction;
+
+ if (what == DragEvent.ACTION_DRAG_EXITED) {
+ // A direct EXITED event means that the window manager knows we've just crossed
+ // a window boundary, so the current drag target within this one must have
+ // just been exited. Send it the usual notifications and then we're done
+ // for now.
+ setDragFocus(event, null);
+ } else {
+ // Cache the drag description when the operation starts, then fill it in
+ // on subsequent calls as a convenience
+ if (what == DragEvent.ACTION_DRAG_STARTED) {
+ mDragDescription = event.mClipDescription;
+ } else {
+ event.mClipDescription = mDragDescription;
+ }
+
+ // For events with a [screen] location, translate into window coordinates
+ if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
+ mDragPoint.set(event.mX, event.mY);
+ if (mTranslator != null) {
+ mTranslator.translatePointInScreenToAppWindow(mDragPoint);
+ }
+
+ if (mCurScrollY != 0) {
+ mDragPoint.offset(0, mCurScrollY);
+ }
+
+ event.mX = mDragPoint.x;
+ event.mY = mDragPoint.y;
+ }
+
+ // Remember who the current drag target is pre-dispatch
+ final View prevDragView = mCurrentDragView;
+
+ // Now dispatch the drag/drop event
+ mView.dispatchDragEvent(event);
+
+ // If we changed apparent drag target, tell the OS about it
+ if (prevDragView != mCurrentDragView) {
+ try {
+ if (prevDragView != null) {
+ sWindowSession.dragRecipientExited(mWindow);
+ }
+ if (mCurrentDragView != null) {
+ sWindowSession.dragRecipientEntered(mWindow);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to note drag target change");
+ }
+ mCurrentDragView = prevDragView;
+ }
+ }
+ }
+ event.recycle();
+ }
+
+ public void getLastTouchPoint(Point outLocation) {
+ outLocation.x = (int) mLastTouchPoint.x;
+ outLocation.y = (int) mLastTouchPoint.y;
+ }
+
+ public void setDragFocus(DragEvent event, View newDragTarget) {
+ final int action = event.mAction;
+ // If we've dragged off of a view, send it the EXITED message
+ if (mCurrentDragView != newDragTarget) {
+ if (mCurrentDragView != null) {
+ event.mAction = DragEvent.ACTION_DRAG_EXITED;
+ mCurrentDragView.dispatchDragEvent(event);
+ }
+ }
+ // If we've dragged over a new view, send it the ENTERED message
+ if (newDragTarget != null) {
+ event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+ newDragTarget.dispatchDragEvent(event);
+ }
+ mCurrentDragView = newDragTarget;
+ event.mAction = action; // restore the event's original state
+ }
+
private AudioManager getAudioManager() {
if (mView == null) {
throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -2725,7 +2828,12 @@
msg.obj = reason;
sendMessage(msg);
}
-
+
+ public void dispatchDragEvent(DragEvent event) {
+ Message msg = obtainMessage(DISPATCH_DRAG_EVENT, event);
+ sendMessage(msg);
+ }
+
/**
* The window is getting focus so if there is anything focused/selected
* send an {@link AccessibilityEvent} to announce that.
@@ -2936,6 +3044,14 @@
}
}
}
+
+ /* Drag/drop */
+ public void dispatchDragEvent(DragEvent event) {
+ final ViewRoot viewRoot = mViewRoot.get();
+ if (viewRoot != null) {
+ viewRoot.dispatchDragEvent(event);
+ }
+ }
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dc3b44d..eddd04e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -347,6 +347,13 @@
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
/**
+ * Window type: the drag-and-drop pseudowindow. There is only one
+ * drag layer (at most), and it is placed on top of all other windows.
+ * @hide
+ */
+ public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+15;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 76701a9..954b3e7 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -73,6 +73,7 @@
public final static int FLAG_ALT_GR = 0x00000020;
public final static int FLAG_MENU = 0x00000040;
public final static int FLAG_LAUNCHER = 0x00000080;
+ public final static int FLAG_VIRTUAL = 0x00000100;
public final static int FLAG_INJECTED = 0x01000000;
@@ -261,24 +262,6 @@
boolean isDisplayedLw();
/**
- * Returns true if the window is both full screen and opaque. Must be
- * called with the window manager lock held.
- *
- * @param width The width of the screen
- * @param height The height of the screen
- * @param shownFrame If true, this is based on the actual shown frame of
- * the window (taking into account animations); if
- * false, this is based on the currently requested
- * frame, which any current animation will be moving
- * towards.
- * @param onlyOpaque If true, this will only pass if the window is
- * also opaque.
- * @return Returns true if the window is both full screen and opaque
- */
- public boolean fillsScreenLw(int width, int height, boolean shownFrame,
- boolean onlyOpaque);
-
- /**
* Returns true if this window has been shown on screen at some time in
* the past. Must be called with the window manager lock held.
*
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 20aafbd..7b9af5b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1081,6 +1081,7 @@
if (mBlockNetworkLoads != flag) {
mBlockNetworkLoads = flag;
verifyNetworkAccess();
+ postSync();
}
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 7944807..fedb873 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1291,6 +1291,7 @@
private void clearHelpers() {
clearTextEntry();
clearActionModes();
+ dismissFullScreenMode();
}
/**
@@ -4911,6 +4912,13 @@
return mFullScreenHolder != null;
}
+ private void dismissFullScreenMode() {
+ if (inFullScreenMode()) {
+ mFullScreenHolder.dismiss();
+ mFullScreenHolder = null;
+ }
+ }
+
void onPinchToZoomAnimationStart() {
// cancel the single touch handling
cancelTouch();
@@ -6878,9 +6886,9 @@
View view = (View) msg.obj;
int npp = msg.arg1;
- if (mFullScreenHolder != null) {
+ if (inFullScreenMode()) {
Log.w(LOGTAG, "Should not have another full screen.");
- mFullScreenHolder.dismiss();
+ dismissFullScreenMode();
}
mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
mFullScreenHolder.setContentView(view);
@@ -6891,10 +6899,7 @@
break;
}
case HIDE_FULLSCREEN:
- if (inFullScreenMode()) {
- mFullScreenHolder.dismiss();
- mFullScreenHolder = null;
- }
+ dismissFullScreenMode();
break;
case DOM_FOCUS_CHANGED:
diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java
index f2b3e2a..9b6c5a4 100644
--- a/core/java/android/widget/Adapter.java
+++ b/core/java/android/widget/Adapter.java
@@ -72,7 +72,7 @@
long getItemId(int position);
/**
- * Indicated whether the item ids are stable across changes to the
+ * Indicates whether the item ids are stable across changes to the
* underlying data.
*
* @return True if the same id always refers to the same object.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index ab75420..f5afb94 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -172,7 +172,7 @@
int mItemCount;
/**
- * The number of items in the adapter before a data changed event occured.
+ * The number of items in the adapter before a data changed event occurred.
*/
int mOldItemCount;
@@ -557,7 +557,7 @@
/**
* @return The number of items owned by the Adapter associated with this
* AdapterView. (This is the number of data items, which may be
- * larger than the number of visible view.)
+ * larger than the number of visible views.)
*/
@ViewDebug.CapturedViewProperty
public int getCount() {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index f245933..1d1e601 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -17,7 +17,9 @@
package android.widget;
import java.util.ArrayList;
+import java.util.HashMap;
+import android.animation.AnimatorInflater;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +42,7 @@
* @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
* @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
* @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
+ * @attr ref android.R.styleable#AdapterViewAnimator_loopViews
*/
public abstract class AdapterViewAnimator extends AdapterView<Adapter>
implements RemoteViewsAdapter.RemoteAdapterConnectionCallback {
@@ -69,15 +72,14 @@
int mNumActiveViews = 1;
/**
- * Array of the children of the {@link AdapterViewAnimator}. This array
- * is accessed in a circular fashion
+ * Map of the children of the {@link AdapterViewAnimator}.
*/
- View[] mActiveViews;
+ private HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
/**
* List of views pending removal from the {@link AdapterViewAnimator}
*/
- ArrayList<View> mPreviousViews;
+ ArrayList<Integer> mPreviousViews;
/**
* The index, relative to the adapter, of the beginning of the window of views
@@ -124,7 +126,7 @@
* Specifies if the animator should wrap from 0 to the end and vice versa
* or have hard boundaries at the beginning and end
*/
- boolean mShouldLoop = true;
+ boolean mLoopViews = true;
/**
* The width and height of some child, used as a size reference in-case our
@@ -134,12 +136,15 @@
int mReferenceChildHeight = -1;
/**
- * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
+ * In and out animations.
*/
- Animation mInAnimation;
- Animation mOutAnimation;
+ ObjectAnimator<?> mInAnimation;
+ ObjectAnimator<?> mOutAnimation;
+
private ArrayList<View> mViewsToBringToFront;
+ private static final int DEFAULT_ANIMATION_DURATION = 200;
+
public AdapterViewAnimator(Context context) {
super(context);
initViewAnimator();
@@ -149,22 +154,29 @@
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ViewAnimator);
+ com.android.internal.R.styleable.AdapterViewAnimator);
int resource = a.getResourceId(
- com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
+ com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
setInAnimation(context, resource);
+ } else {
+ setInAnimation(getDefaultInAnimation());
}
- resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
+ resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
if (resource > 0) {
setOutAnimation(context, resource);
+ } else {
+ setOutAnimation(getDefaultOutAnimation());
}
boolean flag = a.getBoolean(
- com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
+ com.android.internal.R.styleable.AdapterViewAnimator_animateFirstView, true);
setAnimateFirstView(flag);
+ mLoopViews = a.getBoolean(
+ com.android.internal.R.styleable.AdapterViewAnimator_loopViews, false);
+
a.recycle();
initViewAnimator();
@@ -175,11 +187,19 @@
*/
private void initViewAnimator() {
mMainQueue = new Handler(Looper.myLooper());
- mActiveViews = new View[mNumActiveViews];
- mPreviousViews = new ArrayList<View>();
+ mPreviousViews = new ArrayList<Integer>();
mViewsToBringToFront = new ArrayList<View>();
}
+ private class ViewAndIndex {
+ ViewAndIndex(View v, int i) {
+ view = v;
+ index = i;
+ }
+ View view;
+ int index;
+ }
+
/**
* This method is used by subclasses to configure the animator to display the
* desired number of views, and specify the offset
@@ -193,18 +213,17 @@
* @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we
* we loop back to the end, or do we do nothing
*/
- void configureViewAnimator(int numVisibleViews, int activeOffset, boolean shouldLoop) {
+ void configureViewAnimator(int numVisibleViews, int activeOffset) {
if (activeOffset > numVisibleViews - 1) {
// Throw an exception here.
}
mNumActiveViews = numVisibleViews;
mActiveOffset = activeOffset;
- mActiveViews = new View[mNumActiveViews];
mPreviousViews.clear();
+ mViewsMap.clear();
removeAllViewsInLayout();
mCurrentWindowStart = 0;
mCurrentWindowEnd = -1;
- mShouldLoop = shouldLoop;
}
/**
@@ -218,17 +237,23 @@
* @param view The view that is being animated
*/
void animateViewForTransition(int fromIndex, int toIndex, View view) {
- ObjectAnimator pa;
if (fromIndex == -1) {
- view.setAlpha(0.0f);
- pa = new ObjectAnimator(400, view, "alpha", 0.0f, 1.0f);
- pa.start();
+ mInAnimation.setTarget(view);
+ mInAnimation.start();
} else if (toIndex == -1) {
- pa = new ObjectAnimator(400, view, "alpha", 1.0f, 0.0f);
- pa.start();
+ mOutAnimation.setTarget(view);
+ mOutAnimation.start();
}
}
+ ObjectAnimator<?> getDefaultInAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 0.0f, 1.0f);
+ }
+
+ ObjectAnimator<?> getDefaultOutAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 1.0f, 0.0f);
+ }
+
/**
* Sets which child view will be displayed.
*
@@ -238,9 +263,9 @@
if (mAdapter != null) {
mWhichChild = whichChild;
if (whichChild >= mAdapter.getCount()) {
- mWhichChild = mShouldLoop ? 0 : mAdapter.getCount() - 1;
+ mWhichChild = mLoopViews ? 0 : mAdapter.getCount() - 1;
} else if (whichChild < 0) {
- mWhichChild = mShouldLoop ? mAdapter.getCount() - 1 : 0;
+ mWhichChild = mLoopViews ? mAdapter.getCount() - 1 : 0;
}
boolean hasFocus = getFocusedChild() != null;
@@ -254,20 +279,6 @@
}
/**
- * Return default inAnimation. To be overriden by subclasses.
- */
- Animation getDefaultInAnimation() {
- return null;
- }
-
- /**
- * Return default outAnimation. To be overridden by subclasses.
- */
- Animation getDefaultOutAnimation() {
- return null;
- }
-
- /**
* To be overridden by subclasses. This method applies a view / index specific
* transform to the child view.
*
@@ -323,9 +334,10 @@
* @return View at this index, null if the index is outside the bounds
*/
View getViewAtRelativeIndex(int relativeIndex) {
- if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
- int index = mCurrentWindowStartUnbounded + relativeIndex;
- return mActiveViews[modulo(index, mNumActiveViews)];
+ if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1 && mAdapter != null) {
+ int adapterCount = mAdapter.getCount();
+ int i = modulo(mCurrentWindowStartUnbounded + relativeIndex, adapterCount);
+ return mViewsMap.get(i).view;
}
return null;
}
@@ -339,15 +351,15 @@
return new ViewGroup.LayoutParams(0, 0);
}
- private void refreshChildren() {
+ void refreshChildren() {
for (int i = mCurrentWindowStart; i <= mCurrentWindowEnd; i++) {
int index = modulo(i, mNumActiveViews);
// get the fresh child from the adapter
View updatedChild = mAdapter.getView(i, null, this);
- if (mActiveViews[index] != null) {
- FrameLayout fl = (FrameLayout) mActiveViews[index];
+ if (mViewsMap.containsKey(index)) {
+ FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
// flush out the old child
fl.removeAllViewsInLayout();
// add the new child to the frame, if it exists
@@ -373,7 +385,8 @@
if (mAdapter == null) return;
for (int i = 0; i < mPreviousViews.size(); i++) {
- View viewToRemove = mPreviousViews.get(i);
+ View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
+ mViewsMap.remove(mPreviousViews.get(i));
viewToRemove.clearAnimation();
if (viewToRemove instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) viewToRemove;
@@ -386,64 +399,74 @@
removeViewInLayout(viewToRemove);
}
mPreviousViews.clear();
+ int adapterCount = mAdapter.getCount();
int newWindowStartUnbounded = childIndex - mActiveOffset;
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
int newWindowStart = Math.max(0, newWindowStartUnbounded);
- int newWindowEnd = Math.min(mAdapter.getCount() - 1, newWindowEndUnbounded);
+ int newWindowEnd = Math.min(adapterCount - 1, newWindowEndUnbounded);
- // This section clears out any items that are in our mActiveViews list
+ if (mLoopViews) {
+ newWindowStart = newWindowStartUnbounded;
+ newWindowEnd = newWindowEndUnbounded;
+ }
+ int rangeStart = modulo(newWindowStart, adapterCount);
+ int rangeEnd = modulo(newWindowEnd, adapterCount);
+
+ boolean wrap = false;
+ if (rangeStart > rangeEnd) {
+ wrap = true;
+ }
+
+ // This section clears out any items that are in our active views list
// but are outside the effective bounds of our window (this is becomes an issue
// at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
// newWindowEndUnbounded > mAdapter.getCount() - 1
- for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
- if (i < newWindowStart || i > newWindowEnd) {
- int index = modulo(i, mNumActiveViews);
- if (mActiveViews[index] != null) {
- View previousView = mActiveViews[index];
- mPreviousViews.add(previousView);
- int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
- mNumActiveViews);
- animateViewForTransition(previousViewRelativeIndex, -1, previousView);
- mActiveViews[index] = null;
- }
+ for (Integer index : mViewsMap.keySet()) {
+ boolean remove = false;
+ if (!wrap && (index < rangeStart || index > rangeEnd)) {
+ remove = true;
+ } else if (wrap && (index > rangeEnd && index < rangeStart)) {
+ remove = true;
+ }
+
+ if (remove) {
+ View previousView = mViewsMap.get(index).view;
+ int oldRelativeIndex = mViewsMap.get(index).index;
+
+ mPreviousViews.add(index);
+ animateViewForTransition(oldRelativeIndex, -1, previousView);
}
}
// If the window has changed
- if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
+ if (!(newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
// Run through the indices in the new range
for (int i = newWindowStart; i <= newWindowEnd; i++) {
- int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
+ int index = modulo(i, adapterCount);
+ int oldRelativeIndex;
+ if (mViewsMap.containsKey(index)) {
+ oldRelativeIndex = mViewsMap.get(index).index;
+ } else {
+ oldRelativeIndex = -1;
+ }
int newRelativeIndex = i - newWindowStartUnbounded;
- int index = modulo(i, mNumActiveViews);
// If this item is in the current window, great, we just need to apply
// the transform for it's new relative position in the window, and animate
// between it's current and new relative positions
- if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
- View view = mActiveViews[index];
+ boolean inOldRange = mViewsMap.containsKey(index) && !mPreviousViews.contains(index);
+
+ if (inOldRange) {
+ View view = mViewsMap.get(index).view;
+ mViewsMap.get(index).index = newRelativeIndex;
applyTransformForChildAtIndex(view, newRelativeIndex);
animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
- // Otherwise this view is new, so first we have to displace the view that's
- // taking the new view's place within our cache (a circular array)
+ // Otherwise this view is new to the window
} else {
- if (mActiveViews[index] != null) {
- View previousView = mActiveViews[index];
- mPreviousViews.add(previousView);
- int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
- mNumActiveViews);
- animateViewForTransition(previousViewRelativeIndex, -1, previousView);
-
- if (mCurrentWindowStart > newWindowStart) {
- mViewsToBringToFront.add(previousView);
- }
- }
-
- // We've cleared a spot for the new view. Get it from the adapter, add it
- // and apply any transform / animation
- View newView = mAdapter.getView(i, null, this);
+ // Get the new view from the adapter, add it and apply any transform / animation
+ View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
// We wrap the new view in a FrameLayout so as to respect the contract
// with the adapter, that is, that we don't modify this view directly
@@ -453,12 +476,12 @@
if (newView != null) {
fl.addView(newView);
}
- mActiveViews[index] = fl;
+ mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
addChild(fl);
applyTransformForChildAtIndex(fl, newRelativeIndex);
animateViewForTransition(-1, newRelativeIndex, fl);
}
- mActiveViews[index].bringToFront();
+ mViewsMap.get(index).view.bringToFront();
}
for (int i = 0; i < mViewsToBringToFront.size(); i++) {
@@ -667,7 +690,7 @@
* @see #setInAnimation(android.view.animation.Animation)
* @see #setInAnimation(android.content.Context, int)
*/
- public Animation getInAnimation() {
+ public ObjectAnimator<?> getInAnimation() {
return mInAnimation;
}
@@ -679,7 +702,7 @@
* @see #getInAnimation()
* @see #setInAnimation(android.content.Context, int)
*/
- public void setInAnimation(Animation inAnimation) {
+ public void setInAnimation(ObjectAnimator<?> inAnimation) {
mInAnimation = inAnimation;
}
@@ -691,7 +714,7 @@
* @see #setOutAnimation(android.view.animation.Animation)
* @see #setOutAnimation(android.content.Context, int)
*/
- public Animation getOutAnimation() {
+ public ObjectAnimator<?> getOutAnimation() {
return mOutAnimation;
}
@@ -703,7 +726,7 @@
* @see #getOutAnimation()
* @see #setOutAnimation(android.content.Context, int)
*/
- public void setOutAnimation(Animation outAnimation) {
+ public void setOutAnimation(ObjectAnimator<?> outAnimation) {
mOutAnimation = outAnimation;
}
@@ -717,7 +740,7 @@
* @see #setInAnimation(android.view.animation.Animation)
*/
public void setInAnimation(Context context, int resourceID) {
- setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setInAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
@@ -730,7 +753,7 @@
* @see #setOutAnimation(android.view.animation.Animation)
*/
public void setOutAnimation(Context context, int resourceID) {
- setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setOutAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 895683d..b09ade7 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.animation.ObjectAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,10 +44,8 @@
private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 10000;
- private static final int DEFAULT_ANIMATION_DURATION = 200;
private int mFlipInterval = DEFAULT_INTERVAL;
- private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
private boolean mAutoStart = false;
private boolean mRunning = false;
@@ -56,7 +55,6 @@
public AdapterViewFlipper(Context context) {
super(context);
- initDefaultAnimations();
}
public AdapterViewFlipper(Context context, AttributeSet attrs) {
@@ -68,20 +66,12 @@
com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
com.android.internal.R.styleable.ViewFlipper_autoStart, false);
- a.recycle();
- initDefaultAnimations();
- }
- private void initDefaultAnimations() {
- // Set the default animations to be fade in/out
- if (mInAnimation == null) {
- mInAnimation = new AlphaAnimation(0.0f, 1.0f);
- mInAnimation.setDuration(mAnimationDuration);
- }
- if (mOutAnimation == null) {
- mOutAnimation = new AlphaAnimation(1.0f, 0.0f);
- mOutAnimation.setDuration(mAnimationDuration);
- }
+ // By default we want the flipper to loop
+ mLoopViews = a.getBoolean(
+ com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true);
+
+ a.recycle();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -194,8 +184,6 @@
super.showPrevious();
}
- /**
-
/**
* Internal method to start or stop dispatching flip {@link Message} based
* on {@link #mRunning} and {@link #mVisible} state.
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 8aed454..a371290 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -114,9 +114,11 @@
for (int i = 0; i < months.length; i++) {
months[i] = String.valueOf(i + 1);
}
+ mMonthPicker.setRange(1, 12);
+ } else {
+ mMonthPicker.setRange(1, 12, months);
}
- mMonthPicker.setRange(1, 12, months);
mMonthPicker.setSpeed(200);
mMonthPicker.setOnChangeListener(new OnChangedListener() {
public void onChanged(NumberPicker picker, int oldVal, int newVal) {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 8bd797b..3d21048 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -185,7 +185,6 @@
/** Drawable to be used as a divider when it is adjacent to any children */
private Drawable mChildDivider;
- private boolean mClipChildDivider;
// Bounds of the indicator to be drawn
private final Rect mIndicatorRect = new Rect();
@@ -379,7 +378,6 @@
*/
public void setChildDivider(Drawable childDivider) {
mChildDivider = childDivider;
- mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
}
@Override
@@ -396,17 +394,8 @@
pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
// These are the cases where we draw the child divider
final Drawable divider = mChildDivider;
- final boolean clip = mClipChildDivider;
- if (!clip) {
- divider.setBounds(bounds);
- } else {
- canvas.save();
- canvas.clipRect(bounds);
- }
+ divider.setBounds(bounds);
divider.draw(canvas);
- if (clip) {
- canvas.restore();
- }
pos.recycle();
return;
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e5a34e8..7c4897a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -107,7 +107,6 @@
private boolean mIsCacheColorOpaque;
private boolean mDividerIsOpaque;
- private boolean mClipDivider;
private boolean mHeaderDividersEnabled;
private boolean mFooterDividersEnabled;
@@ -3057,20 +3056,9 @@
void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
// This widget draws the same divider for all children
final Drawable divider = mDivider;
- final boolean clipDivider = mClipDivider;
- if (!clipDivider) {
- divider.setBounds(bounds);
- } else {
- canvas.save();
- canvas.clipRect(bounds);
- }
-
+ divider.setBounds(bounds);
divider.draw(canvas);
-
- if (clipDivider) {
- canvas.restore();
- }
}
/**
@@ -3091,10 +3079,8 @@
public void setDivider(Drawable divider) {
if (divider != null) {
mDividerHeight = divider.getIntrinsicHeight();
- mClipDivider = divider instanceof ColorDrawable;
} else {
mDividerHeight = 0;
- mClipDivider = false;
}
mDivider = divider;
mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 582d9e4..4482b5b 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.annotation.Widget;
import android.content.Context;
import android.os.Handler;
@@ -26,14 +28,6 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnLongClickListener;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.widget.EditText;
-
-import com.android.internal.R;
/**
* A view for selecting a number
@@ -274,6 +268,12 @@
mEnd = end;
mCurrent = start;
updateView();
+
+ if (displayedValues != null) {
+ // Allow text entry rather than strictly numeric entry.
+ mText.setRawInputType(InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ }
}
/**
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 1bc0612..d6c2db1 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -88,6 +88,7 @@
private boolean mOutsideTouchable = false;
private boolean mClippingEnabled = true;
private boolean mSplitTouchEnabled;
+ private boolean mLayoutInScreen;
private OnTouchListener mTouchInterceptor;
@@ -177,7 +178,10 @@
attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
- mAnimationStyle = a.getResourceId(R.styleable.PopupWindow_windowAnimationStyle, -1);
+
+ final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
+ mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 :
+ animStyle;
// If this is a StateListDrawable, try to find and store the drawable to be
// used when the drop-down is placed above its anchor view, and the one to be
@@ -607,6 +611,29 @@
}
/**
+ * <p>Indicates whether the popup window will be forced into using absolute screen coordinates
+ * for positioning.</p>
+ *
+ * @return true if the window will always be positioned in screen coordinates.
+ * @hide
+ */
+ public boolean isLayoutInScreenEnabled() {
+ return mLayoutInScreen;
+ }
+
+ /**
+ * <p>Allows the popup window to force the flag
+ * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior.
+ * This will cause the popup to be positioned in absolute screen coordinates.</p>
+ *
+ * @param enabled true if the popup should always be positioned in screen coordinates
+ * @hide
+ */
+ public void setLayoutInScreenEnabled(boolean enabled) {
+ mLayoutInScreen = enabled;
+ }
+
+ /**
* <p>Change the width and height measure specs that are given to the
* window manager by the popup. By default these are 0, meaning that
* the current width or height is requested as an explicit size from
@@ -910,7 +937,8 @@
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
if(mIgnoreCheekPress) {
curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
}
@@ -934,6 +962,9 @@
if (mSplitTouchEnabled) {
curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
}
+ if (mLayoutInScreen) {
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ }
return curFlags;
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 27f5ad4..23d6758 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -601,19 +601,6 @@
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
- boolean isDataDirty = false;
-
- // If the data set has changed, then notify the remote factory so that it can
- // update its internals first.
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- isDataDirty = metaData.isDataDirty;
- metaData.isDataDirty = false;
- }
- if (isDataDirty) {
- completeNotifyDataSetChanged();
- }
-
// Get the next index to load
int position = -1;
synchronized (mCache) {
@@ -843,46 +830,43 @@
}
public void notifyDataSetChanged() {
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- // Set flag to calls the remote factory's onDataSetChanged() on the next worker loop
- metaData.isDataDirty = true;
- }
-
- // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
- // a chance to update itself, and return new meta data associated with the new data. After
- // which completeNotifyDataSetChanged() is called.
- }
-
- private void completeNotifyDataSetChanged() {
- // Complete the actual notifyDataSetChanged() call initiated earlier
- if (mServiceConnection.isConnected()) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- try {
- factory.onDataSetChanged();
- } catch (Exception e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
- // Return early to prevent from further being notified (since nothing has changed)
- return;
- }
- }
-
- // Flush the cache so that we can reload new items from the service
- synchronized (mCache) {
- mCache.reset();
- }
-
- // Re-request the new metadata (only after the notification to the factory)
- updateMetaData();
-
- // Propagate the notification back to the base adapter
- mMainQueue.post(new Runnable() {
+ mWorkerQueue.post(new Runnable() {
@Override
public void run() {
- superNotifyDataSetChanged();
+ // Complete the actual notifyDataSetChanged() call initiated earlier
+ if (mServiceConnection.isConnected()) {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+ try {
+ factory.onDataSetChanged();
+ } catch (Exception e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+
+ // Return early to prevent from further being notified (since nothing has
+ // changed)
+ return;
+ }
+ }
+
+ // Flush the cache so that we can reload new items from the service
+ synchronized (mCache) {
+ mCache.reset();
+ }
+
+ // Re-request the new metadata (only after the notification to the factory)
+ updateMetaData();
+
+ // Propagate the notification back to the base adapter
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ superNotifyDataSetChanged();
+ }
+ });
}
});
+
+ // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
+ // a chance to update itself and return new meta data associated with the new data.
}
private void superNotifyDataSetChanged() {
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 56dc5cd..839de7d 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -132,7 +132,7 @@
}
private void initStackView() {
- configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2, false);
+ configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2);
setStaticTransformationsEnabled(true);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
@@ -287,7 +287,6 @@
// framework level support for drawing outside of a parent's bounds.
private void disableParentalClipping() {
if (mAncestorContainingAllChildren != null) {
- Log.v(TAG, "Disabling parental clipping.");
ViewGroup vg = this;
while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
if (vg == mAncestorContainingAllChildren) break;
@@ -363,7 +362,9 @@
if (mAdapter == null) return;
- if (mCurrentWindowStartUnbounded + activeIndex == 0) {
+ if (mLoopViews) {
+ mStackSlider.setMode(StackSlider.NORMAL_MODE);
+ } else if (mCurrentWindowStartUnbounded + activeIndex == 0) {
mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
} else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
activeIndex--;
@@ -776,6 +777,7 @@
mWhichChild = 0;
showOnly(mWhichChild, true, true);
+ refreshChildren();
}
final int childCount = getChildCount();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f4d193f..70c6378 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -64,7 +64,6 @@
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
@@ -1160,7 +1159,7 @@
fixFocusableAndClickableSettings();
- // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+ // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
prepareCursorControllers();
}
@@ -2730,7 +2729,7 @@
sendAfterTextChanged((Editable) text);
}
- // SelectionModifierCursorController depends on canSelectText, which depends on text
+ // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
prepareCursorControllers();
}
@@ -3781,23 +3780,41 @@
showError();
mShowErrorAfterAttach = false;
}
+
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ if (mInsertionPointCursorController != null) {
+ observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ }
+ if (mSelectionModifierCursorController != null) {
+ observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+ }
+ }
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
- final ViewTreeObserver observer = getViewTreeObserver();
- if (observer != null) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
observer.removeOnPreDrawListener(this);
mPreDrawState = PREDRAW_NOT_REGISTERED;
}
+ if (mInsertionPointCursorController != null) {
+ observer.removeOnTouchModeChangeListener(mInsertionPointCursorController);
+ }
+ if (mSelectionModifierCursorController != null) {
+ observer.removeOnTouchModeChangeListener(mSelectionModifierCursorController);
+ }
}
if (mError != null) {
hideError();
}
+
+ hideControllers();
}
@Override
@@ -4165,6 +4182,15 @@
*/
canvas.restore();
+
+ if (mInsertionPointCursorController != null &&
+ mInsertionPointCursorController.isShowing()) {
+ mInsertionPointCursorController.updatePosition();
+ }
+ if (mSelectionModifierCursorController != null &&
+ mSelectionModifierCursorController.isShowing()) {
+ mSelectionModifierCursorController.updatePosition();
+ }
}
@Override
@@ -4788,6 +4814,7 @@
if (mInputMethodState != null) {
mInputMethodState.mExtracting = req;
}
+ hideControllers();
}
/**
@@ -6326,7 +6353,11 @@
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
- hideControllers();
+
+ // Hide the controller if the amount of content changed
+ if (before != after) {
+ hideControllers();
+ }
}
/**
@@ -6635,6 +6666,8 @@
} else {
terminateSelectionActionMode();
}
+
+ mLastTouchOffset = -1;
}
startStopMarquee(focused);
@@ -6668,11 +6701,26 @@
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
+ hideInsertionPointCursorController();
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.hide();
+ }
}
startStopMarquee(hasWindowFocus);
}
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (visibility != VISIBLE) {
+ hideInsertionPointCursorController();
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.hide();
+ }
+ }
+ }
+
/**
* Use {@link BaseInputConnection#removeComposingSpans
* BaseInputConnection.removeComposingSpans()} to remove any IME composing
@@ -6707,8 +6755,16 @@
if (start >= prevStart && start < prevEnd) {
// Restore previous selection
Selection.setSelection((Spannable)mText, prevStart, prevEnd);
- // Tapping inside the selection displays the cut/copy/paste context menu.
- showContextMenu();
+
+ if (mSelectionModifierCursorController != null &&
+ !mSelectionModifierCursorController.isShowing()) {
+ // If the anchors aren't showing, revive them.
+ mSelectionModifierCursorController.show();
+ } else {
+ // Tapping inside the selection displays the cut/copy/paste context menu
+ // as long as the anchors are already showing.
+ showContextMenu();
+ }
return;
} else {
// Tapping outside stops selection mode, if any
@@ -6718,6 +6774,8 @@
mInsertionPointCursorController.show();
}
}
+ } else if (hasSelection() && mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.show();
}
}
@@ -6742,8 +6800,6 @@
if (hasSelection()) {
startSelectionActionMode();
- } else if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.show();
}
}
}
@@ -6844,7 +6900,7 @@
mInsertionPointCursorController = null;
}
- if (canSelectText() && mLayout != null) {
+ if (textCanBeSelected() && mLayout != null) {
if (mSelectionModifierCursorController == null) {
mSelectionModifierCursorController = new SelectionModifierCursorController();
}
@@ -7064,7 +7120,7 @@
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_A:
- if (canSelectAll()) {
+ if (canSelectText()) {
return onTextContextMenuItem(ID_SELECT_ALL);
}
@@ -7095,11 +7151,11 @@
return super.onKeyShortcut(keyCode, event);
}
- private boolean canSelectAll() {
- return canSelectText() && mText.length() != 0;
+ private boolean canSelectText() {
+ return textCanBeSelected() && mText.length() != 0;
}
- private boolean canSelectText() {
+ private boolean textCanBeSelected() {
// prepareCursorController() relies on this method.
// If you change this condition, make sure prepareCursorController is called anywhere
// the value of this condition might be changed.
@@ -7569,30 +7625,30 @@
boolean atLeastOne = false;
- if (canSelectAll()) {
+ if (canSelectText()) {
menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
- setIcon(com.android.internal.R.drawable.ic_menu_chat_dashboard).
+ setIcon(com.android.internal.R.drawable.ic_menu_select_all).
setAlphabeticShortcut('a');
atLeastOne = true;
}
if (canCut()) {
menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
- setIcon(com.android.internal.R.drawable.ic_menu_compose).
+ setIcon(com.android.internal.R.drawable.ic_menu_cut).
setAlphabeticShortcut('x');
atLeastOne = true;
}
if (canCopy()) {
menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
- setIcon(com.android.internal.R.drawable.ic_menu_attachment).
+ setIcon(com.android.internal.R.drawable.ic_menu_copy).
setAlphabeticShortcut('c');
atLeastOne = true;
}
if (canPaste()) {
menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
- setIcon(com.android.internal.R.drawable.ic_menu_camera).
+ setIcon(com.android.internal.R.drawable.ic_menu_paste).
setAlphabeticShortcut('v');
atLeastOne = true;
}
@@ -7691,7 +7747,7 @@
* It is not used outside of {@link TextView}.
* @hide
*/
- private interface CursorController {
+ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
/**
* Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
* See also {@link #hide()}.
@@ -7732,6 +7788,10 @@
private int mPositionY;
private CursorController mController;
private boolean mIsDragging;
+ private float mOffsetX;
+ private float mOffsetY;
+ private float mHotspotX;
+ private float mHotspotY;
public HandleView(CursorController controller, Drawable handle) {
super(TextView.this.mContext);
@@ -7740,6 +7800,9 @@
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mHotspotX = mDrawable.getIntrinsicWidth() * 0.5f;
+ mHotspotY = -mDrawable.getIntrinsicHeight() * 0.2f;
}
@Override
@@ -7755,7 +7818,7 @@
}
mContainer.setContentView(this);
final int[] coords = mTempCoords;
- TextView.this.getLocationOnScreen(coords);
+ TextView.this.getLocationInWindow(coords);
coords[0] += mPositionX;
coords[1] += mPositionY;
mContainer.showAtLocation(TextView.this, 0, coords[0], coords[1]);
@@ -7777,20 +7840,18 @@
final int compoundPaddingRight = getCompoundPaddingRight();
final TextView hostView = TextView.this;
- final int right = hostView.mRight;
- final int left = hostView.mLeft;
- final int bottom = hostView.mBottom;
- final int top = hostView.mTop;
+ final int left = 0;
+ final int right = hostView.getWidth();
+ final int top = 0;
+ final int bottom = hostView.getHeight();
final int clipLeft = left + compoundPaddingLeft;
final int clipTop = top + extendedPaddingTop;
final int clipRight = right - compoundPaddingRight;
final int clipBottom = bottom - extendedPaddingBottom;
- final int handleWidth = mDrawable.getIntrinsicWidth();
- return mPositionX >= clipLeft - handleWidth * 0.75f &&
- mPositionX <= clipRight + handleWidth * 0.25f &&
- mPositionY >= clipTop && mPositionY <= clipBottom;
+ return mPositionX + mHotspotX >= clipLeft && mPositionX + mHotspotX <= clipRight &&
+ mPositionY + mHotspotY >= clipTop && mPositionY + mHotspotY <= clipBottom;
}
private void moveTo(int x, int y) {
@@ -7799,7 +7860,7 @@
if (isPositionInBounds()) {
if (mContainer.isShowing()){
final int[] coords = mTempCoords;
- TextView.this.getLocationOnScreen(coords);
+ TextView.this.getLocationInWindow(coords);
coords[0] += mPositionX;
coords[1] += mPositionY;
mContainer.update(coords[0], coords[1], mRight - mLeft, mBottom - mTop);
@@ -7827,20 +7888,25 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIsDragging = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_DOWN: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
- final int[] coords = mTempCoords;
- TextView.this.getLocationOnScreen(coords);
- final int x = (int) (rawX - coords[0] + 0.5f);
- final int y = (int) (rawY - coords[1] + 0.5f);
- mController.updatePosition(this, x, y);
+ mOffsetX = rawX - mPositionX;
+ mOffsetY = rawY - mPositionY;
+ mIsDragging = true;
break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ final float rawX = ev.getRawX();
+ final float rawY = ev.getRawY();
+ final float newPosX = rawX - mOffsetX + mHotspotX;
+ final float newPosY = rawY - mOffsetY + mHotspotY;
+ mController.updatePosition(this, (int) Math.round(newPosX),
+ (int) Math.round(newPosY));
+
+ break;
+ }
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
@@ -7936,6 +8002,12 @@
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
+
+ public void onTouchModeChanged(boolean isInTouchMode) {
+ if (!isInTouchMode) {
+ hide();
+ }
+ }
}
private class SelectionModifierCursorController implements CursorController {
@@ -7946,6 +8018,14 @@
// Whether selection anchors are active
private boolean mIsShowing;
+ private static final int DELAY_BEFORE_FADE_OUT = 4100;
+
+ private final Runnable mHider = new Runnable() {
+ public void run() {
+ hide();
+ }
+ };
+
SelectionModifierCursorController() {
Resources res = mContext.getResources();
mStartHandle = new HandleView(this, res.getDrawable(mTextSelectHandleLeftRes));
@@ -7958,12 +8038,19 @@
mStartHandle.show();
mEndHandle.show();
hideInsertionPointCursorController();
+ hideDelayed(DELAY_BEFORE_FADE_OUT);
}
public void hide() {
mStartHandle.hide();
mEndHandle.hide();
mIsShowing = false;
+ removeCallbacks(mHider);
+ }
+
+ private void hideDelayed(int delay) {
+ removeCallbacks(mHider);
+ postDelayed(mHider, delay);
}
public boolean isShowing() {
@@ -8029,6 +8116,7 @@
mLayout.getLineForOffset(selectionEnd);
mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
mEndHandle.positionAtCursor(selectionEnd, true);
+ hideDelayed(DELAY_BEFORE_FADE_OUT);
}
public boolean onTouchEvent(MotionEvent event) {
@@ -8085,6 +8173,12 @@
public boolean isSelectionStartDragged() {
return mStartHandle.isDragging();
}
+
+ public void onTouchModeChanged(boolean isInTouchMode) {
+ if (!isInTouchMode) {
+ hide();
+ }
+ }
}
private void hideInsertionPointCursorController() {
@@ -8146,7 +8240,7 @@
final int previousLine = layout.getLineForOffset(previousOffset);
final int previousLineTop = layout.getLineTop(previousLine);
final int previousLineBottom = layout.getLineBottom(previousLine);
- final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
+ final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6;
// If new line is just before or after previous line and y position is less than
// hysteresisThreshold away from previous line, keep cursor on previous line.
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 6e11cff..2a8cd94 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -180,7 +180,7 @@
Log.d(TAG, "Found gdbserver: " + entry.getName());
}
- final String installGdbServerPath = APK_LIB + GDBSERVER;
+ final String installGdbServerPath = GDBSERVER;
nativeFiles.add(Pair.create(entry, installGdbServerPath));
return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4da74e6..d5213db 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,11 +16,14 @@
package com.android.internal.view;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.view.DragEvent;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.KeyEvent;
@@ -66,7 +69,10 @@
}
}
}
-
+
+ public void dispatchDragEvent(DragEvent event) {
+ }
+
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 4503852..f700791 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -297,9 +297,25 @@
#define kClassPathName "android/graphics/BitmapRegionDecoder"
+static jclass make_globalref(JNIEnv* env, const char classname[]) {
+ jclass c = env->FindClass(classname);
+ SkASSERT(c);
+ return (jclass)env->NewGlobalRef(c);
+}
+
+static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
+ const char fieldname[], const char type[]) {
+ jfieldID id = env->GetFieldID(clazz, fieldname, type);
+ SkASSERT(id);
+ return id;
+}
+
int register_android_graphics_BitmapRegionDecoder(JNIEnv* env);
int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
{
+
+ gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
+ gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
gBitmapRegionDecoderMethods, SK_ARRAY_COUNT(gBitmapRegionDecoderMethods));
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 1ebe14b..9467be8 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -46,7 +46,7 @@
}
void doThrowIOE(JNIEnv* env, const char* msg) {
- doThrow(env, "java/lang/IOException", msg);
+ doThrow(env, "java/io/IOException", msg);
}
bool GraphicsJNI::hasException(JNIEnv *env) {
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index b782766..cafceab 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -27,6 +27,8 @@
#include "SkMatrix.h"
#include "SkTemplates.h"
+#include "Matrix.h"
+
namespace android {
class SkMatrixGlue {
@@ -403,10 +405,20 @@
{"native_equals", "(II)Z", (void*) SkMatrixGlue::equals}
};
+static jfieldID sNativeInstanceField;
+
int register_android_graphics_Matrix(JNIEnv* env) {
int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Matrix", methods,
sizeof(methods) / sizeof(methods[0]));
+
+ jclass clazz = env->FindClass("android/graphics/Matrix");
+ sNativeInstanceField = env->GetFieldID(clazz, "native_instance", "I");
+
return result;
}
+SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) {
+ return reinterpret_cast<SkMatrix*>(env->GetIntField(matrixObj, sNativeInstanceField));
+}
+
}
diff --git a/core/jni/android/graphics/Matrix.h b/core/jni/android/graphics/Matrix.h
new file mode 100644
index 0000000..31edf88
--- /dev/null
+++ b/core/jni/android/graphics/Matrix.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_GRAPHICS_MATRIX_H
+#define _ANDROID_GRAPHICS_MATRIX_H
+
+#include "jni.h"
+#include "SkMatrix.h"
+
+namespace android {
+
+/* Gets the underlying SkMatrix from a Matrix object. */
+extern SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_MATRIX_H
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 05e1ff3..1b68ba9 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -77,7 +77,7 @@
static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
// skip printing this message if it is due to certain types of errors
- if (iErrCode == SQLITE_CONSTRAINT) return;
+ if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) return;
LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg);
}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index cb1556b1..7521af4 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -50,7 +50,20 @@
*/
#ifdef USE_OPENGL_RENDERER
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
#define DEBUG_RENDERER 0
+#define PROFILE_RENDERER 0
+
+// Debug
+#if DEBUG_RENDERER
+ #define RENDERER_LOGD(...) LOGD(__VA_ARGS__)
+#else
+ #define RENDERER_LOGD(...)
+#endif
// ----------------------------------------------------------------------------
// Java APIs
@@ -66,7 +79,8 @@
// ----------------------------------------------------------------------------
static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
-#if DEBUG_RENDERER
+ RENDERER_LOGD("Create OpenGLRenderer");
+#if PROFILE_RENDERER
return new OpenGLDebugRenderer;
#else
return new OpenGLRenderer;
@@ -75,6 +89,7 @@
static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
OpenGLRenderer* renderer) {
+ RENDERER_LOGD("Destroy OpenGLRenderer");
delete renderer;
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 93fd54f..537ac72 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,10 +22,26 @@
#include <utils/Log.h>
#include <ui/Input.h>
#include "android_view_MotionEvent.h"
+#include "android/graphics/Matrix.h"
+
+#include <math.h>
+#include "SkMatrix.h"
+#include "SkScalar.h"
// Number of float items per entry in a DVM sample data array
#define NUM_SAMPLE_DATA 9
+#define SAMPLE_X 0
+#define SAMPLE_Y 1
+#define SAMPLE_PRESSURE 2
+#define SAMPLE_SIZE 3
+#define SAMPLE_TOUCH_MAJOR 4
+#define SAMPLE_TOUCH_MINOR 5
+#define SAMPLE_TOOL_MAJOR 6
+#define SAMPLE_TOOL_MINOR 7
+#define SAMPLE_ORIENTATION 8
+
+
namespace android {
// ----------------------------------------------------------------------------
@@ -238,8 +254,87 @@
}
}
+static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
+ // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+ // Coordinate system: down is increasing Y, right is increasing X.
+ SkPoint vector;
+ vector.fX = SkFloatToScalar(sinf(angleRadians));
+ vector.fY = SkFloatToScalar(- cosf(angleRadians));
+ matrix->mapVectors(& vector, 1);
+
+ // Derive the transformed vector's clockwise angle from vertical.
+ float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(- vector.fY));
+ if (result < - M_PI_2) {
+ result += M_PI;
+ } else if (result > M_PI_2) {
+ result -= M_PI;
+ }
+ return result;
+}
+
+static void android_view_MotionEvent_nativeTransform(JNIEnv* env,
+ jobject eventObj, jobject matrixObj) {
+ SkMatrix* matrix = android_graphics_Matrix_getSkMatrix(env, matrixObj);
+
+ jfloat oldXOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset);
+ jfloat oldYOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset);
+ jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
+ jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
+ jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
+ gMotionEventClassInfo.mDataSamples));
+ jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
+
+ // The tricky part of this implementation is to preserve the value of
+ // rawX and rawY. So we apply the transformation to the first point
+ // then derive an appropriate new X/Y offset that will preserve rawX and rawY.
+ SkPoint point;
+ jfloat rawX = dataSamples[SAMPLE_X];
+ jfloat rawY = dataSamples[SAMPLE_Y];
+ matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
+ & point);
+ jfloat newX = SkScalarToFloat(point.fX);
+ jfloat newY = SkScalarToFloat(point.fY);
+ jfloat newXOffset = newX - rawX;
+ jfloat newYOffset = newY - rawY;
+
+ dataSamples[SAMPLE_ORIENTATION] = transformAngle(matrix, dataSamples[SAMPLE_ORIENTATION]);
+
+ // Apply the transformation to all samples.
+ jfloat* currentDataSample = dataSamples;
+ jfloat* endDataSample = dataSamples + numPointers * numSamples * NUM_SAMPLE_DATA;
+ for (;;) {
+ currentDataSample += NUM_SAMPLE_DATA;
+ if (currentDataSample == endDataSample) {
+ break;
+ }
+
+ jfloat x = currentDataSample[SAMPLE_X] + oldXOffset;
+ jfloat y = currentDataSample[SAMPLE_Y] + oldYOffset;
+ matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), & point);
+ currentDataSample[SAMPLE_X] = SkScalarToFloat(point.fX) - newXOffset;
+ currentDataSample[SAMPLE_Y] = SkScalarToFloat(point.fY) - newYOffset;
+
+ currentDataSample[SAMPLE_ORIENTATION] = transformAngle(matrix,
+ currentDataSample[SAMPLE_ORIENTATION]);
+ }
+
+ env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0);
+
+ env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset, newXOffset);
+ env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset, newYOffset);
+
+ env->DeleteLocalRef(dataSampleArray);
+}
+
// ----------------------------------------------------------------------------
+static JNINativeMethod gMotionEventMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeTransform",
+ "(Landroid/graphics/Matrix;)V",
+ (void*)android_view_MotionEvent_nativeTransform },
+};
+
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className); \
@@ -258,6 +353,10 @@
LOG_FATAL_IF(! var, "Unable to find field " fieldName);
int register_android_view_MotionEvent(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/view/MotionEvent",
+ gMotionEventMethods, NELEM(gMotionEventMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz,
diff --git a/core/res/res/drawable-hdpi/ic_menu_copy.png b/core/res/res/drawable-hdpi/ic_menu_copy.png
new file mode 100644
index 0000000..8f11153
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_cut.png b/core/res/res/drawable-hdpi/ic_menu_cut.png
new file mode 100644
index 0000000..6ad379e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_paste.png b/core/res/res/drawable-hdpi/ic_menu_paste.png
new file mode 100644
index 0000000..5a3850f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_menu_select_all.png b/core/res/res/drawable-hdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..dde6741
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
index 53ed136..abb91cc 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
index 6455790..7c4f40e 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
index 49bb9c1..6dbf925 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
index b3c4c4c..b05a49f 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
index 00dea6ec..109be42 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
index 45b1850..2800cab 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
index 35b3529..51cbfa6 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
index 720de7f..ca51ccc 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
index b3387be..fd98571 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
index 7ddfbcc..723815b 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
index 1855e5f..030c9e9 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
index 844f304..ffc5433 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
index 0cfd09d..75c8162 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
index ee030fbe..aebfa29 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
index 7140957..ed416f1 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
index 1eeabf4..d063229 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_copy.png b/core/res/res/drawable-mdpi/ic_menu_copy.png
new file mode 100644
index 0000000..89d626f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_cut.png b/core/res/res/drawable-mdpi/ic_menu_cut.png
new file mode 100644
index 0000000..1b4733e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_cut.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_paste.png b/core/res/res/drawable-mdpi/ic_menu_paste.png
new file mode 100755
index 0000000..cdf7ca3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_paste.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_menu_select_all.png b/core/res/res/drawable-mdpi/ic_menu_select_all.png
new file mode 100644
index 0000000..37fd3cbd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_menu_select_all.png
Binary files differ
diff --git a/core/res/res/menu/webview_copy.xml b/core/res/res/menu/webview_copy.xml
index 224f54f..adba563 100644
--- a/core/res/res/menu/webview_copy.xml
+++ b/core/res/res/menu/webview_copy.xml
@@ -16,7 +16,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/copy"
- android:icon="@drawable/ic_menu_attachment"
+ android:icon="@drawable/ic_menu_copy"
android:showAsAction="always"
/>
<item android:id="@+id/share"
@@ -24,7 +24,7 @@
android:showAsAction="always"
/>
<item android:id="@+id/select_all"
- android:icon="@drawable/ic_menu_chat_dashboard"
+ android:icon="@drawable/ic_menu_select_all"
android:showAsAction="always"
/>
<item android:id="@+id/find"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index b618756..830fb01 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -71,20 +71,6 @@
<item>@drawable/indicator_code_lock_point_area_default</item>
<item>@drawable/indicator_code_lock_point_area_green</item>
<item>@drawable/indicator_code_lock_point_area_red</item>
- <!-- SlidingTab drawables shared by InCallScreen and LockScreen -->
- <item>@drawable/jog_tab_bar_left_end_confirm_gray</item>
- <item>@drawable/jog_tab_bar_left_end_normal</item>
- <item>@drawable/jog_tab_bar_left_end_pressed</item>
- <item>@drawable/jog_tab_bar_right_end_confirm_gray</item>
- <item>@drawable/jog_tab_bar_right_end_normal</item>
- <item>@drawable/jog_tab_bar_right_end_pressed</item>
- <item>@drawable/jog_tab_left_confirm_gray</item>
- <item>@drawable/jog_tab_left_normal</item>
- <item>@drawable/jog_tab_left_pressed</item>
- <item>@drawable/jog_tab_right_confirm_gray</item>
- <item>@drawable/jog_tab_right_normal</item>
- <item>@drawable/jog_tab_right_pressed</item>
- <item>@drawable/jog_tab_target_gray</item>
</array>
<!-- Do not translate. These are all of the color state list resources that should be
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 317b3f3..8e2b762 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2363,7 +2363,7 @@
</declare-styleable>
<declare-styleable name="PopupWindow">
<attr name="popupBackground" format="reference|color" />
- <attr name="windowAnimationStyle" />
+ <attr name="popupAnimationStyle" format="reference" />
</declare-styleable>
<declare-styleable name="ViewAnimator">
<!-- Identifier for the animation to use when a view is shown. -->
@@ -2384,8 +2384,11 @@
<attr name="inAnimation" />
<!-- Identifier for the animation to use when a view is hidden. -->
<attr name="outAnimation" />
+ <!--Defines whether the animator loops to the first view once it
+ has reached the end of the list. -->
+ <attr name="loopViews" format="boolean" />
<!-- Defines whether to animate the current View when the ViewAnimation
- is first displayed. -->
+ is first displayed. -->
<attr name="animateFirstView" />
</declare-styleable>
<declare-styleable name="AdapterViewFlipper">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f704874..71967d4a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -82,6 +82,15 @@
<!-- Set to true if external storage is case sensitive.
Typically external storage is FAT, which is case insensitive. -->
<bool name="config_caseSensitiveExternalStorage">false</bool>
+
+ <!-- A product with no SD card == not removable. -->
+ <bool name="config_externalStorageRemovable" product="nosdcard">false</bool>
+ <!-- Configures whether the primary external storage device is
+ removable. For example, if external storage is on an SD card,
+ it is removable; if it is built in to the device, it is not removable.
+ The default product has external storage on an SD card, which is
+ removable. -->
+ <bool name="config_externalStorageRemovable" product="default">true</bool>
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index adcbb10..35f8df5 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1256,6 +1256,7 @@
<public type="attr" name="textSelectHandleRight" id="0x010102c6" />
<public type="attr" name="textSelectHandle" id="0x010102c7" />
<public type="attr" name="textSelectHandleWindowStyle" id="0x010102c8" />
+ <public type="attr" name="popupAnimationStyle" id="0x010102c9" />
<public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
@@ -1360,6 +1361,7 @@
<public type="attr" name="breadCrumbShortTitle" />
<public type="attr" name="listDividerAlertDialog" />
<public type="attr" name="textColorAlertDialogListItem" />
+ <public type="attr" name="loopViews" />
<public type="anim" name="animator_fade_in" />
<public type="anim" name="animator_fade_out" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
old mode 100644
new mode 100755
index 7a9b59a..8b4f91f
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -387,8 +387,10 @@
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_storage">Storage</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+ <string name="permgroupdesc_storage" product="nosdcard">Access the shared storage.</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgroupdesc_storage">Access the SD card.</string>
+ <string name="permgroupdesc_storage" product="default">Access the SD card.</string>
<!-- Permissions -->
@@ -1230,10 +1232,14 @@
<string name="permdesc_writeDictionary">Allows an application to write new words into the
user dictionary.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+ <string name="permlab_sdcardWrite" product="nosdcard">modify/delete shared storage contents</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_sdcardWrite">modify/delete SD card contents</string>
+ <string name="permlab_sdcardWrite" product="default">modify/delete SD card contents</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+ <string name="permdesc_sdcardWrite" product="nosdcard">Allows an application to write to the shared storage.</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_sdcardWrite">Allows an application to write to the SD card.</string>
+ <string name="permdesc_sdcardWrite" product="default">Allows an application to write to the SD card.</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_cache_filesystem">access the cache filesystem</string>
@@ -1243,31 +1249,29 @@
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
- <string name="policylab_limitPassword">Limit password</string>
+ <string name="policylab_limitPassword">Set password rules</string>
<!-- Description of policy access to limiting the user's password choices -->
- <string name="policydesc_limitPassword">Restrict the types of passwords you
- are allowed to use.</string>
+ <string name="policydesc_limitPassword">Control the length and the characters
+ allowed in screen-unlock passwords</string>
<!-- Title of policy access to watch user login attempts -->
- <string name="policylab_watchLogin">Watch login attempts</string>
+ <string name="policylab_watchLogin">Monitor screen-unlock attempts</string>
<!-- Description of policy access to watch user login attempts -->
- <string name="policydesc_watchLogin">Monitor failed attempts to login to
- the device, to perform some action.</string>
+ <string name="policydesc_watchLogin">Monitor the number of incorrect passwords
+ entered when unlocking the screen, and lock the phone or erase all the phone\'s
+ data if too many incorrect passwords are entered</string>
<!-- Title of policy access to reset user's password -->
- <string name="policylab_resetPassword">Reset password</string>
+ <string name="policylab_resetPassword">Change the screen-unlock password</string>
<!-- Description of policy access to reset user's password -->
- <string name="policydesc_resetPassword">Force your password
- to a new value, requiring the administrator give it to you
- before you can log in.</string>
+ <string name="policydesc_resetPassword">Change the screen-unlock password</string>
<!-- Title of policy access to force lock the device -->
- <string name="policylab_forceLock">Force lock</string>
+ <string name="policylab_forceLock">Lock the screen</string>
<!-- Description of policy access to limiting the user's password choices -->
- <string name="policydesc_forceLock">Control when device locks,
- requiring you re-enter its password.</string>
+ <string name="policydesc_forceLock">Control how and when the screen locks</string>
<!-- Title of policy access to wipe the user's data -->
<string name="policylab_wipeData">Erase all data</string>
<!-- Description of policy access to wipe the user's data -->
- <string name="policydesc_wipeData">Perform a factory reset, deleting
- all of your data without any confirmation.</string>
+ <string name="policydesc_wipeData">Erase the phone\'s data without warning,
+ by performing a factory data reset</string>
<string name="policylab_setGlobalProxy">Set the device global proxy</string>
<!-- Description of policy access to wipe the user's data -->
<string name="policydesc_setGlobalProxy">Set the device global proxy
@@ -2115,12 +2119,16 @@
<!-- See USB_STORAGE. USB_STORAGE_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to mount. This is the title. -->
<string name="usb_storage_title">USB connected</string>
+ <!-- See USB_STORAGE. This is the message. [CHAR LIMIT=NONE] -->
+ <string name="usb_storage_message" product="nosdcard">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s shared storage.</string>
<!-- See USB_STORAGE. This is the message. -->
- <string name="usb_storage_message">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string>
+ <string name="usb_storage_message" product="default">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string>
<!-- See USB_STORAGE. This is the button text to mount the phone on the computer. -->
<string name="usb_storage_button_mount">Turn on USB storage</string>
+ <!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. [CHAR LIMIT=NONE] -->
+ <string name="usb_storage_error_message" product="nosdcard">There is a problem using your shared storage for USB storage.</string>
<!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. -->
- <string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
+ <string name="usb_storage_error_message" product="default">There is a problem using your SD card for USB storage.</string>
<!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title -->
<string name="usb_storage_notification_title">USB connected</string>
<!-- See USB_STORAGE. This is the message. -->
@@ -2135,8 +2143,10 @@
<!-- This is the label for the activity, and should never be visible to the user. -->
<!-- See USB_STORAGE_STOP. USB_STORAGE_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop usb storage. This is the title. -->
<string name="usb_storage_stop_title">USB storage in use</string>
+ <!-- See USB_STORAGE_STOP. This is the message. [CHAR LIMIT=NONE] -->
+ <string name="usb_storage_stop_message" product="nosdcard">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s shared storage from your computer.</string>
<!-- See USB_STORAGE_STOP. This is the message. -->
- <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string>
+ <string name="usb_storage_stop_message" product="default">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string>
<!-- See USB_STORAGE_STOP. This is the button text to stop usb storage. -->
<string name="usb_storage_stop_button_mount">Turn off USB storage</string>
<!-- See USB_STORAGE_STOP_DIALOG. If there was an error stopping, this is the text. -->
@@ -2153,10 +2163,14 @@
<!-- External media format dialog strings -->
<!-- This is the label for the activity, and should never be visible to the user. -->
+ <!-- See EXTMEDIA_FORMAT. EXTMEDIA_FORMAT_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to format the SD card. This is the title. [CHAR LIMIT=20] -->
+ <string name="extmedia_format_title" product="nosdcard">Format shared storage</string>
<!-- See EXTMEDIA_FORMAT. EXTMEDIA_FORMAT_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to format the SD card. This is the title. -->
- <string name="extmedia_format_title">Format SD card</string>
+ <string name="extmedia_format_title" product="default">Format SD card</string>
+ <!-- See EXTMEDIA_FORMAT. This is the message. [CHAR LIMIT=NONE] -->
+ <string name="extmedia_format_message" product="nosdcard">Format shared storage, erasing all files stored there? Action cannot be reversed!</string>
<!-- See EXTMEDIA_FORMAT. This is the message. -->
- <string name="extmedia_format_message">Are you sure you want to format the SD card? All data on your card will be lost.</string>
+ <string name="extmedia_format_message" product="default">Are you sure you want to format the SD card? All data on your card will be lost.</string>
<!-- See EXTMEDIA_FORMAT. This is the button text to format the sd card. -->
<string name="extmedia_format_button_format">Format</string>
@@ -2181,29 +2195,51 @@
<string name="candidates_style"><u>candidates</u></string>
<!-- External media notification strings -->
+ <!-- Shown when external media is being checked [CHAR LIMIT=30] -->
+ <string name="ext_media_checking_notification_title" product="nosdcard">Preparing shared storage</string>
<!-- Shown when external media is being checked -->
- <string name="ext_media_checking_notification_title">Preparing SD card</string>
+ <string name="ext_media_checking_notification_title" product="default">Preparing SD card</string>
<string name="ext_media_checking_notification_message">Checking for errors.</string>
+ <!-- Shown when external media is blank (or unsupported filesystem) [CHAR LIMIT=30] -->
+ <string name="ext_media_nofs_notification_title" product="nosdcard">Blank shared storage</string>
<!-- Shown when external media is blank (or unsupported filesystem) -->
- <string name="ext_media_nofs_notification_title">Blank SD card</string>
- <string name="ext_media_nofs_notification_message">SD card blank or has unsupported filesystem.</string>
+ <string name="ext_media_nofs_notification_title" product="default">Blank SD card</string>
+ <!-- Shown when shared storage cannot be read. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_nofs_notification_message" product="nosdcard">Shared storage blank or has unsupported filesystem.</string>
+ <string name="ext_media_nofs_notification_message" product="default">SD card blank or has unsupported filesystem.</string>
+ <!-- Shown when external media is unmountable (corrupt)) [CHAR LIMIT=30] -->
+ <string name="ext_media_unmountable_notification_title" product="nosdcard">Damaged shared storage</string>
<!-- Shown when external media is unmountable (corrupt)) -->
- <string name="ext_media_unmountable_notification_title">Damaged SD card</string>
- <string name="ext_media_unmountable_notification_message">SD card damaged. You may have to reformat it.</string>
+ <string name="ext_media_unmountable_notification_title" product="default">Damaged SD card</string>
+ <!-- Shown when shared storage cannot be read. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_unmountable_notification_message" product="nosdcard">Shared storage damaged. You may have to reformat it.</string>
+ <string name="ext_media_unmountable_notification_message" product="default">SD card damaged. You may have to reformat it.</string>
+ <!-- Shown when external media is unsafely removed [CHAR LIMIT=30] -->
+ <string name="ext_media_badremoval_notification_title" product="nosdcard">Shared storage unexpectedly removed</string>
<!-- Shown when external media is unsafely removed -->
- <string name="ext_media_badremoval_notification_title">SD card unexpectedly removed</string>
- <string name="ext_media_badremoval_notification_message">Unmount SD card before removing to avoid data loss.</string>
+ <string name="ext_media_badremoval_notification_title" product="default">SD card unexpectedly removed</string>
+ <!-- Shown when external media is unsafely removed. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_badremoval_notification_message" product="nosdcard">Unmount shared storage before removing to avoid data loss.</string>
+ <string name="ext_media_badremoval_notification_message" product="default">Unmount SD card before removing to avoid data loss.</string>
+ <!-- Shown when external media has been safely removed [CHAR LIMIT=30] -->
+ <string name="ext_media_safe_unmount_notification_title" product="nosdcard">Shared storage safe to remove</string>
<!-- Shown when external media has been safely removed -->
- <string name="ext_media_safe_unmount_notification_title">SD card safe to remove</string>
- <string name="ext_media_safe_unmount_notification_message">You can safely remove SD card.</string>
+ <string name="ext_media_safe_unmount_notification_title" product="default">SD card safe to remove</string>
+ <!-- Shown when external media has been safely removed. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_safe_unmount_notification_message" product="nosdcard">You can safely remove shared storage.</string>
+ <string name="ext_media_safe_unmount_notification_message" product="default">You can safely remove SD card.</string>
+ <!-- Shown when external media is missing [CHAR LIMIT=30] -->
+ <string name="ext_media_nomedia_notification_title" product="nosdcard">Removed shared storage</string>
<!-- Shown when external media is missing -->
- <string name="ext_media_nomedia_notification_title">Removed SD card</string>
- <string name="ext_media_nomedia_notification_message">SD card removed. Insert a new one.</string>
+ <string name="ext_media_nomedia_notification_title" product="default">Removed SD card</string>
+ <!-- Shown when external media is missing. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_nomedia_notification_message" product="nosdcard">Shared storage removed. Insert new media.</string>
+ <string name="ext_media_nomedia_notification_message" product="default">SD card removed. Insert a new one.</string>
<!-- Shown in LauncherActivity when the requested target Intent didn't return any matching Activities, leaving the list empty. -->
<string name="activity_list_empty">No matching activities found</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 6e9530c..3dfaf7f 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -196,6 +196,9 @@
<item name="windowExitAnimation">@anim/fade_out</item>
</style>
+ <!-- A special animation value used internally for popup windows. -->
+ <style name="Animation.PopupWindow" />
+
<!-- Status Bar Styles -->
<style name="TextAppearance.StatusBar">
@@ -564,6 +567,7 @@
<style name="Widget.PopupWindow">
<item name="android:popupBackground">@android:drawable/editbox_dropdown_background_dark</item>
+ <item name="android:popupAnimationStyle">@android:style/Animation.PopupWindow</item>
</style>
<!-- Default style for {@link android.app.FragmentBreadCrumbs} view. -->
@@ -587,7 +591,7 @@
</style>
<style name="Widget.QuickContactBadge">
- <item name="android:layout_width">50dip</item>
+ <item name="android:layout_width">47.33333dip</item>
<item name="android:layout_height">56dip</item>
<item name="android:background">@android:drawable/quickcontact_badge</item>
<item name="android:clickable">true</item>
@@ -883,7 +887,7 @@
<!-- Style for the small popup windows that contain text selection anchors. -->
<style name="Widget.TextSelectHandle">
- <item name="android:windowAnimationStyle">@android:style/Animation.TextSelectHandle</item>
+ <item name="android:popupAnimationStyle">@android:style/Animation.TextSelectHandle</item>
</style>
<!-- Style for animating text selection handles. -->
diff --git a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
index 38f336e..27eea4d 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
@@ -120,7 +120,7 @@
Cursor cursor = getCursor(dlRequest);
try {
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, error);
+ verifyInt(cursor, DownloadManager.COLUMN_REASON, error);
} finally {
cursor.close();
}
@@ -183,7 +183,7 @@
Cursor cursor = getCursor(dlRequest);
try {
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_CANNOT_RESUME);
} finally {
cursor.close();
@@ -269,7 +269,7 @@
try {
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_FILE_ERROR);
} finally {
cursor.close();
@@ -476,7 +476,7 @@
// For the last download we should have failed b/c there is not enough space left in cache
Cursor cursor = getCursor(dlRequest);
try {
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_INSUFFICIENT_SPACE);
} finally {
cursor.close();
diff --git a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
index 4ff0295..ddf138f 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java
@@ -145,7 +145,7 @@
cursor = getCursor(dlRequest);
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
- verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+ verifyInt(cursor, DownloadManager.COLUMN_REASON,
DownloadManager.ERROR_INSUFFICIENT_SPACE);
} finally {
if (cursor != null) {
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 1beba53..e111662 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -30,7 +30,6 @@
import android.text.util.Rfc822Tokenizer;
import android.test.MoreAsserts;
-import com.android.common.Rfc822Validator;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -238,39 +237,6 @@
}
}
- //==============================================================================================
- // Email validator
- //==============================================================================================
-
- @SmallTest
- public void testEmailValidator() {
- Rfc822Validator validator = new Rfc822Validator("gmail.com");
- String[] validEmails = new String[] {
- "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info",
- };
-
- for (String email : validEmails) {
- assertTrue(email + " should be a valid email address", validator.isValid(email));
- }
-
- String[] invalidEmails = new String[] {
- "a", "a@b", "a b", "a@b.12"
- };
-
- for (String email : invalidEmails) {
- assertFalse(email + " should not be a valid email address", validator.isValid(email));
- }
-
- Map<String, String> fixes = Maps.newHashMap();
- fixes.put("a", "<a@gmail.com>");
- fixes.put("a b", "<ab@gmail.com>");
- fixes.put("a@b", "<a@b>");
-
- for (Map.Entry<String, String> e : fixes.entrySet()) {
- assertEquals(e.getValue(), validator.fixText(e.getKey()).toString());
- }
- }
-
@SmallTest
public void testRfc822TokenizerFullAddress() {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>");
diff --git a/docs/html/guide/appendix/market-filters.jd b/docs/html/guide/appendix/market-filters.jd
index 0797892..e74cefb 100644
--- a/docs/html/guide/appendix/market-filters.jd
+++ b/docs/html/guide/appendix/market-filters.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2 align="left">Market filters quickview</h2>
+<h2>Quickview</h2>
<ul> <li>Android Market applies filters to that let you control whether your app is shown to a
user who is browing or searching for apps.</li>
<li>Filtering is determined by elements in an app's manifest file,
diff --git a/docs/html/guide/practices/design/performance.jd b/docs/html/guide/practices/design/performance.jd
index 56872a7..f5588ac 100644
--- a/docs/html/guide/practices/design/performance.jd
+++ b/docs/html/guide/practices/design/performance.jd
@@ -1,6 +1,30 @@
page.title=Designing for Performance
@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#intro">Introduction</a></li>
+ <li><a href="#optimize_judiciously">Optimize Judiciously</a></li>
+ <li><a href="#object_creation">Avoid Creating Objects</a></li>
+ <li><a href="#myths">Performance Myths</a></li>
+ <li><a href="#prefer_static">Prefer Static Over Virtual</a></li>
+ <li><a href="#internal_get_set">Avoid Internal Getters/Setters</a></li>
+ <li><a href="#use_final">Use Static Final For Constants</a></li>
+ <li><a href="#foreach">Use Enhanced For Loop Syntax</a></li>
+ <li><a href="#avoid_enums">Avoid Enums Where You Only Need Ints</a></li>
+ <li><a href="#package_inner">Use Package Scope with Inner Classes</a></li>
+ <li><a href="#avoidfloat">Use Floating-Point Judiciously</a> </li>
+ <li><a href="#library">Know And Use The Libraries</a></li>
+ <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
+ <li><a href="#closing_notes">Closing Notes</a></li>
+</ol>
+
+</div>
+</div>
+
<p>An Android application will run on a mobile device with limited computing
power and storage, and constrained battery life. Because of
this, it should be <em>efficient</em>. Battery life is one reason you might
@@ -8,24 +32,6 @@
Battery life is important to users, and Android's battery usage breakdown
means users will know if your app is responsible draining their battery.</p>
-<p>This document covers these topics: </p>
-<ul>
- <li><a href="#intro">Introduction</a></li>
- <li><a href="#optimize_judiciously">Optimize Judiciously</a></li>
- <li><a href="#object_creation">Avoid Creating Objects</a></li>
- <li><a href="#myths">Performance Myths</a></li>
- <li><a href="#prefer_static">Prefer Static Over Virtual</a></li>
- <li><a href="#internal_get_set">Avoid Internal Getters/Setters</a></li>
- <li><a href="#use_final">Use Static Final For Constants</a></li>
- <li><a href="#foreach">Use Enhanced For Loop Syntax</a></li>
- <li><a href="#avoid_enums">Avoid Enums Where You Only Need Ints</a></li>
- <li><a href="#package_inner">Use Package Scope with Inner Classes</a></li>
- <li><a href="#avoidfloat">Use Floating-Point Judiciously</a> </li>
- <li><a href="#library">Know And Use The Libraries</a></li>
- <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
- <li><a href="#closing_notes">Closing Notes</a></li>
-</ul>
-
<p>Note that although this document primarily covers micro-optimizations,
these will almost never make or break your software. Choosing the right
algorithms and data structures should always be your priority, but is
diff --git a/docs/html/guide/practices/design/responsiveness.jd b/docs/html/guide/practices/design/responsiveness.jd
index 8a4e7cf..2c7633d 100644
--- a/docs/html/guide/practices/design/responsiveness.jd
+++ b/docs/html/guide/practices/design/responsiveness.jd
@@ -1,13 +1,57 @@
page.title=Designing for Responsiveness
@jd:body
-<p>It's possible to write code that wins every performance test in the world, but still sends users in a fiery rage when they try to use the application. These are the applications that aren't <em>responsive</em> enough — the ones that feel
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#anr">What Triggers ANR?</a></li>
+ <li><a href="#avoiding">How to Avoid ANR</a></li>
+ <li><a href="#reinforcing">Reinforcing Responsiveness</a></li>
+</ol>
+
+</div>
+</div>
+
+<div class="figure">
+<img src="{@docRoot}images/anr.png" alt="Screenshot of ANR dialog box" width="240" height="320"/>
+<p><strong>Figure 1.</strong> An ANR dialog displayed to the user.</p>
+</div>
+
+<p>It's possible to write code that wins every performance test in the world, but still sends users
+in a fiery rage when they try to use the application. These are the applications that aren't
+<em>responsive</em> enough — the ones that feel
sluggish, hang or freeze for significant periods, or take too long to process
input. </p>
-<p>In Android, the system guards against applications that are insufficiently responsive for a period of time by displaying a dialog to the user, called the Application Not Responding (ANR) dialog. The user can choose to let the application continue, but the user won't appreciate having to act on this dialog every time he or she uses your application. So it's important to design responsiveness into your application, so that the system never has cause to display an ANR to the user. </p>
+<p>In Android, the system guards against applications that are insufficiently responsive for a
+period of time by displaying a dialog to the user, called the Application Not Responding (ANR)
+dialog. The user can choose to let the application continue, but the user won't appreciate having to
+act on this dialog every time he or she uses your application. So it's important to design
+responsiveness into your application, so that the system never has cause to display an ANR to the
+user. </p>
-<p>Generally, the system displays an ANR if an application cannot respond to user input. For example, if an application blocks on some I/O operation (frequently a network access), then the main application thread won't be able to process incoming user input events. After a time, the system concludes that the application has hung, and displays the ANR to give the user the option to kill it.
+<p>It's possible to write code that wins every performance test in the world,
+but still sends users in a fiery rage when they try to use the application.
+These are the applications that aren't <em>responsive</em> enough — the
+ones that feel sluggish, hang or freeze for significant periods, or take too
+long to process input. </p>
+
+<p>In Android, the system guards against applications that are insufficiently
+responsive for a period of time by displaying a dialog to the user, called the
+Application Not Responding (ANR) dialog, shown at right in Figure 1. The user
+can choose to let the application continue, but the user won't appreciate having
+to act on this dialog every time he or she uses your application. It's critical
+to design responsiveness into your application, so that the system never has
+cause to display an ANR dialog to the user. </p>
+
+<p>Generally, the system displays an ANR if an application cannot respond to
+user input. For example, if an application blocks on some I/O operation
+(frequently a network access), then the main application thread won't be able to
+process incoming user input events. After a time, the system concludes that the
+application is frozen, and displays the ANR to give the user the option to kill
+it. </p>
<p>Similarly, if your application spends too much time building an elaborate in-memory
structure, or perhaps computing the next move in a game, the system will
@@ -15,31 +59,17 @@
sure these computations are efficient using the techniques above, but even the
most efficient code still takes time to run.</p>
-<p>In both of these cases, the fix is usually to create a child thread, and do
+<p>In both of these cases, the recommended approach is to create a child thread and do
most of your work there. This keeps the main thread (which drives the user
-interface event loop) running, and prevents the system from concluding your code
+interface event loop) running and prevents the system from concluding that your code
has frozen. Since such threading usually is accomplished at the class
level, you can think of responsiveness as a <em>class</em> problem. (Compare
this with basic performance, which was described above as a <em>method</em>-level
concern.)</p>
-<div class="sidebox-wrapper">
-<div class="sidebox">
-<img src="{@docRoot}images/anr.png" width="240" height="320" alt="Screenshot of ANR dialog box">
-<p style="margin-top:.5em;padding:.5em;">An ANR dialog displayed to the user.</p>
-</div>
-</div>
-
-<p>This document discusses how the Android system determines whether an application is
-not responding and provides guidelines for
-ensuring that your application is responsive. </p>
-
-<p>This document covers these topics: </p>
-<ul>
- <li><a href="#anr">What Triggers ANR?</a></li>
- <li><a href="#avoiding">How to Avoid ANR</a></li>
- <li><a href="#reinforcing">Reinforcing Responsiveness</a></li>
-</ul>
+<p>This document describes how the Android system determines whether an
+application is not responding and provides guidelines for ensuring that your
+application stays responsive. </p>
<h2 id="anr">What Triggers ANR?</h2>
@@ -48,8 +78,10 @@
for a particular application when it detects one of the following
conditions:</p>
<ul>
- <li>No response to an input event (e.g. key press, screen touch) within 5 seconds</li>
- <li>A {@link android.content.BroadcastReceiver BroadcastReceiver} hasn't finished executing within 10 seconds</li>
+ <li>No response to an input event (e.g. key press, screen touch)
+ within 5 seconds</li>
+ <li>A {@link android.content.BroadcastReceiver BroadcastReceiver}
+ hasn't finished executing within 10 seconds</li>
</ul>
<h2 id="avoiding">How to Avoid ANR</h2>
diff --git a/docs/html/guide/practices/design/seamlessness.jd b/docs/html/guide/practices/design/seamlessness.jd
index a6c1641..dedc16f 100644
--- a/docs/html/guide/practices/design/seamlessness.jd
+++ b/docs/html/guide/practices/design/seamlessness.jd
@@ -1,6 +1,26 @@
page.title=Designing for Seamlessness
@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#drop">Don't Drop Data</a></li>
+ <li><a href="#expose">Don't Expose Raw Data</a></li>
+ <li><a href="#interrupt">Don't Interrupt the User</a></li>
+ <li><a href="#threads">Got a Lot to Do? Do it in a Thread</a></li>
+ <li><a href="#multiple-activities">Don't Overload a Single Activity Screen</a></li>
+ <li><a href="#themes">Extend System Themes</a></li>
+ <li><a href="#flexui">Design Your UI to Work with Multiple Screen Resolutions</a></li>
+ <li><a href="#network">Assume the Network is Slow</a></li>
+ <li><a href="#keyboard">Don't Assume Touchscreen or Keyboard</a></li>
+ <li><a href="#battery">Do Conserve the Device Battery</a></li>
+</ol>
+
+</div>
+</div>
+
<p>Even if your application is fast and responsive, certain design decisions can
still cause problems for users — because of unplanned interactions with
other applications or dialogs, inadvertent loss of data, unintended blocking,
@@ -42,20 +62,7 @@
by allowing you to integrate cleanly and seamlessly with other applications, and
so you should design your own code to return the favor.</p>
-<p>This document discusses common seamlessness problems and how to avoid them.
-It covers these topics: </p>
-<ul>
- <li><a href="#drop">Don't Drop Data</a></li>
- <li><a href="#expose">Don't Expose Raw Data</a></li>
- <li><a href="#interrupt">Don't Interrupt the User</a></li>
- <li><a href="#threads">Got a Lot to Do? Do it in a Thread</a></li>
- <li><a href="#multiple-activities">Don't Overload a Single Activity Screen</a></li>
- <li><a href="#themes">Extend System Themes</a></li>
- <li><a href="#flexui">Design Your UI to Work with Multiple Screen Resolutions</a></li>
- <li><a href="#network">Assume the Network is Slow</a></li>
- <li><a href="#keyboard">Don't Assume Touchscreen or Keyboard</a></li>
- <li><a href="#battery">Do Conserve the Device Battery</a></li>
-</ul>
+<p>This document discusses common seamlessness problems and how to avoid them.</p>
<h2 id="drop">Don't Drop Data</h2>
diff --git a/docs/html/guide/practices/screens_support.jd b/docs/html/guide/practices/screens_support.jd
index 2863fb2..13b5e3a 100644
--- a/docs/html/guide/practices/screens_support.jd
+++ b/docs/html/guide/practices/screens_support.jd
@@ -5,7 +5,7 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Multiple screens quickview: </h2>
+ <h2>Quickview</h2>
<ul>
<li>Android runs on devices that have different screen sizes and resolutions.</li>
<li>The screen on which your application is displayed can affect its user interface.</li>
@@ -131,57 +131,79 @@
</dl>
-<h3 id="range">Range of Screens Supported</h3>
+<h3 id="range">Range of screens supported</h3>
-<p>Android 1.5 and earlier versions of the platform were designed to support a
-single screen configuration — HVGA (320x480) resolution on a 3.2" screen.
-Because the platform targeted just one screen, application developers could
-write their applications specifically for that screen, without needing to worry
-about how their applications would be displayed on other screens. </p>
-
-<p>Starting from Android 1.6, the platform adds support for multiple screen
+<p>Starting from Android 1.6, the platform provides support for multiple screen
sizes and resolutions, reflecting the many new types and sizes of devices on
-which the platform will run. This means that developers must design their
-applications for proper display on a range of devices and screens.</p>
+which the platform runs. If you are developing an application that will run
+on Android 1.6 or later, you can use the compatibility features of the Android
+platform to ensure that your application UI renders properly across the range of
+supported screen sizes and resolutions.</p>
-<p>To simplify the way application developers design their user interfaces for
-multiple devices, and to allow more devices to participate without impacting
+<p>To simplify the way that developers design their user interfaces for
+multiple devices and to allow more devices to participate without affecting
applications, the platform divides the range of actual supported screen sizes
and resolutions into:</p>
<ul>
<li>A set of three generalized sizes: <em>large</em>, <em>normal</em>, and <em>small</em>, and </li>
-<li>A set of three generalized densities: high (<em>hdpi</em>), medium (<em>mdpi</em>), and low (<em>ldpi</em>)
+<li>A set of three generalized densities: <em>hdpi</em> (high), <em>mdpi</em> (medium), and <em>ldpi</em> (low)
</ul>
-<!--<p>Applications use to these generalized sizesThe to let you apply custom UI
-and enable/disable functionality according to the generalized class of screen,
-rather than by the specific screen. When you are developing your application,
-you use these generalized sizes and densities and Applications can use these
-generalized sizes and densities to tell the platform I will do it or you do it.
-Or a combination of both. -->
-
<p>Applications can provide custom resources (primarily layouts) for any of the
-three generalized sizes, if needed, and they can also provide resources
-(primarily drawables such as images) for any of the three generalized densities.
-Applications do not need to work with the actual physical size or density of the
-device screen. At run time, the platform handles the loading of the correct size
-or density resources, based on the generalized size or density of the current
-device screen, and adapts them to the actual pixel map of the screen.</p>
+three generalized sizes and can provide resources (primarily drawables such as
+images) for any of the three generalized densities. Applications do not need to
+work with the actual physical size or density of the device screen. At run time,
+the platform handles the loading of the correct size or density resources, based
+on the generalized size or density of the current device screen, and adapts them
+to the actual pixel map of the screen.</p>
-<p>The table below lists some of the more common screens supported
-by Android and illustrates how the platform maps them to generalized screen
-configurations. Some devices use screens that are not specifically listed
-in the table — the platform maps those to the same set generalized
-screen configurations. </p>
+<p>The generalized size/density configurations are arranged around a
+baseline configuration that is assigned a size of <em>normal</em> and a density of
+<em>mdpi</em> (medium). All applications written for Android 1.5 or earlier are (by
+definition) designed for the baseline HVGA screen used on the T-Mobile G1 and
+similar devices, which is size <em>normal</em> and density
+<em>mdpi</em>.</p>
-<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Examples of
-device screens supported by Android.</p>
+<p>Each generalized screen configuration spans a range of actual screen
+densities and physical sizes. For example, that means that multiple devices that
+report a screen size of <em>normal</em> might offer screens that differ slightly
+in actual size or aspect ratio. Similarly, devices that report a screen density
+of <em>hdpi</em> might offer screens with slightly different pixel densities.
+The platform makes these differences abstract, however — applications can
+offer UI designed for the generalized sizes and densities and let the system
+handle the actual rendering of the UI on the current device screen according to
+its characteristics. </p>
- <table id="screens-table" width="80%" style="margin-top:2em;">
+
+<img src="{@docRoot}images/screens_support/screens-ranges.png" />
+<p class="img-caption"><strong>Figure 1.</strong>
+Illustration of how the Android platform maps actual screen densities and sizes
+to generalized density and size configurations. </p>
+
+<p>Although the platform lets your application provide layouts and resources for
+generalized size-density configurations, you do not necessarily need to do write
+custom code or provide custom resources for each of the nine supported
+configurations. The platform provides robust compatibility features, described
+in the sections below, that can handle most of the work of rendering your
+application on any device screen, provided that you've implemented your
+application UI properly. For more information about how to implement a UI that
+renders properly across device screens and platform versions, see
+<a href="#screen-independence">Best Practices for Screen Independence</a>.</p>
+
+<p>To help you test your applications, the Android SDK includes emulator skins
+that replicate the sizes and densities of actual device screens on which your
+application is likely to run. You can also modify the default size and density
+of the emulator skins to replicate the characteristics of any specific
+screen.</p>
+
+<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Screen
+sizes and densities of emulator skins included in the Android SDK.</p>
+
+ <table id="screens-table">
<tbody>
<tr>
- <td></td>
+ <td style="border:none"></td>
<td style="background-color:#f3f3f3">
<nobr>Low density (120), <em>ldpi</em></nobr>
</td>
@@ -196,10 +218,7 @@
<td style="background-color:#f3f3f3">
<em>Small</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">QVGA (240x320), <nobr>2.6"-3.0" diagonal</nobr></li>
- </ul>
+ <td style="font-size:.9em;">QVGA (240x320)</td>
</td>
<td></td>
<td></td>
@@ -208,74 +227,29 @@
<td style="background-color:#f3f3f3">
<em>Normal</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WQVGA (240x400), <nobr>3.2"-3.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWQVGA (240x432), <nobr>3.5"-3.8" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;background-color:#FFE;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">HVGA (320x480), <nobr>3.0"-3.5" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>3.3"-4.0" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>3.5"-4.0" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WQVGA400 (240x400)<br>WQVGA432 (240x432)</td>
+ <td style="font-size:.9em;">HVGA (320x480)</td>
+ <td style="font-size:.9em;">WVGA800 (480x800)<br>WVGA854 (480x854)</td>
</tr>
<tr>
<td style="background-color:#f3f3f3">
<em>Large</em> screen
</td>
<td></td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>4.8"-5.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>5.0"-5.8" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WVGA800* (480x800)<br>WVGA854* (480x854)</td>
<td></td>
</tr>
- </tbody>
- </table>
+ <tr>
+ <td colspan="4" style="border:none;font-size:90%;">* To emulate this
+ configuration, specify a custom density of 160 when
+ creating an AVD that uses a WVGA800 or WVGA854 skin.
+ </td>
+</table>
-<p class="caption" style="margin-top:1em;margin-bottom:1.5em;"> </p>
-
-<p>As shown above, the various screen configurations are arranged around a
-<em>baseline screen</em> that is assigned a size of "normal" and a density of
-"medium". The HVGA screen is used as the baseline because all applications
-written against Android 1.5 or earlier are (by definition) written for the HVGA
-screen used on the T-Mobile G1 and similar devices.</p>
-
-<!-- <p>Note that each screen configuration spans a range of actual resolutions
-and physical screen sizes. For example, the The baseline configuration spans a
-range of actual screen sizes — from 3.0" to 3.5" diagonal — all with
-the same HVGA resolution. That means that the actual pixel density of devices in
-a single screen configuration can vary. </p>
-
-Because differences in density can affect the displayed size of UI elements
-declared in pixels, the framework provides a density-independent pixel (dip)
-unit that applications can use to declare UI dimensions, letting the platform
-automatically handle the scaling to the actual pixel density of the screen. When
-UI dimensions are declared in dip, the result is that they are displayed at the
-same physical size on all screens in a given configuration. </p> -->
-
-<p>Although the platform currently supports the nine possible size-density
-configurations listed in the table, you do not necessarily need to create custom
-resources for each one of them. The platform provides robust compatibility
-features, described in the sections below, that can handle most of the work of
-rendering your application on the current device screen, provided that the UI is
-properly implemented. For more information, see <a
-href="#screen-independence">Best Practices for Screen Independence</a>.</p>
-
-<!--
<p>For an overview of the relative numbers of high (hdpi), medium (mdpi), and
-low (ldpi) density screens, see the <a
-href="{@docRoot}guide/resources/dashboard/screen-densities.html">Screen Densities dashboard</a>.</p>
--->
+low (ldpi) density screens in Android-powered devices available now, see the <a
+href="{@docRoot}resources/dashboard/screens.html">Screen Sizes and Densities</a> dashboard.</p>
+
<h3 id="support">How Android supports multiple screens</h3>
@@ -307,8 +281,8 @@
size-specific resources are <code>large</code>, <code>normal</code>, and
<code>small</code>, and those for density-specific resources are
<code>hdpi</code> (high), <code>mdpi</code> (medium), and <code>ldpi</code>
-(low). The qualifiers correspond to the generalized densities given in
-<a href="#range">Table 1</a>, above.</li>
+(low). The qualifiers correspond to the generalized densities described in
+<a href="#range">Range of screens supported</a>, above.</li>
<li>The platform also provides a
<a href="{@docRoot}guide/topics/manifest/supports-screens-element.html">
<code><supports-screens></code></a>
@@ -457,7 +431,7 @@
<div id=vi09 style=TEXT-ALIGN:left>
<img src="{@docRoot}images/screens_support/dip.png" style="padding-bottom:0;margin-bottom:0;" />
<p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0
-1em;"><strong>Figure 1.</strong> Examples of density independence on WVGA high
+1em;"><strong>Figure 2.</strong> Examples of density independence on WVGA high
density (left), HVGA medium density (center), and QVGA low density (right). </p>
</div>
@@ -479,7 +453,8 @@
display of your application on different classes of device screens, as listed
below. The <code>smallScreens</code>, <code>normalScreens</code>, and
<code>largeScreens</code> attributes correspond to the generalized screen sizes
-shown in <a href="#range">Table 1</a>, earlier in this document.</p>
+described in <a href="#range">Range of screens supported</a>, earlier in this
+document.</p>
<table id="vrr8">
<tr>
@@ -489,6 +464,12 @@
<th >
Description
</th>
+ <th>
+ Default value,<br><nobr>Android 1.5 and Lower</nobr>
+ </th>
+ <th>
+ Default value,<br><nobr>Android 1.6 and Higher</nobr>
+ </th>
</tr>
<tr>
<td>
@@ -497,10 +478,10 @@
<td>
Whether or not the application UI is designed for use on
<em>small</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not. </p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -509,8 +490,10 @@
<td>
Whether or not the application UI is designed for use on
<em>normal</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. The default value is "<code>true</code>".
+"<code>false</code>" if not. The default value is always "<code>true</code>".
</td>
+<td>"<code>true</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -519,10 +502,10 @@
<td>
Whether or not the application UI is designed for use on
<em>large</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not.
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -535,9 +518,13 @@
<ul>
<li>If set to "<code>true</code>", the platform disables its
density-compatibility features for all screen densities — specifically,
-the auto-scaling of absolute pixel units and math — and relies on the
-application to use density-independent pixel units and/or to manage the
-adaptation of pixel values according to density of the current screen. </li>
+the auto-scaling of absolute pixel units (<code>px</code>) and math — and
+relies on the application to use density-independent pixel units
+(<code>dp</code>) and/or math to manage the adaptation of pixel values according
+to density of the current screen. That is, as long as your application uses
+density-independent units (dp) for screen layout sizes, then it will perform
+properly on different densities when this attribute is set to
+"<code>true</code>".</li>
<li>If set to "<code>false</code>", the platform enables its
density-compatibility features for all screen densities. In this case, the
@@ -546,45 +533,73 @@
(160). The platform then transparently auto-scales the application's pixel units
and math as needed to match the actual device screen density. </li>
</ul>
- <p>See <a href="#defaults">Default values for attributes</a> for
-information about the assumed value of this attribute, if not declared.</p>
+<p>Note that the setting of this attribute affects density-compatibility only.
+It does not affect size-compatibility features such as display on a virtual
+baseline screen.</p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
</table>
<p>In general, when you declare a screen-size attribute
(<code>smallScreens</code>, <code>normalScreens</code>, or
-<code>largeScreens</code>) as "true", you are signaling to the platform that
-your application wants to manage its UI by itself, for all screen sizes, without
-the platform applying any size-compatibility behaviors (such as a virtual HVGA
-display area). If you declare a screen-size attribute as "false", you are
-signaling that your application is not designed for that screen size. The
-effects are conditioned by the screen size that your application does not
-support:</p>
-
+<code>largeScreens</code>) as "<code>true</code>", you are signaling to the
+platform that your application is designed to render properly on that screen
+size. As a result, the platform does not apply any size-compatibility features
+(such as a virtual HVGA display area). If you declare a screen-size attribute as
+"<code>false</code>", you are signaling that your application is <em>not</em>
+designed for that screen size. In this case, the platform <em>does</em> apply
+size-compatibility features, rendering the application in an HVGA baseline
+display area. If the current screen is larger than <em>normal</em> size, the
+platform renders the application in a virtual HVGA screen on the larger screen.
+See <a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
+illustration of what an application looks like when displayed in a virtual HVGA
+screen.</p>
+
+<p>In other words, setting a <code><supports-screens></code> attribute to
+"<code>false</code>" tells the platform to enable it's compatibility features
+when displaying the application on a screen of that size <em>or any larger
+size</em>, if also disallowed. Otherwise, the platform gives the application a
+normal display area that can use the full device screen area, if
+appropriate.</p>
+
+<p>Android Market also makes use of the <code><supports-screens></code>
+attributes. It uses them to filter the application from devices whose screens
+are not supported by the application. Specifically, Android Market considers an
+application compatible with a device if the application supports a screen that
+is the same or smaller than the current device screen. Android Market filters
+the application if it disallows the device's screen size and does not support a
+smaller size. In general, Android does not provide downward size-compatibility
+features for applications.</p>
+
+<p>Here are some examples:</p>
+
<ul>
- <li>If you declare <code>largeScreens="false"</code>, your application can
-still be installed by users of devices with large screens. When run on a device
-with a large screen, this attribute value causes the platform to run the
-application in compatibility mode, rendering it in a baseline screen area
-(normal size, medium density) reserved on the larger screen. See
-<a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
-illustration of what an application looks like when displayed in compatibility
-mode.</li>
- <li>If you declare <code>smallScreens="false"</code>, your application can
-still be installed by users of devices with small screens. However, this
-attribute value causes Android Market to filter your application from the list
-of applications available to such users. In effect, this prevents users from
-installing the application on small-screen devices. </li>
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="true"
+largeScreens="false" </code> in your application's manifest. <p>Although the
+application is not designed for display on large screens, the platform can still
+run it successfully in <a href="#compatibility-examples">size-compatibility
+mode</a>. Android Market does not filter the application from devices
+<em>normal</em> and <em>large</em> size screens, but does filter it from
+<em>small</em> size screens, since the application provides no screen support at
+<em>small</em> size (and there is no smaller size).</p></li>
+
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="false"
+largeScreens="true"</code> in your application's manifest. <p>Android Market
+filters the application from users of devices with <em>small</em> and
+<em>normal</em> size screens. In effect, this prevents such users from
+installing the application.</p></li>
</ul>
-<p>If you declare the <code>android:anyDensity</code> attribute as "true", you
-are signaling to the platform that your application wants to manage its UI by
-itself, for all screen densities, using the actual screen dimensions and pixels.
-In this case, the application must ensure that it declares its UI dimensions
-using density-independent pixels and scales any actual pixel values or math by
-the scaling factor available from
-{@link android.util.DisplayMetrics#density android.util.DisplayMetrics.density}.</p>
+<p>If you declare the <code>android:anyDensity</code> attribute as
+"<code>true</code>", you are signaling to the platform that your application is
+designed to display properly on any screen density. In this case, the
+application must ensure that it declares its UI dimensions using
+density-independent pixels (<code>dp</code>) and scales any absolute pixel
+values (<code>px</code>) or math by the scaling factor available from {@link
+android.util.DisplayMetrics#density android.util.DisplayMetrics.density}. See <a
+href="#dips-pels">Converting from dips to pixels</a> for an example.</p>
<p>Note that the setting of the <code>android:anyDensity</code> attribute does
not affect the platform's pre-scaling of drawable resources, such as bitmaps and
@@ -594,22 +609,22 @@
normal, and small screens in any densities.</p>
<pre><manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
+ ...
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
- android:resizable="true"
android:anyDensity="true" />
- </manifest>
+ ...
+</manifest>
</pre>
-
+<!-- android:resizeable="true" -->
<h4 id="defaults">
Default values for attributes
</h4>
<p>The default values for the <code><supports-screens></code> attributes
-differs, depending on the the value of the
+differ, depending on the the value of the
<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>android:minSdkVersion</code></a>
attribute in the application's manifest, as well as on
the value of <code>android:targetSdkVersion</code>, if declared:</p>
@@ -618,19 +633,20 @@
<ul>
<li>
If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "3" (Android 1.5) or lower, the default
-value for everything except android:normalScreens is <code>false</code>. If you
-are primarily targeting pre-Android 1.6 platforms but also want to support other
-densities/screen sizes, you need to set the appropriate attributes to
-<code>true</code>.
+<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
+default value for everything is "<code>true</code>". If your application uses
+APIs introduced in Android 1.6 or higher, but does not support specific screen
+densities and/or screen sizes, you need to explicitly set the appropriate
+attributes to "<code>false</code>".
</li>
<li>
- If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
-default value for everything is <code>true</code>. If your application
-requires <span style=BACKGROUND-COLOR:#ffffff>Android 1.6 </span>features,
-but does not support these densities and/or screen sizes, you need to set the
-appropriate attributes to <code>false</code>.
+ If <code>android:minSdkVersion</code> is declared with a value of "3"
+(Android 1.5) or lower <em>and</em> a <code>android:targetSdkVersion</code>
+attribute is <em>not</em> declared with a value of "4" or higher, the default
+value for all attributes except <code>android:normalScreens</code> is
+"<code>false</code>". If you are primarily targeting pre-Android 1.6 platforms
+but also want to support other densities/screen sizes, you need to explicitly
+set the appropriate attributes to "<code>true</code>".
</li>
<li>
Note that <code>android:normalScreens</code> always defaults to
@@ -646,8 +662,8 @@
of resources based on the characteristics of the screen on which your application
is running. You can use these qualifiers to provide size- and density-specific
resources in your application. For more information about the generalized sizes
-and densities that correspond to the qualifiers, see <a href="#range">Table
-1</a>, earlier in this document.</p>
+and densities that correspond to the qualifiers, see <a href="#range">Range
+of Screens Supported</a>, earlier in this document.</p>
<table>
<tr>
@@ -659,30 +675,29 @@
<tr>
<td rowspan="3">Size</td>
<td><code>small</code></td>
- <td>Resources for small screens, such as QVGA low density.</td>
+ <td>Resources designed for <em>small</em> size screens.</td>
</tr>
<tr>
<td><code>normal</code></td>
- <td>Resources for normal (baseline configuration) screens, such as T-Mobile
-G1/HTC Magic screen size, or equivalent.</td>
+ <td>Resources designed for <em>normal</em> size screens.</td>
</tr>
<tr>
<td><code>large</code></td>
-<td>Resources for large screens. Typical example is a tablet like device.</td>
+<td>Resources for <em>large</em> size screens.</td>
</tr>
<tr>
<td rowspan="4">Density</td>
<td><code>ldpi</code></td>
-<td>Low-density resources, for 100 to 140 dpi screens.</td>
+<td>Resources designed for low-density (<em>ldpi</em>) screens.</td>
</tr>
<tr>
<td><code>mdpi</code></td>
-<td>Medium-density resources for 140 to 180 dpi screens.</td>
+<td>Resources designed for medium-density (<em>mdpi</em>) screens.</td>
</tr>
<tr>
<td><code>hdpi</code></td>
-<td>High-density resources for 190 to 250 dpi screens.</td>
+<td>Resources designed for high-density (<em>hdpi</em>) screens.</td>
</tr>
<tr>
<td><code>nodpi</code></td>
@@ -747,8 +762,8 @@
<h2 id="screen-independence">Best practices for Screen Independence</h2>
<p>The objective of supporting multiple screens is to create an application that
-can run properly on any display and function properly on any of the screen
-configurations listed in <a href="#range">Table 1</a> earlier in this document.
+can run properly on any display and function properly on any of the generalized
+screen configurations supported by the platform.
</p>
<p>You can easily ensure that your application will display properly on
@@ -855,7 +870,7 @@
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;">
<img src="{@docRoot}images/screens_support/scale-test.png" style="padding:0;margin:0;">
-<p class="caption" style="margin:0;padding:0;"><strong>Figure 2.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
+<p class="caption" style="margin:0;padding:0;"><strong>Figure 3.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
</div>
<p>Even with the size- and density-compatibility features that the platform
@@ -947,7 +962,7 @@
{@link android.graphics.Canvas Canvas} for more
information on auto-scaling.</p>
-<p>Figure 2, at right, demonstrates the results of the pre-scale and auto-scale
+<p>Figure 3, at right, demonstrates the results of the pre-scale and auto-scale
mechanisms when loading low (120), medium (160) and high (240) density bitmaps
on a baseline screen. The differences are subtle, because all of the bitmaps are
being scaled to match the current screen density, however the scaled bitmaps
@@ -1078,7 +1093,7 @@
<div id="f9.5" style="float:right;margin:0;padding:0;">
<img src="{@docRoot}images/screens_support/avds-config.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 3.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 4.</strong>
A typical set of AVDs for testing screens support.</p>
</div>
@@ -1143,52 +1158,29 @@
<p>Note that starting the emulator with the <code>-scale</code> option will
scale the entire emulator display, based on both the dpi of the skin and of your
-monitor. Using the default densities, the emulator skins included in the Android
-1.6 SDK will emulate the following screen sizes:</p>
-
-<ul>
- <li>
- QVGA, low density: 3.3"
- </li>
- <li>
- WQVGA, low density: 3.9"
- </li>
- <li>
- WQVGA432, low density: 4.1"
- </li>
- <li>
- HVGA, medium density: 3.6"
- </li>
- <li>
- WVGA800, high density: 3.9"
- </li>
- <li>
- WVGA854, high density: 4.1"
- </li>
-</ul>
+monitor. The default emulator skins included in the Android SDK are listed
+in <a href="#screens-table">Table 1</a>, earlier in this document.</p>
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;width:520px;">
<img src="{@docRoot}images/screens_support/avd-density.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 4.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 5.</strong>
Resolution and density options that you can use, when creating an AVD using the AVD Manager.</p>
</div>
<p>You should also make sure to test your application on different physical
-screen sizes within a single size-density configuration. For example, according
-to <a href="#range">Table 1</a>, the minimum supported diagonal of QVGA is 2.8".
-To display this is on a 30" monitor you will need to adjust the value passed to
-<code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also pass a float value to
-<code>-scale</code> to specify your own scaling factor:</p>
+screen sizes within a single size-density configuration. For example, to
+display this screen configuration on a 30" monitor you will need to adjust
+the value passed to <code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also
+pass a float value to <code>-scale</code> to specify your own scaling factor:</p>
<pre>emulator -avd <name> -scale 0.6</pre>
<p>If you would like to test your application on a screen that uses a resolution
or density not supported by the built-in skins, you can either adjust an
-existing skin, or create an AVD
-that uses a custom resolution or density.</p>
+existing skin, or create an AVD that uses a custom resolution or density.</p>
<p>In the AVD Manager, you can specify a custom skin resolution or density in
-the Create New AVD dialog, as shown in Figure 4, at right.</p>
+the Create New AVD dialog, as shown in Figure 5, at right.</p>
<p>In the <code>android</code> tool, follow these steps to create an AVD with a
custom resolution or density:</p>
@@ -1203,9 +1195,9 @@
<li>To specify a custom density for the skin, answer "yes" when asked whether
you want to create a custom hardware profile for the new AVD.</li>
<li>Continue through the various profile settings until the tool asks you to
-specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Consult <a
-href="#range">Table 1</a>, earlier in this document, and enter the appropriate
-value. For example, enter "160" to use medium density for the WVGA800 screen.</li>
+specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Enter an appropriate
+value, such as "120" for a low-density screen, "160" for a medium density screen,
+or "240" for a high-density screen.</li>
<li>Set any other hardware options and complete the AVD creation.</li>
</ol>
diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
index c8d241c..6cb98e6 100644
--- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Activity and task design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Activities are the main building blocks of Android applications. </li>
diff --git a/docs/html/guide/practices/ui_guidelines/icon_design.jd b/docs/html/guide/practices/ui_guidelines/icon_design.jd
index 51ccfaf..389d5fa 100644
--- a/docs/html/guide/practices/ui_guidelines/icon_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/icon_design.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Icon design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can use several types of icons in an Android application.</li>
@@ -35,25 +35,30 @@
</ol>
-<h2>See also</h2>
-
-<ol>
-<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple
-Screens</a></li>
-<li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon
-Templates Pack, v2.0 »</a></li>
-</ol>
-
<h2>Older versions</h2>
<ol>
<li style="margin-top:4px;"><a
href="{@docRoot}guide/practices/ui_guidelines/icon_design_1.html">Icon Design
Guidelines, Android 1.0</a></li>
+</ol>
+
+<h2>Downloads</h2>
+
+<ol>
+<li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon
+Templates Pack, v2.0 »</a></li>
<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
Templates Pack, v1.0 »</a></li>
</ol>
+<h2>See also</h2>
+
+<ol>
+<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple
+Screens</a></li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_1.jd b/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
index 1c75843..995cfea 100644
--- a/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
+++ b/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Icon design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can use several types of icons in an Android application.</li>
@@ -35,12 +35,17 @@
</ol>
+<h2>Downloads</h2>
+
+<ol>
+<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
+Templates Pack, v1.0 »</a></li>
+</ol>
+
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
-<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
-Templates Pack, v1.0 »</a></li>
</ol>
diff --git a/docs/html/guide/practices/ui_guidelines/menu_design.jd b/docs/html/guide/practices/ui_guidelines/menu_design.jd
index ebf8a4b..840ee66 100644
--- a/docs/html/guide/practices/ui_guidelines/menu_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/menu_design.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Menu design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>An Options menu is for any commands that are global to the current activity. </li>
diff --git a/docs/html/guide/practices/ui_guidelines/widget_design.jd b/docs/html/guide/practices/ui_guidelines/widget_design.jd
index fc62fe6..e978069 100644
--- a/docs/html/guide/practices/ui_guidelines/widget_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/widget_design.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Widget design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Widgets have six standard sizes on the Home screen</li>
@@ -27,7 +27,7 @@
<h2>See also</h2>
<ol>
-<li><a href="{@docRoot}guide/topics/appwidgets/index.html">AppWidgets</a> topic in the <em>Dev Guide</em></li>
+<li><a href="{@docRoot}guide/topics/appwidgets/index.html">App Widgets</a></li>
<li><a href="http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html">AppWidgets blog post</a></li>
</ol>
diff --git a/docs/html/guide/publishing/app-signing.jd b/docs/html/guide/publishing/app-signing.jd
index 34d94191..6758054 100644
--- a/docs/html/guide/publishing/app-signing.jd
+++ b/docs/html/guide/publishing/app-signing.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Signing quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>All Android apps <em>must</em> be signed</a></li>
diff --git a/docs/html/guide/publishing/licensing.jd b/docs/html/guide/publishing/licensing.jd
index 07af68d..fc83ec0 100644
--- a/docs/html/guide/publishing/licensing.jd
+++ b/docs/html/guide/publishing/licensing.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Market Licensing quickview: </h2>
+ <h2>Quickview</h2>
<ul>
<li>Licensing lets you protect your application on any device that includes Android Market.</li>
<li>Your app maintains control of how it enforces its licensing status. </li>
diff --git a/docs/html/guide/publishing/preparing.jd b/docs/html/guide/publishing/preparing.jd
index 442c12a..45a5b777 100644
--- a/docs/html/guide/publishing/preparing.jd
+++ b/docs/html/guide/publishing/preparing.jd
@@ -1,20 +1,6 @@
page.title=Preparing to Publish: A Checklist
@jd:body
-<!--
-<div id="qv-wrapper">
-<div id="qv">
-
-<h2>In this document</h2>
-
-<ol>
-<li><a href=""></a></li>
-</ol>
-
-</div>
-</div>
--->
-
<p>Publishing an application means testing it, packaging it appropriately, and
making it available to users of Android-powered mobile devices.</p>
@@ -34,7 +20,7 @@
<div class="special">
-<p>Before you consider your application ready for release:</p>
+<p><a href="#releaseready">Before you consider your application ready for release</a>:</p>
<ol>
<li>Test your application extensively on an actual device </li>
@@ -44,7 +30,7 @@
<li>Turn off logging and debugging and clean up data/files</li>
</ol>
-<p>Before you do the final compile of your application:</p>
+<p><a href="#finalcompile">Before you do the final compile of your application</a>:</p>
<ol start="6">
<li>Version your application</li>
@@ -52,8 +38,9 @@
<li>Register for a Maps API Key, if your application is using MapView elements</li>
</ol>
-<p><em>Compile your application...</em></p>
-<p>After compiling your application:</p>
+<p><a href="#compile">Compile your application</a></p>
+
+<p><a href="#post-compile">After you compile your application</a>:</p>
<ol start="9">
<li>Sign your application</li>
<li>Test your compiled application</li>
@@ -242,7 +229,7 @@
you can compile your application for release.</p>
-<h2 id="post-compile">After compiling your application</h2>
+<h2 id="post-compile">After you compile your application</h2>
<h3 id="signapp">9. Sign your application</h3>
diff --git a/docs/html/guide/publishing/publishing.jd b/docs/html/guide/publishing/publishing.jd
index 9b470c8..af1ea74 100644
--- a/docs/html/guide/publishing/publishing.jd
+++ b/docs/html/guide/publishing/publishing.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Publishing quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can publish your application using a hosted service such as Android Market or through a web server.</li>
diff --git a/docs/html/guide/publishing/versioning.jd b/docs/html/guide/publishing/versioning.jd
index 1d55f8a..b646247 100644
--- a/docs/html/guide/publishing/versioning.jd
+++ b/docs/html/guide/publishing/versioning.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Versioning quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Your application <em>must</em> be versioned</a></li>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index 7a8dd59..3de5627 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -3,12 +3,14 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.appwidget.AppWidgetProvider}</li>
- <li>{@link android.appwidget.AppWidgetProviderInfo}</li>
- <li>{@link android.appwidget.AppWidgetManager}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>App Widgets provide users access to some of your application features
+directly from the Home screen (without the need to launch an activity)</li>
+ <li>App Widgets are backed by a special kind of broadcast receiver that handles the App
+Widget lifecycle</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
@@ -28,6 +30,13 @@
</li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.appwidget.AppWidgetProvider}</li>
+ <li>{@link android.appwidget.AppWidgetProviderInfo}</li>
+ <li>{@link android.appwidget.AppWidgetManager}</li>
+ </ol>
+
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/practices/ui_guidelines/widget_design.html">App Widget Design
diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd
index db06efc..84c2ed2 100644
--- a/docs/html/guide/topics/fundamentals.jd
+++ b/docs/html/guide/topics/fundamentals.jd
@@ -3,14 +3,6 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.app.Activity}</li>
-<li>{@link android.app.Service}</li>
-<li>{@link android.content.BroadcastReceiver}</li>
-<li>{@link android.content.ContentProvider}</li>
-<li>{@link android.content.Intent}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -43,6 +35,16 @@
<li><a href="#proclife">Processes and lifecycles</a></li>
</ol></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.app.Activity}</li>
+<li>{@link android.app.Service}</li>
+<li>{@link android.content.BroadcastReceiver}</li>
+<li>{@link android.content.ContentProvider}</li>
+<li>{@link android.content.Intent}</li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/topics/intents/intents-filters.jd b/docs/html/guide/topics/intents/intents-filters.jd
index bd1d694..5905214 100644
--- a/docs/html/guide/topics/intents/intents-filters.jd
+++ b/docs/html/guide/topics/intents/intents-filters.jd
@@ -3,15 +3,6 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.content.Intent}</li>
-<li>{@link android.content.IntentFilter}</li>
-<li>{@link android.app.Activity}</li>
-<li>{@link android.app.Service}</li>
-<li>{@link android.content.BroadcastReceiver}</li>
-<li>{@link android.content.pm.PackageManager}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -22,6 +13,15 @@
<li style="margin-left: 2em"><a href="#imatch">Using intent matching</a></li>
<li><a href="#npex">Note Pad Example</a></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.content.Intent}</li>
+<li>{@link android.content.IntentFilter}</li>
+<li>{@link android.content.BroadcastReceiver}</li>
+<li>{@link android.content.pm.PackageManager}</li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/topics/media/index.jd b/docs/html/guide/topics/media/index.jd
index 96c500c..558d453 100644
--- a/docs/html/guide/topics/media/index.jd
+++ b/docs/html/guide/topics/media/index.jd
@@ -4,7 +4,7 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Audio/Video quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Audio playback and record</li>
<li>Video playback</li>
@@ -12,14 +12,6 @@
<li>Built-in codecs for a variety of media. See <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li>
</ul>
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.media.MediaPlayer MediaPlayer} (all available formats)</li>
-<li>{@link android.media.MediaRecorder MediaRecorder} (all available formats)</li>
-<li>{@link android.media.JetPlayer JetPlayer} (playback, JET content)</li>
-<li>{@link android.media.SoundPool SoundPool} (sound management)</li>
-</ol>
-
<h2>In this document</h2>
<ol>
<li><a href="#playback.html">Audio and Video Playback</a>
@@ -32,6 +24,14 @@
<li><a href="#capture">Audio Capture</a></li>
</ol>
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.media.MediaPlayer MediaPlayer}</li>
+<li>{@link android.media.MediaRecorder MediaRecorder}</li>
+<li>{@link android.media.JetPlayer JetPlayer}</li>
+<li>{@link android.media.SoundPool SoundPool}</li>
+</ol>
+
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a></li>
diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd
index da4e7a1..2aed5e1 100644
--- a/docs/html/guide/topics/providers/content-providers.jd
+++ b/docs/html/guide/topics/providers/content-providers.jd
@@ -3,12 +3,6 @@
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.content.ContentProvider}</li>
-<li>{@link android.content.ContentResolver}</li>
-<li>{@link android.database.Cursor}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -18,6 +12,13 @@
<li><a href="#creating">Creating a content provider</a></li>
<li><a href="#urisum">Content URI summary</a></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.content.ContentProvider}</li>
+<li>{@link android.content.ContentResolver}</li>
+<li>{@link android.database.Cursor}</li>
+</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/search/adding-custom-suggestions.jd b/docs/html/guide/topics/search/adding-custom-suggestions.jd
index ce0c619..c8f06b9 100644
--- a/docs/html/guide/topics/search/adding-custom-suggestions.jd
+++ b/docs/html/guide/topics/search/adding-custom-suggestions.jd
@@ -33,7 +33,7 @@
<li>{@link android.content.ContentProvider}</li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/search/index.jd b/docs/html/guide/topics/search/index.jd
index 78e0be2..f563715 100644
--- a/docs/html/guide/topics/search/index.jd
+++ b/docs/html/guide/topics/search/index.jd
@@ -13,7 +13,7 @@
<ol>
<li><a href="searchable-config.html">Searchable Configuration</a></li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/search/search-dialog.jd b/docs/html/guide/topics/search/search-dialog.jd
index 49c6627..49938b4 100644
--- a/docs/html/guide/topics/search/search-dialog.jd
+++ b/docs/html/guide/topics/search/search-dialog.jd
@@ -29,7 +29,7 @@
<li>{@link android.app.SearchManager}</li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/testing/testing_android.jd b/docs/html/guide/topics/testing/testing_android.jd
index 513e472..1d5f911 100755
--- a/docs/html/guide/topics/testing/testing_android.jd
+++ b/docs/html/guide/topics/testing/testing_android.jd
@@ -50,14 +50,14 @@
<a href="#NextSteps">Next Steps</a>
</li>
</ol>
- <h2>Key Classes and Packages</h2>
+ <h2>Key classes</h2>
<ol>
<li>{@link android.test.InstrumentationTestRunner}</li>
<li>{@link android.test}</li>
<li>{@link android.test.mock}</li>
<li>{@link junit.framework}</li>
</ol>
- <h2>Related Tutorials</h2>
+ <h2>Related tutorials</h2>
<ol>
<li>
<a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">
@@ -67,7 +67,7 @@
<a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
</li>
</ol>
- <h2>See Also</h2>
+ <h2>See also</h2>
<ol>
<li>
<a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
diff --git a/docs/html/guide/topics/ui/binding.jd b/docs/html/guide/topics/ui/binding.jd
index 6ac0bb0..26364ee8 100644
--- a/docs/html/guide/topics/ui/binding.jd
+++ b/docs/html/guide/topics/ui/binding.jd
@@ -11,11 +11,11 @@
<li><a href="#HandlingUserSelections">Handling User Selections</a></li>
</ol>
- <h2>See also</h2>
+ <h2>Related tutorials</h2>
<ol>
- <li><a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Hello Spinner tutorial</a></li>
- <li><a href="{@docRoot}resources/tutorials/views/hello-listview.html">Hello ListView tutorial</a></li>
- <li><a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Hello GridView tutorial</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-listview.html">List View</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Grid View</a></li>
</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd
index 5c12db9..fe641a2 100644
--- a/docs/html/guide/topics/ui/declaring-layout.jd
+++ b/docs/html/guide/topics/ui/declaring-layout.jd
@@ -5,12 +5,6 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.view.View}</li>
- <li>{@link android.view.ViewGroup}</li>
- <li>{@link android.view.ViewGroup.LayoutParams}</li>
- </ol>
<h2>In this document</h2>
<ol>
<li><a href="#write">Write the XML</a></li>
@@ -26,6 +20,12 @@
<li><a href="#example">Example Layout</a></li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.view.View}</li>
+ <li>{@link android.view.ViewGroup}</li>
+ <li>{@link android.view.ViewGroup.LayoutParams}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index f47a709..879eb8b 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -5,10 +5,6 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.app.Dialog}</li>
- </ol>
<h2>In this document</h2>
<ol>
<li><a href="#ShowingADialog">Showing a Dialog</a></li>
@@ -26,6 +22,11 @@
</li>
<li><a href="#CustomDialog">Creating a Custom Dialog</a></li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.Dialog}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/index.jd b/docs/html/guide/topics/ui/index.jd
index abcf6be..375c9fe 100644
--- a/docs/html/guide/topics/ui/index.jd
+++ b/docs/html/guide/topics/ui/index.jd
@@ -4,13 +4,6 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.view.View}</li>
- <li>{@link android.view.ViewGroup}</li>
- <li>{@link android.widget Widget classes}</li>
- </ol>
-
<h2>In this document</h2>
<ol>
<li><a href="#ViewHierarchy">View Hierarchy</a></li>
@@ -25,6 +18,13 @@
</ol>
</li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.view.View}</li>
+ <li>{@link android.view.ViewGroup}</li>
+ <li>{@link android.widget Widget classes}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/notifiers/index.jd b/docs/html/guide/topics/ui/notifiers/index.jd
index f7ccce7..d29324c 100644
--- a/docs/html/guide/topics/ui/notifiers/index.jd
+++ b/docs/html/guide/topics/ui/notifiers/index.jd
@@ -3,13 +3,7 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>In this document</h2>
- <ol>
- <li><a href="#Toast">Toast Notification</a></li>
- <li><a href="#StatusBarNotification">Status Bar Notification</a></li>
- <li><a href="#Dialog">Dialog Notification</a></li>
- </ol>
- <h2>More about</h2>
+ <h2>Topics</h2>
<ol>
<li><a href="toasts.html">Creating Toast Notifications</a></li>
<li><a href="notifications.html">Creating Status Bar Notifications</a></li>
diff --git a/docs/html/guide/topics/ui/notifiers/notifications.jd b/docs/html/guide/topics/ui/notifiers/notifications.jd
index a0dd9f1..abc945a 100644
--- a/docs/html/guide/topics/ui/notifiers/notifications.jd
+++ b/docs/html/guide/topics/ui/notifiers/notifications.jd
@@ -5,18 +5,21 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.app.Notification}</li>
- <li>{@link android.app.NotificationManager}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>A status bar notification allows your application to notify the user of an event
+without interupting their current activity</li>
+ <li>You can attach an intent to your notification that the system will initiate when the
+user clicks it</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
<li><a href="#ManageYourNotifications">Managing your Notifications</a></li>
<li><a href="#CreateANotification">Creating a Notification</a>
<ol>
- <li><a href="#Update">Updating the notification</a></li>
+ <li><a href="#Updating">Updating the notification</a></li>
<li><a href="#Sound">Adding a sound</a></li>
<li><a href="#Vibration">Adding vibration</a></li>
<li><a href="#Lights">Adding flashing lights</a></li>
@@ -25,6 +28,11 @@
</li>
<li><a href="#CustomExpandedView">Creating a Custom Expanded View</a></li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.Notification}</li>
+ <li>{@link android.app.NotificationManager}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/notifiers/toasts.jd b/docs/html/guide/topics/ui/notifiers/toasts.jd
index 5b324d2..0d3e10c 100644
--- a/docs/html/guide/topics/ui/notifiers/toasts.jd
+++ b/docs/html/guide/topics/ui/notifiers/toasts.jd
@@ -5,16 +5,24 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
+ <h2>Quickview</h2>
<ol>
- <li>{@link android.widget.Toast}</li>
+ <li>A toast is a message that appears on the surface of the screen for a moment, but it
+does not take focus (or pause the current activity), so it cannot accept user input</li>
+ <li>You can customize the toast layout to include images</li>
</ol>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
- <li><a href="#Position">Positioning your Toast</a></li>
+ <li><a href="#Positioning">Positioning your Toast</a></li>
<li><a href="#CustomToastView">Creating a Custom Toast View</a></li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.widget.Toast}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/ui-events.jd b/docs/html/guide/topics/ui/ui-events.jd
index ccef64f..7d7bfaf 100644
--- a/docs/html/guide/topics/ui/ui-events.jd
+++ b/docs/html/guide/topics/ui/ui-events.jd
@@ -13,9 +13,9 @@
<li><a href="#HandlingFocus">Handling Focus</a></li>
</ol>
- <h2>See also</h2>
+ <h2>Related tutorials</h2>
<ol>
- <li><a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Hello Form Stuff tutorial</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff</a></li>
</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/wireless/bluetooth.jd b/docs/html/guide/topics/wireless/bluetooth.jd
index a8ff007..fa2875b 100644
--- a/docs/html/guide/topics/wireless/bluetooth.jd
+++ b/docs/html/guide/topics/wireless/bluetooth.jd
@@ -3,14 +3,13 @@
<div id="qv-wrapper">
<div id="qv">
- <h2>Key Classes</h2>
- <ol>
- <li>{@link android.bluetooth.BluetoothAdapter}</li>
- <li>{@link android.bluetooth.BluetoothDevice}</li>
- <li>{@link android.bluetooth.BluetoothSocket}</li>
- <li>{@link android.bluetooth.BluetoothServerSocket}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>Android's bluetooth APIs allow your application to perform wireless data transactions with
+other devices</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#TheBasics">The Basics</a></li>
@@ -33,11 +32,18 @@
</li>
<li><a href="#ManagingAConnection">Managing a Connection</a></li>
</ol>
-
- <h2>See also</h2>
+
+ <h2>Key classes</h2>
<ol>
- <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat sample
- app</a></li>
+ <li>{@link android.bluetooth.BluetoothAdapter}</li>
+ <li>{@link android.bluetooth.BluetoothDevice}</li>
+ <li>{@link android.bluetooth.BluetoothSocket}</li>
+ <li>{@link android.bluetooth.BluetoothServerSocket}</li>
+ </ol>
+
+ <h2>Related samples</h2>
+ <ol>
+ <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat</a></li>
</ol>
</div>
diff --git a/docs/html/images/screens_support/screens-ranges.png b/docs/html/images/screens_support/screens-ranges.png
new file mode 100644
index 0000000..034ac34
--- /dev/null
+++ b/docs/html/images/screens_support/screens-ranges.png
Binary files differ
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index 92d4e15..3b4ccb0e 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,7 +52,7 @@
<div class="dashboard-panel">
<img alt="" height="250" width="460"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:12.0,17.5,0.1,41.7,28.7&chl=
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:9.7,16.4,0.1,40.4,33.4&chl=
Android%201.5|Android%201.6|Other*|Android%202.1|Android%202.2&chco=c4df9b,
6fad0c" />
@@ -62,13 +62,13 @@
<th>API Level</th>
<th>Distribution</th>
</tr>
-<tr><td>Android 1.5</td><td>3</td><td>12.0%</td></tr>
-<tr><td>Android 1.6</td><td>4</td><td>17.5%</td></tr>
-<tr><td>Android 2.1</td><td>7</td><td>41.7%</td></tr>
-<tr><td>Android 2.2</td><td>8</td><td>28.7%</td></tr>
+<tr><td>Android 1.5</td><td>3</td><td>9.7%</td></tr>
+<tr><td>Android 1.6</td><td>4</td><td>16.4%</td></tr>
+<tr><td>Android 2.1</td><td>7</td><td>40.4%</td></tr>
+<tr><td>Android 2.2</td><td>8</td><td>33.4%</td></tr>
</table>
-<p><em>Data collected during two weeks ending on September 1, 2010</em></p>
+<p><em>Data collected during two weeks ending on October 1, 2010</em></p>
<p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
</div><!-- end dashboard-panel -->
@@ -96,19 +96,19 @@
<div class="dashboard-panel">
<img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,y,r&chxr=0,0,12|1,0,100|2,0,100&
-chxl=0%3A%7C2010/03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/
-01%7C08/15%7C2010/09/01%7C1%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C2%3A%7C0%25%7C25%25%7C50%25
-%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.4,99.5,99.6,99.6,99.6,99.7,
-100.6,101.1,99.9,100.0,100.0,99.8,99.9|61.6,60.6,61.5,61.7,62.3,63.5,73.0,76.4,78.6,81.1,84.5,86.6,
-88.0|24.3,25.4,29.4,30.2,32.7,35.3,46.2,51.3,55.1,59.0,64.1,68.2,70.4|0.0,0.0,4.0,28.3,32.0,34.9,45.
-9,51.0,54.9,58.8,64.0,68.1,70.3|0.0,0.0,0.0,0.0,0.0,0.0,0.8,1.2,1.8,3.3,4.3,11.3,27.8&chm=tAndroid%
-201.5,7caa36,0,0,15,,t::-5|b,c3df9b,0,1,0|tAndroid%201.6,638d23,1,0,15,,t::-5|b,b0db6e,1,2,0|
-tAndroid%202.0.1,496c13,2,0,15,,t::-5|b,9ddb3d,2,3,0|tAndroid%202.1,2f4708,3,3,15,,t::-5|b,89cf19,3,
-4,0|tAndroid%202.2,131d02,4,11,15,,t::-5|B,6fad0c,4,5,0&chg=7,25&chdl=Android%201.5|Android%201.6|
-Android%202.0.1|Android%202.1|Android%202.2&chco=add274,9ad145,84c323,6ba213,507d08" />
+src="http://chart.apis.google.com/chart?cht=lc&chs=660x250&chxt=x,y,r&chxr=0,0,12|1,0,100|2,0,100&
+chxl=0:|2010/04/01|04/15|05/01|05/15|06/01|06/15|07/01|07/15|08/01|08/15|09/01|09/15|2010/10/01|1:|0
+%25|25%25|50%25|75%25|100%25|2:|0%25|25%25|50%25|75%25|100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&
+chxtc=0,5&chd=t:99.6,99.6,99.6,99.7,100.6,101.1,99.9,100.0,100.0,99.8,99.9,100.0,100.0|61.5,61.7,62.
+3,63.5,73.0,76.4,78.6,81.1,84.5,86.6,88.0,89.3,90.3|29.4,30.2,32.7,35.3,46.2,51.3,55.1,59.0,64.1,68.
+2,70.4,72.2,73.9|4.0,28.3,32.0,34.9,45.9,51.0,54.9,58.8,64.0,68.1,70.3,72.1,73.8|0.0,0.0,0.0,0.0,0.8
+,1.2,1.8,3.3,4.3,11.3,27.8,32.1,33.4&chm=tAndroid+1.5,7caa36,0,0,15,,t::-5|b,c3df9b,0,1,0|tAndroid+1
+.6,638d23,1,0,15,,t::-5|b,b0db6e,1,2,0|tAndroid+2.0.1,496c13,2,0,15,,t::-5|b,9ddb3d,2,3,0|tAndroid+2
+.1,2f4708,3,1,15,,t:-30:-40|b,89cf19,3,4,0|tAndroid+2.2,131d02,4,9,15,,t::-5|B,6fad0c,4,5,0&chg=7,25
+&chdl=Android+1.5|Android+1.6|Android+2.0.1|Android+2.1|Android+2.2&chco=add274,9ad145,84c323,6ba213
+,507d08" />
-<p><em>Last historical dataset collected during two weeks ending on September 1, 2010</em></p>
+<p><em>Last historical dataset collected during two weeks ending on October 1, 2010</em></p>
</div><!-- end dashboard-panel -->
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 9a19056..d283dea3 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -346,6 +346,15 @@
return b;
}
+ /**
+ * Creates a new bitmap, scaled from an existing bitmap.
+ *
+ * @param src The source bitmap.
+ * @param dstWidth The new bitmap's desired width.
+ * @param dstHeight The new bitmap's desired height.
+ * @param filter true if the source should be filtered.
+ * @return the new scaled bitmap.
+ */
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
int dstHeight, boolean filter) {
Matrix m;
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e75a19e..952f2b5 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1079,7 +1079,7 @@
*
* @param colors Array of colors representing the pixels of the bitmap
* @param offset Offset into the array of colors for the first pixel
- * @param stride The number of of colors in the array between rows (must be
+ * @param stride The number of colors in the array between rows (must be
* >= width or <= -width).
* @param x The X coordinate for where to draw the bitmap
* @param y The Y coordinate for where to draw the bitmap
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c3416a0..1324431 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -236,6 +236,7 @@
* @param y The y-coordinate of the end of a line
*/
public void lineTo(float x, float y) {
+ isSimplePath = false;
native_lineTo(mNativePath, x, y);
}
@@ -250,6 +251,7 @@
* this contour, to specify a line
*/
public void rLineTo(float dx, float dy) {
+ isSimplePath = false;
native_rLineTo(mNativePath, dx, dy);
}
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 604c602..a25fad4 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -26,10 +26,8 @@
import java.io.IOException;
/**
- * A specialized Drawable that fills the Canvas with a specified color,
- * with respect to the clip region. Note that a ColorDrawable ignores the ColorFilter.
- * It also ignores the Bounds, meaning it will draw everywhere in the current clip,
- * even if setBounds(...) was called with a smaller area.
+ * A specialized Drawable that fills the Canvas with a specified color.
+ * Note that a ColorDrawable ignores the ColorFilter.
*
* <p>It can be defined in an XML file with the <code><color></code> element.</p>
*
@@ -37,6 +35,7 @@
*/
public class ColorDrawable extends Drawable {
private ColorState mState;
+ private final Paint mPaint = new Paint();
/**
* Creates a new black ColorDrawable.
@@ -66,7 +65,10 @@
@Override
public void draw(Canvas canvas) {
- canvas.drawColor(mState.mUseColor);
+ if ((mState.mUseColor >>> 24) != 0) {
+ mPaint.setColor(mState.mUseColor);
+ canvas.drawRect(getBounds(), mPaint);
+ }
}
/**
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 05b2d60..91824e6 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -39,11 +39,11 @@
public enum DataType {
//FLOAT_16 (1, 2),
FLOAT_32 (2, 4),
- //FLOAT_64 (3, 8),
+ FLOAT_64 (3, 8),
SIGNED_8 (4, 1),
SIGNED_16 (5, 2),
SIGNED_32 (6, 4),
- //SIGNED_64 (7, 8),
+ SIGNED_64 (7, 8),
UNSIGNED_8 (8, 1),
UNSIGNED_16 (9, 2),
UNSIGNED_32 (10, 4),
@@ -142,6 +142,13 @@
return rs.mElement_I32;
}
+ public static Element I64(RenderScript rs) {
+ if(rs.mElement_I64 == null) {
+ rs.mElement_I64 = createUser(rs, DataType.SIGNED_64);
+ }
+ return rs.mElement_I64;
+ }
+
public static Element F32(RenderScript rs) {
if(rs.mElement_F32 == null) {
rs.mElement_F32 = createUser(rs, DataType.FLOAT_32);
@@ -149,6 +156,13 @@
return rs.mElement_F32;
}
+ public static Element F64(RenderScript rs) {
+ if(rs.mElement_F64 == null) {
+ rs.mElement_F64 = createUser(rs, DataType.FLOAT_64);
+ }
+ return rs.mElement_F64;
+ }
+
public static Element ELEMENT(RenderScript rs) {
if(rs.mElement_ELEMENT == null) {
rs.mElement_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b6f88be..ff3e22b 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -124,7 +124,7 @@
addI32(Float.floatToRawIntBits(v));
}
- public void addF64(float v) {
+ public void addF64(double v) {
addI64(Double.doubleToRawLongBits(v));
}
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index c4421c3..3c0b4e5 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -388,6 +388,10 @@
synchronized void nSamplerSet(int param, int value) {
rsnSamplerSet(mContext, param, value);
}
+ native void rsnSamplerSet2(int con, int param, float value);
+ synchronized void nSamplerSet2(int param, float value) {
+ rsnSamplerSet2(mContext, param, value);
+ }
native int rsnSamplerCreate(int con);
synchronized int nSamplerCreate() {
return rsnSamplerCreate(mContext);
@@ -497,7 +501,9 @@
Element mElement_I16;
Element mElement_U32;
Element mElement_I32;
+ Element mElement_I64;
Element mElement_F32;
+ Element mElement_F64;
Element mElement_BOOLEAN;
Element mElement_ELEMENT;
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 343fcdb..b627207 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -130,6 +130,7 @@
Value mWrapS;
Value mWrapT;
Value mWrapR;
+ float mAniso;
public Builder(RenderScript rs) {
mRS = rs;
@@ -138,6 +139,7 @@
mWrapS = Value.WRAP;
mWrapT = Value.WRAP;
mWrapR = Value.WRAP;
+ mAniso = 1.0f;
}
public void setMin(Value v) {
@@ -182,6 +184,14 @@
}
}
+ public void setAnisotropy(float v) {
+ if(v >= 0.0f) {
+ mAniso = v;
+ } else {
+ throw new IllegalArgumentException("Invalid value");
+ }
+ }
+
static synchronized Sampler internalCreate(RenderScript rs, Builder b) {
rs.nSamplerBegin();
rs.nSamplerSet(0, b.mMin.mID);
@@ -189,6 +199,7 @@
rs.nSamplerSet(2, b.mWrapS.mID);
rs.nSamplerSet(3, b.mWrapT.mID);
rs.nSamplerSet(4, b.mWrapR.mID);
+ rs.nSamplerSet2(5, b.mAniso);
int id = rs.nSamplerCreate();
return new Sampler(id, rs);
}
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 6aed11b..67a2b63 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -1096,6 +1096,13 @@
rsSamplerSet(con, (RsSamplerParam)p, (RsSamplerValue)v);
}
+static void
+nSamplerSet2(JNIEnv *_env, jobject _this, RsContext con, jint p, jfloat v)
+{
+ LOG_API("nSamplerSet2, con(%p), param(%i), value(%f)", con, p, v);
+ rsSamplerSet2(con, (RsSamplerParam)p, v);
+}
+
static jint
nSamplerCreate(JNIEnv *_env, jobject _this, RsContext con)
{
@@ -1303,6 +1310,7 @@
{"rsnSamplerBegin", "(I)V", (void*)nSamplerBegin },
{"rsnSamplerSet", "(III)V", (void*)nSamplerSet },
+{"rsnSamplerSet2", "(IIF)V", (void*)nSamplerSet2 },
{"rsnSamplerCreate", "(I)I", (void*)nSamplerCreate },
{"rsnMeshCreate", "(III)I", (void*)nMeshCreate },
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 705b101..60031a4 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -59,6 +59,27 @@
void setPreviewSize(int width, int height);
void getPreviewSize(int *width, int *height) const;
void getSupportedPreviewSizes(Vector<Size> &sizes) const;
+
+ // Set the dimensions in pixels to the given width and height
+ // for video frames. The given width and height must be one
+ // of the supported dimensions returned from
+ // getSupportedVideoSizes(). Must not be called if
+ // getSupportedVideoSizes() returns an empty Vector of Size.
+ void setVideoSize(int width, int height);
+ // Retrieve the current dimensions (width and height)
+ // in pixels for video frames, which must be one of the
+ // supported dimensions returned from getSupportedVideoSizes().
+ // Must not be called if getSupportedVideoSizes() returns an
+ // empty Vector of Size.
+ void getVideoSize(int *width, int *height) const;
+ // Retrieve a Vector of supported dimensions (width and height)
+ // in pixels for video frames. If sizes returned from the method
+ // is empty, the camera does not support calls to setVideoSize()
+ // or getVideoSize(). In adddition, it also indicates that
+ // the camera only has a single output, and does not have
+ // separate output for video frames and preview frame.
+ void getSupportedVideoSizes(Vector<Size> &sizes) const;
+
void setPreviewFrameRate(int fps);
int getPreviewFrameRate() const;
void getPreviewFpsRange(int *min_fps, int *max_fps) const;
@@ -281,6 +302,16 @@
// Example value: "0.95,1.9,Infinity" or "0.049,0.05,0.051". Read only.
static const char KEY_FOCUS_DISTANCES[];
+ // The current dimensions in pixels (width x height) for video frames.
+ // The width and height must be one of the supported sizes retrieved
+ // via KEY_SUPPORTED_VIDEO_SIZES.
+ // Example value: "1280x720". Read/write.
+ static const char KEY_VIDEO_SIZE[];
+ // A list of the supported dimensions in pixels (width x height)
+ // for video frames. See CAMERA_MSG_VIDEO_FRAME for details in
+ // frameworks/base/include/camera/Camera.h.
+ // Example: "176x144,1280x720". Read only.
+ static const char KEY_SUPPORTED_VIDEO_SIZES[];
// The image format for video frames. See CAMERA_MSG_VIDEO_FRAME in
// frameworks/base/include/camera/Camera.h.
// Example value: "yuv420sp" or PIXEL_FORMAT_XXX constants. Read only.
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 1510f87..c6990bf 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -42,8 +42,11 @@
#define CBLK_FORCEREADY_ON 0x0004 // track is considered ready immediately by AudioFlinger
#define CBLK_FORCEREADY_OFF 0x0000 // track is ready when buffer full
#define CBLK_INVALID_MSK 0x0008
-#define CBLK_INVALID_ON 0x0008 // track buffer is invalidated by AudioFlinger: must be re-created
-#define CBLK_INVALID_OFF 0x0000
+#define CBLK_INVALID_ON 0x0008 // track buffer is invalidated by AudioFlinger:
+#define CBLK_INVALID_OFF 0x0000 // must be re-created
+#define CBLK_DISABLED_MSK 0x0010
+#define CBLK_DISABLED_ON 0x0010 // track disabled by AudioFlinger due to underrun:
+#define CBLK_DISABLED_OFF 0x0000 // must be re-started
struct audio_track_cblk_t
{
diff --git a/include/surfaceflinger/ISurfaceComposer.h b/include/surfaceflinger/ISurfaceComposer.h
index 76307b2..6533600 100644
--- a/include/surfaceflinger/ISurfaceComposer.h
+++ b/include/surfaceflinger/ISurfaceComposer.h
@@ -115,7 +115,8 @@
*/
virtual status_t captureScreen(DisplayID dpy,
sp<IMemoryHeap>* heap,
- uint32_t* width, uint32_t* height, PixelFormat* format) = 0;
+ uint32_t* width, uint32_t* height, PixelFormat* format,
+ uint32_t reqWidth, uint32_t reqHeight) = 0;
/* Signal surfaceflinger that there might be some work to do
* This is an ASYNCHRONOUS call.
diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h
index 8773d713..a80832d 100644
--- a/include/surfaceflinger/SurfaceComposerClient.h
+++ b/include/surfaceflinger/SurfaceComposerClient.h
@@ -170,6 +170,36 @@
};
// ---------------------------------------------------------------------------
+
+class ScreenshotClient
+{
+ sp<IMemoryHeap> mHeap;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ PixelFormat mFormat;
+public:
+ ScreenshotClient();
+
+ // frees the previous screenshot and capture a new one
+ status_t update();
+ status_t update(uint32_t reqWidth, uint32_t reqHeight);
+
+ // release memory occupied by the screenshot
+ void release();
+
+ // pixels are valid until this object is freed or
+ // release() or update() is called
+ void const* getPixels() const;
+
+ uint32_t getWidth() const;
+ uint32_t getHeight() const;
+ PixelFormat getFormat() const;
+ uint32_t getStride() const;
+ // size of allocated memory in bytes
+ size_t getSize() const;
+};
+
+// ---------------------------------------------------------------------------
}; // namespace android
#endif // ANDROID_SF_SURFACE_COMPOSER_CLIENT_H
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index d6b09dc..d78e35f 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -142,8 +142,13 @@
public:
// Synthetic raw event type codes produced when devices are added or removed.
enum {
+ // Sent when a device is added.
DEVICE_ADDED = 0x10000000,
- DEVICE_REMOVED = 0x20000000
+ // Sent when a device is removed.
+ DEVICE_REMOVED = 0x20000000,
+ // Sent when all added/removed devices from the most recent scan have been reported.
+ // This event is always sent at least once.
+ FINISHED_DEVICE_SCAN = 0x30000000,
};
virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
@@ -181,6 +186,8 @@
*/
virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags) const = 0;
+
+ virtual void dump(String8& dump) = 0;
};
class EventHub : public EventHubInterface
@@ -211,16 +218,18 @@
virtual bool getEvent(RawEvent* outEvent);
+ virtual void dump(String8& dump);
+
protected:
virtual ~EventHub();
private:
bool openPlatformInput(void);
- int open_device(const char *device);
- int close_device(const char *device);
- int scan_dir(const char *dirname);
- int read_notify(int nfd);
+ int openDevice(const char *device);
+ int closeDevice(const char *device);
+ int scanDir(const char *dirname);
+ int readNotify(int nfd);
status_t mError;
@@ -239,8 +248,8 @@
~device_t();
};
- device_t* getDevice(int32_t deviceId) const;
- bool hasKeycode(device_t* device, int keycode) const;
+ device_t* getDeviceLocked(int32_t deviceId) const;
+ bool hasKeycodeLocked(device_t* device, int keycode) const;
int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const;
int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const;
@@ -269,6 +278,7 @@
int mFDCount;
bool mOpened;
+ bool mNeedToSendFinishedDeviceScan;
List<String8> mExcludedDevices;
// device ids that report particular switches.
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 21baf32..ee40b85 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -73,7 +73,8 @@
* policy decisions such as waking from device sleep.
*/
enum {
- /* These flags originate in RawEvents and are generally set in the key map. */
+ /* These flags originate in RawEvents and are generally set in the key map.
+ * See also labels for policy flags in KeycodeLabels.h. */
POLICY_FLAG_WAKE = 0x00000001,
POLICY_FLAG_WAKE_DROPPED = 0x00000002,
@@ -83,6 +84,7 @@
POLICY_FLAG_ALT_GR = 0x00000020,
POLICY_FLAG_MENU = 0x00000040,
POLICY_FLAG_LAUNCHER = 0x00000080,
+ POLICY_FLAG_VIRTUAL = 0x00000100,
POLICY_FLAG_RAW_MASK = 0x0000ffff,
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index e85735a..3619189 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -103,10 +103,6 @@
virtual bool getDisplayInfo(int32_t displayId,
int32_t* width, int32_t* height, int32_t* orientation) = 0;
- /* Provides feedback for a virtual key down.
- */
- virtual void virtualKeyDownFeedback() = 0;
-
/* Intercepts a key event.
* The policy can use this method as an opportunity to perform power management functions
* and early event preprocessing such as updating policy flags.
@@ -283,14 +279,14 @@
// low-level input event decoding and device management
void process(const RawEvent* rawEvent);
- void addDevice(nsecs_t when, int32_t deviceId);
- void removeDevice(nsecs_t when, int32_t deviceId);
+ void addDevice(int32_t deviceId);
+ void removeDevice(int32_t deviceId);
InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
void configureExcludedDevices();
void consumeEvent(const RawEvent* rawEvent);
- void handleConfigurationChanged(nsecs_t when);
+ void handleConfigurationChanged();
// state management for all devices
Mutex mStateLock;
@@ -308,9 +304,6 @@
GetStateFunc getStateFunc);
bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
-
- // dump state
- void dumpDeviceInfo(String8& dump);
};
@@ -340,6 +333,7 @@
inline bool isIgnored() { return mMappers.isEmpty(); }
+ void dump(String8& dump);
void addMapper(InputMapper* mapper);
void configure();
void reset();
@@ -393,6 +387,7 @@
virtual uint32_t getSources() = 0;
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
virtual void configure();
virtual void reset();
virtual void process(const RawEvent* rawEvent) = 0;
@@ -436,6 +431,7 @@
virtual uint32_t getSources();
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
virtual void reset();
virtual void process(const RawEvent* rawEvent);
@@ -484,6 +480,7 @@
virtual uint32_t getSources();
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
virtual void reset();
virtual void process(const RawEvent* rawEvent);
@@ -540,6 +537,7 @@
virtual uint32_t getSources();
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
virtual void configure();
virtual void reset();
@@ -761,15 +759,16 @@
} mLocked;
virtual void configureParameters();
- virtual void logParameters();
+ virtual void dumpParameters(String8& dump);
virtual void configureRawAxes();
- virtual void logRawAxes();
+ virtual void dumpRawAxes(String8& dump);
virtual bool configureSurfaceLocked();
- virtual void logMotionRangesLocked();
+ virtual void dumpSurfaceLocked(String8& dump);
virtual void configureVirtualKeysLocked();
+ virtual void dumpVirtualKeysLocked(String8& dump);
virtual void parseCalibration();
virtual void resolveCalibration();
- virtual void logCalibration();
+ virtual void dumpCalibration(String8& dump);
enum TouchResult {
// Dispatch the touch normally.
diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h
index c8d6ffc..f71d9cd 100755
--- a/include/ui/KeycodeLabels.h
+++ b/include/ui/KeycodeLabels.h
@@ -142,6 +142,7 @@
{ NULL, 0 }
};
+// See also policy flags in Input.h.
static const KeycodeLabel FLAGS[] = {
{ "WAKE", 0x00000001 },
{ "WAKE_DROPPED", 0x00000002 },
@@ -151,6 +152,7 @@
{ "ALT_GR", 0x00000020 },
{ "MENU", 0x00000040 },
{ "LAUNCHER", 0x00000080 },
+ { "VIRTUAL", 0x00000100 },
{ NULL, 0 }
};
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index 9668bde..e1ff780 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -64,15 +64,8 @@
mNumEntries(-1), mDirectoryOffset(-1),
mHashTableSize(-1), mHashTable(NULL)
{}
- ~ZipFileRO() {
- free(mHashTable);
- if (mDirectoryMap)
- mDirectoryMap->release();
- if (mFd >= 0)
- close(mFd);
- if (mFileName)
- free(mFileName);
- }
+
+ ~ZipFileRO();
/*
* Open an archive.
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index af58f77..45b1b9a 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -73,6 +73,8 @@
const char CameraParameters::KEY_SMOOTH_ZOOM_SUPPORTED[] = "smooth-zoom-supported";
const char CameraParameters::KEY_FOCUS_DISTANCES[] = "focus-distances";
const char CameraParameters::KEY_VIDEO_FRAME_FORMAT[] = "video-frame-format";
+const char CameraParameters::KEY_VIDEO_SIZE[] = "video-size";
+const char CameraParameters::KEY_SUPPORTED_VIDEO_SIZES[] = "video-size-values";
const char CameraParameters::TRUE[] = "true";
const char CameraParameters::FOCUS_DISTANCE_INFINITY[] = "Infinity";
@@ -337,6 +339,27 @@
parseSizesList(previewSizesStr, sizes);
}
+void CameraParameters::setVideoSize(int width, int height)
+{
+ char str[32];
+ sprintf(str, "%dx%d", width, height);
+ set(KEY_VIDEO_SIZE, str);
+}
+
+void CameraParameters::getVideoSize(int *width, int *height) const
+{
+ *width = *height = -1;
+ const char *p = get(KEY_VIDEO_SIZE);
+ if (p == 0) return;
+ parse_pair(p, width, height, 'x');
+}
+
+void CameraParameters::getSupportedVideoSizes(Vector<Size> &sizes) const
+{
+ const char *videoSizesStr = get(KEY_SUPPORTED_VIDEO_SIZES);
+ parseSizesList(videoSizesStr, sizes);
+}
+
void CameraParameters::setPreviewFrameRate(int fps)
{
set(KEY_PREVIEW_FRAME_RATE, fps);
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index a4933c0..2952a66 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -65,8 +65,8 @@
PathCache pathCache;
PatchCache patchCache;
TextDropShadowCache dropShadowCache;
- GammaFontRenderer fontRenderer;
FboCache fboCache;
+ GammaFontRenderer fontRenderer;
Line line;
}; // class Caches
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index ee90702..ce85d46 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -22,6 +22,62 @@
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define PATH_HEAP_SIZE 64
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+PathHeap::PathHeap(): mHeap(PATH_HEAP_SIZE * sizeof(SkPath)) {
+}
+
+PathHeap::PathHeap(SkFlattenableReadBuffer& buffer): mHeap(PATH_HEAP_SIZE * sizeof(SkPath)) {
+ int count = buffer.readS32();
+
+ mPaths.setCount(count);
+ SkPath** ptr = mPaths.begin();
+ SkPath* p = (SkPath*) mHeap.allocThrow(count * sizeof(SkPath));
+
+ for (int i = 0; i < count; i++) {
+ new (p) SkPath;
+ p->unflatten(buffer);
+ *ptr++ = p;
+ p++;
+ }
+}
+
+PathHeap::~PathHeap() {
+ SkPath** iter = mPaths.begin();
+ SkPath** stop = mPaths.end();
+ while (iter < stop) {
+ (*iter)->~SkPath();
+ iter++;
+ }
+}
+
+int PathHeap::append(const SkPath& path) {
+ SkPath* p = (SkPath*) mHeap.allocThrow(sizeof(SkPath));
+ new (p) SkPath(path);
+ *mPaths.append() = p;
+ return mPaths.count();
+}
+
+void PathHeap::flatten(SkFlattenableWriteBuffer& buffer) const {
+ int count = mPaths.count();
+
+ buffer.write32(count);
+ SkPath** iter = mPaths.begin();
+ SkPath** stop = mPaths.end();
+ while (iter < stop) {
+ (*iter)->flatten(buffer);
+ iter++;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 735f0e7..5d02bd7 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -45,39 +45,11 @@
class PathHeap: public SkRefCnt {
public:
- PathHeap(): mHeap(64 * sizeof(SkPath)) {
- };
+ PathHeap();
+ PathHeap(SkFlattenableReadBuffer& buffer);
+ ~PathHeap();
- PathHeap(SkFlattenableReadBuffer& buffer): mHeap(64 * sizeof(SkPath)) {
- int count = buffer.readS32();
-
- mPaths.setCount(count);
- SkPath** ptr = mPaths.begin();
- SkPath* p = (SkPath*) mHeap.allocThrow(count * sizeof(SkPath));
-
- for (int i = 0; i < count; i++) {
- new (p) SkPath;
- p->unflatten(buffer);
- *ptr++ = p;
- p++;
- }
- }
-
- ~PathHeap() {
- SkPath** iter = mPaths.begin();
- SkPath** stop = mPaths.end();
- while (iter < stop) {
- (*iter)->~SkPath();
- iter++;
- }
- }
-
- int append(const SkPath& path) {
- SkPath* p = (SkPath*) mHeap.allocThrow(sizeof(SkPath));
- new (p) SkPath(path);
- *mPaths.append() = p;
- return mPaths.count();
- }
+ int append(const SkPath& path);
int count() const { return mPaths.count(); }
@@ -85,17 +57,7 @@
return *mPaths[index];
}
- void flatten(SkFlattenableWriteBuffer& buffer) const {
- int count = mPaths.count();
-
- buffer.write32(count);
- SkPath** iter = mPaths.begin();
- SkPath** stop = mPaths.end();
- while (iter < stop) {
- (*iter)->flatten(buffer);
- iter++;
- }
- }
+ void flatten(SkFlattenableWriteBuffer& buffer) const;
private:
SkChunkAlloc mHeap;
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index b66696d..8389e56 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -426,6 +426,8 @@
void FontRenderer::initTextTexture() {
mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
+ memset(mTextTexture, 0, mCacheWidth * mCacheHeight * sizeof(uint8_t));
+
mUploadTexture = false;
glGenTextures(1, &mTextureId);
@@ -435,6 +437,7 @@
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+ mLinearFiltering = false;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -443,17 +446,17 @@
// Split up our cache texture into lines of certain widths
int nextLine = 0;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 18, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
- mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0));
+ mCacheLines.push(new CacheTextureLine(mCacheWidth, 42, nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
}
@@ -631,6 +634,7 @@
precacheLatin(paint);
}
}
+
FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
checkInit();
@@ -713,7 +717,7 @@
float normalizeFactor = 0.0f;
for(int32_t r = -radius; r <= radius; r ++) {
- float floatR = (float)r;
+ float floatR = (float) r;
weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
normalizeFactor += weights[r + radius];
}
@@ -742,7 +746,7 @@
if ((x > radius) && (x < (width - radius))) {
const uint8_t *i = input + (x - radius);
for(int r = -radius; r <= radius; r ++) {
- currentPixel = (float)(*i);
+ currentPixel = (float) (*i);
blurredPixel += currentPixel * gPtr[0];
gPtr++;
i++;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index de5c019..f10efad 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -156,8 +156,16 @@
DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, uint32_t radius);
- GLuint getTexture() {
+ GLuint getTexture(bool linearFiltering = false) {
checkInit();
+ if (linearFiltering != mLinearFiltering) {
+ mLinearFiltering = linearFiltering;
+ const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
+
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ }
return mTextureId;
}
@@ -183,14 +191,14 @@
}
bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
- if (glyph.fHeight > mMaxHeight) {
+ if (glyph.fHeight + 2 > mMaxHeight) {
return false;
}
- if (mCurrentCol + glyph.fWidth < mMaxWidth) {
- *retOriginX = mCurrentCol;
- *retOriginY = mCurrentRow;
- mCurrentCol += glyph.fWidth;
+ if (mCurrentCol + glyph.fWidth + 2 < mMaxWidth) {
+ *retOriginX = mCurrentCol + 1;
+ *retOriginY = mCurrentRow + 1;
+ mCurrentCol += glyph.fWidth + 2;
mDirty = true;
return true;
}
@@ -252,6 +260,8 @@
bool mInitialized;
+ bool mLinearFiltering;
+
void computeGaussianWeights(float* weights, int32_t radius);
void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
int32_t width, int32_t height);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 60523db..a0cc5d6 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -44,7 +44,7 @@
uint32_t id;
bool operator<(const LayerSize& rhs) const {
- if (id != 0 && rhs.id != 0) {
+ if (id != 0 && rhs.id != 0 && id != rhs.id) {
return id < rhs.id;
}
if (width == rhs.width) {
@@ -54,7 +54,7 @@
}
bool operator==(const LayerSize& rhs) const {
- return width == rhs.width && height == rhs.height;
+ return id == rhs.id && width == rhs.width && height == rhs.height;
}
}; // struct LayerSize
@@ -83,7 +83,7 @@
*/
bool blend;
/**
- * Indicates that this layer has never been used before.
+ * Indicates whether this layer has been used already.
*/
bool empty;
}; // struct Layer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 2770868..8c70cf9 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -114,6 +114,8 @@
glGenTextures(1, &layer->texture);
glBindTexture(GL_TEXTURE_2D, layer->texture);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index c698b5a..219fd5e 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -53,6 +53,15 @@
mSimpleMatrix = true;
}
+#define EPSILON 0.00001f
+#define almost(u, v) (fabs((u) - (v)) < EPSILON)
+
+bool Matrix4::changesBounds() {
+ return !(almost(data[0], 1.0f) && almost(data[1], 0.0f) && almost(data[2], 0.0f) &&
+ almost(data[4], 0.0f) && almost(data[5], 1.0f) && almost(data[6], 0.0f) &&
+ almost(data[8], 0.0f) && almost(data[9], 0.0f) && almost(data[10], 1.0f));
+}
+
void Matrix4::load(const float* v) {
memcpy(data, v, sizeof(data));
mSimpleMatrix = false;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 0608efe..fe81159 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -103,6 +103,8 @@
multiply(u);
}
+ bool changesBounds();
+
void copyTo(float* v) const;
void copyTo(SkMatrix& v) const;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 23de3a5..e3790f5c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -113,8 +113,6 @@
///////////////////////////////////////////////////////////////////////////////
OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()) {
- LOGD("Create OpenGLRenderer");
-
mShader = NULL;
mColorFilter = NULL;
mHasShadow = false;
@@ -133,7 +131,6 @@
}
OpenGLRenderer::~OpenGLRenderer() {
- LOGD("Destroy OpenGLRenderer");
// The context has already been destroyed at this point, do not call
// GL APIs. All GL state should be kept in Caches.h
}
@@ -366,6 +363,8 @@
return false;
}
+ glActiveTexture(GL_TEXTURE0);
+
LayerSize size(bounds.getWidth(), bounds.getHeight());
Layer* layer = mCaches.layerCache.get(size);
if (!layer) {
@@ -383,17 +382,22 @@
// Copy the framebuffer into the layer
glBindTexture(GL_TEXTURE_2D, layer->texture);
- if (layer->empty) {
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
- bounds.getWidth(), bounds.getHeight(), 0);
- layer->empty = false;
- } else {
- glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
- bounds.getWidth(), bounds.getHeight());
- }
+ // TODO: Workaround for b/3054204
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
- if (flags & SkCanvas::kClipToLayer_SaveFlag) {
- if (mSnapshot->clipTransformed(bounds)) setScissorFromClip();
+ // TODO: Waiting for b/3054204 to be fixed
+// if (layer->empty) {
+// glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+// bounds.getWidth(), bounds.getHeight(), 0);
+// layer->empty = false;
+// } else {
+// glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+// bounds.getWidth(), bounds.getHeight());
+// }
+
+ if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
+ setScissorFromClip();
}
// Enqueue the buffer coordinates to clear the corresponding region later
@@ -760,8 +764,10 @@
GLuint textureUnit = 0;
glActiveTexture(gTextureUnits[textureUnit]);
- setupTextureAlpha8(fontRenderer.getTexture(), 0, 0, textureUnit, x, y, r, g, b, a,
- mode, false, true);
+ // Assume that the modelView matrix does not force scales, rotates, etc.
+ const bool linearFilter = mSnapshot->transform->changesBounds();
+ setupTextureAlpha8(fontRenderer.getTexture(linearFilter), 0, 0, textureUnit,
+ x, y, r, g, b, a, mode, false, true);
const Rect& clip = mSnapshot->getLocalClip();
clearLayerRegions();
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 70e06a1..377727b 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -138,8 +138,8 @@
const SkPath *path, const SkPaint* paint) {
const SkRect& bounds = path->getBounds();
- const float pathWidth = bounds.width();
- const float pathHeight = bounds.height();
+ const float pathWidth = fmax(bounds.width(), 1.0f);
+ const float pathHeight = fmax(bounds.height(), 1.0f);
if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
LOGW("Path too large to be rendered into a texture");
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 062c986..c736a1c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -141,6 +141,7 @@
}
if (clipped) {
+ clipRect->snapToPixelBoundaries();
flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
}
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 7902b9a..13ae1fb 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -122,7 +122,8 @@
RS_SAMPLER_MAG_FILTER,
RS_SAMPLER_WRAP_S,
RS_SAMPLER_WRAP_T,
- RS_SAMPLER_WRAP_R
+ RS_SAMPLER_WRAP_R,
+ RS_SAMPLER_ANISO
};
enum RsSamplerValue {
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
index 33945a5..f5fecba 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
@@ -65,7 +65,6 @@
static void copyInput() {
- RS_DEBUG_MARKER;
rs_allocation ain = rsGetAllocation(InPixel);
uint32_t dimx = rsAllocationGetDimX(ain);
uint32_t dimy = rsAllocationGetDimY(ain);
@@ -74,7 +73,6 @@
ScratchPixel1[x + y * dimx] = convert_float4(InPixel[x + y * dimx]);
}
}
- RS_DEBUG_MARKER;
}
void filter() {
diff --git a/libs/rs/java/Samples/res/drawable/checker.png b/libs/rs/java/Samples/res/drawable/checker.png
new file mode 100644
index 0000000..b631e1e
--- /dev/null
+++ b/libs/rs/java/Samples/res/drawable/checker.png
Binary files differ
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index d4e83d3..a15c4a1 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -19,11 +19,12 @@
import java.io.Writer;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.renderscript.*;
import android.renderscript.ProgramStore.DepthFunc;
+import android.renderscript.Sampler.Value;
import android.util.Log;
-import android.graphics.BitmapFactory;
-import android.graphics.Bitmap;
public class RsRenderStatesRS {
@@ -42,7 +43,7 @@
mOptionsARGB.inScaled = false;
mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
mMode = 0;
- mMaxModes = 8;
+ mMaxModes = 9;
initRS();
}
@@ -53,6 +54,8 @@
private Sampler mLinearWrap;
private Sampler mMipLinearWrap;
private Sampler mNearestClamp;
+ private Sampler mMipLinearAniso8;
+ private Sampler mMipLinearAniso15;
private ProgramStore mProgStoreBlendNoneDepth;
private ProgramStore mProgStoreBlendNone;
@@ -74,10 +77,12 @@
private ProgramRaster mCullBack;
private ProgramRaster mCullFront;
+ private ProgramRaster mCullNone;
private Allocation mTexTorus;
private Allocation mTexOpaque;
private Allocation mTexTransparent;
+ private Allocation mTexChecker;
private Allocation mAllocPV;
@@ -243,10 +248,12 @@
mTexTorus = loadTextureRGB(R.drawable.torusmap);
mTexOpaque = loadTextureRGB(R.drawable.data);
mTexTransparent = loadTextureARGB(R.drawable.leaf);
+ mTexChecker = loadTextureRGB(R.drawable.checker);
mScript.set_gTexTorus(mTexTorus);
mScript.set_gTexOpaque(mTexOpaque);
mScript.set_gTexTransparent(mTexTransparent);
+ mScript.set_gTexChecker(mTexChecker);
}
private void initFonts() {
@@ -295,18 +302,32 @@
mNearestClamp = Sampler.CLAMP_NEAREST(mRS);
mMipLinearWrap = Sampler.WRAP_LINEAR_MIP_LINEAR(mRS);
+ bs = new Sampler.Builder(mRS);
+ bs.setMin(Sampler.Value.LINEAR_MIP_LINEAR);
+ bs.setMag(Sampler.Value.LINEAR);
+ bs.setWrapS(Sampler.Value.WRAP);
+ bs.setWrapT(Sampler.Value.WRAP);
+ bs.setAnisotropy(8.0f);
+ mMipLinearAniso8 = bs.create();
+ bs.setAnisotropy(15.0f);
+ mMipLinearAniso15 = bs.create();
+
mScript.set_gLinearClamp(mLinearClamp);
mScript.set_gLinearWrap(mLinearWrap);
mScript.set_gMipLinearWrap(mMipLinearWrap);
+ mScript.set_gMipLinearAniso8(mMipLinearAniso8);
+ mScript.set_gMipLinearAniso15(mMipLinearAniso15);
mScript.set_gNearestClamp(mNearestClamp);
}
private void initProgramRaster() {
mCullBack = ProgramRaster.CULL_BACK(mRS);
mCullFront = ProgramRaster.CULL_FRONT(mRS);
+ mCullNone = ProgramRaster.CULL_NONE(mRS);
mScript.set_gCullBack(mCullBack);
mScript.set_gCullFront(mCullFront);
+ mScript.set_gCullNone(mCullNone);
}
private void initRS() {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index 659e1e4..b471504 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -31,6 +31,7 @@
rs_allocation gTexOpaque;
rs_allocation gTexTorus;
rs_allocation gTexTransparent;
+rs_allocation gTexChecker;
rs_mesh gMbyNMesh;
rs_mesh gTorusMesh;
@@ -47,10 +48,13 @@
rs_sampler gLinearClamp;
rs_sampler gLinearWrap;
rs_sampler gMipLinearWrap;
+rs_sampler gMipLinearAniso8;
+rs_sampler gMipLinearAniso15;
rs_sampler gNearestClamp;
rs_program_raster gCullBack;
rs_program_raster gCullFront;
+rs_program_raster gCullNone;
// Custom vertex shader compunents
VertexShaderConstants *gVSConstants;
@@ -64,11 +68,11 @@
#pragma rs export_var(gProgVertex, gProgFragmentColor, gProgFragmentTexture)
#pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
-#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent)
+#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
#pragma rs export_var(gMbyNMesh, gTorusMesh)
#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
-#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gNearestClamp)
-#pragma rs export_var(gCullBack, gCullFront)
+#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
+#pragma rs export_var(gCullBack, gCullFront, gCullNone)
#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
//What we are showing
@@ -110,7 +114,7 @@
rsgBindProgramVertex(gProgVertex);
// Setup the projectioni matrix
rs_matrix4x4 proj;
- rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -1,1);
+ rsMatrixLoadOrtho(&proj, 0, rsgGetWidth(), rsgGetHeight(), 0, -500, 500);
rsgProgramVertexLoadProjectionMatrix(&proj);
}
@@ -276,14 +280,12 @@
startX + width, startY + height, 0, 1.5, 1.5,
startX + width, startY, 0, 1.5, 0);
-
rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
rsgBindFont(gFontMono);
rsgDrawText("Filtering: linear clamp", 10, 290);
rsgDrawText("Filtering: linear wrap", 10, 590);
rsgDrawText("Filtering: nearest clamp", 310, 290);
rsgDrawText("Filtering: miplinear wrap", 310, 590);
-
}
float gTorusRotation = 0;
@@ -430,7 +432,7 @@
rsgBindSampler(gProgFragmentMultitex, 0, gLinearClamp);
rsgBindSampler(gProgFragmentMultitex, 1, gLinearWrap);
rsgBindSampler(gProgFragmentMultitex, 2, gLinearClamp);
- rsgBindTexture(gProgFragmentMultitex, 0, gTexOpaque);
+ rsgBindTexture(gProgFragmentMultitex, 0, gTexChecker);
rsgBindTexture(gProgFragmentMultitex, 1, gTexTorus);
rsgBindTexture(gProgFragmentMultitex, 2, gTexTransparent);
@@ -446,6 +448,69 @@
rsgDrawText("Custom shader with multitexturing", 10, 280);
}
+float gAnisoTime = 0.0f;
+uint anisoMode = 0;
+void displayAnisoSample() {
+
+ gAnisoTime += gDt;
+
+ rsgBindProgramVertex(gProgVertex);
+ float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+ rs_matrix4x4 proj;
+ rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+ rsgProgramVertexLoadProjectionMatrix(&proj);
+
+ rs_matrix4x4 matrix;
+ // Fragment shader with texture
+ rsgBindProgramStore(gProgStoreBlendNone);
+ rsgBindProgramFragment(gProgFragmentTexture);
+ rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -10.0f);
+ rsMatrixRotate(&matrix, -80, 1.0f, 0.0f, 0.0f);
+ rsgProgramVertexLoadModelMatrix(&matrix);
+
+ rsgBindProgramRaster(gCullNone);
+
+ rsgBindTexture(gProgFragmentTexture, 0, gTexChecker);
+
+ if(gAnisoTime >= 5.0f) {
+ gAnisoTime = 0.0f;
+ anisoMode ++;
+ anisoMode = anisoMode % 3;
+ }
+
+ if(anisoMode == 0) {
+ rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso8);
+ }
+ else if(anisoMode == 1) {
+ rsgBindSampler(gProgFragmentTexture, 0, gMipLinearAniso15);
+ }
+ else {
+ rsgBindSampler(gProgFragmentTexture, 0, gMipLinearWrap);
+ }
+
+ float startX = -15;
+ float startY = -15;
+ float width = 30;
+ float height = 30;
+ rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+ startX, startY + height, 0, 0, 10,
+ startX + width, startY + height, 0, 10, 10,
+ startX + width, startY, 0, 10, 0);
+
+ rsgBindProgramRaster(gCullBack);
+
+ rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+ rsgBindFont(gFontMono);
+ if(anisoMode == 0) {
+ rsgDrawText("Anisotropic filtering 8", 10, 40);
+ }
+ else if(anisoMode == 1) {
+ rsgDrawText("Anisotropic filtering 15", 10, 40);
+ }
+ else {
+ rsgDrawText("Miplinear filtering", 10, 40);
+ }
+}
int root(int launchID) {
@@ -479,6 +544,9 @@
case 7:
displayMultitextureSample();
break;
+ case 8:
+ displayAnisoSample();
+ break;
}
return 10;
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
index dbc9133..835dea2 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestCore.java
@@ -21,6 +21,8 @@
import android.util.Log;
import java.util.ArrayList;
import java.util.ListIterator;
+import java.util.Timer;
+import java.util.TimerTask;
public class RSTestCore {
@@ -42,12 +44,18 @@
private ArrayList<UnitTest> unitTests;
private ListIterator<UnitTest> test_iter;
private UnitTest activeTest;
+ private boolean stopTesting;
+
+ /* Periodic timer for ensuring future tests get scheduled */
+ private Timer mTimer;
+ public static final int RS_TIMER_PERIOD = 100;
public void init(RenderScriptGL rs, Resources res, int width, int height) {
mRS = rs;
mRes = res;
mWidth = width;
mHeight = height;
+ stopTesting = false;
mScript = new ScriptC_rslist(mRS, mRes, R.raw.rslist, true);
@@ -88,9 +96,17 @@
test_iter = unitTests.listIterator();
refreshTestResults(); /* Kick off the first test */
+
+ TimerTask pTask = new TimerTask() {
+ public void run() {
+ refreshTestResults();
+ }
+ };
+
+ mTimer = new Timer();
+ mTimer.schedule(pTask, RS_TIMER_PERIOD, RS_TIMER_PERIOD);
}
- static int count = 0;
public void checkAndRunNextTest() {
if (activeTest != null) {
if (!activeTest.isAlive()) {
@@ -104,7 +120,7 @@
}
}
- if (activeTest == null) {
+ if (!stopTesting && activeTest == null) {
if (test_iter.hasNext()) {
activeTest = test_iter.next();
activeTest.start();
@@ -112,8 +128,14 @@
* should start running. The message handler in UnitTest.java
* ensures this. */
}
+ else {
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer.purge();
+ mTimer = null;
+ }
+ }
}
- count++;
}
public void refreshTestResults() {
@@ -127,6 +149,29 @@
}
}
+ public void cleanup() {
+ stopTesting = true;
+ UnitTest t = activeTest;
+
+ /* Stop periodic refresh of testing */
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer.purge();
+ mTimer = null;
+ }
+
+ /* Wait to exit until we finish the current test */
+ if (t != null) {
+ try {
+ t.join();
+ }
+ catch (InterruptedException e) {
+ }
+ t = null;
+ }
+
+ }
+
public void newTouchPosition(float x, float y, float pressure, int id) {
}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
index ce99c6d..b811d48 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/RSTestView.java
@@ -62,6 +62,7 @@
@Override
protected void onDetachedFromWindow() {
if(mRS != null) {
+ mRender.cleanup();
mRS = null;
destroyRenderScript();
}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
index 8391fb3..9d57e90 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_fp_mad.java
@@ -33,6 +33,7 @@
pRS.mMessageCallback = mRsMessage;
s.invoke_fp_mad_test(0, 0);
pRS.finish();
+ waitForMessage();
pRS.destroy();
}
}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
index bef6ec5..fb355dd 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UT_primitives.java
@@ -33,6 +33,7 @@
pRS.mMessageCallback = mRsMessage;
s.invoke_primitives_test(0, 0);
pRS.finish();
+ waitForMessage();
pRS.destroy();
}
}
diff --git a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
index 5eb0d67..c9d88a6 100644
--- a/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
+++ b/libs/rs/java/tests/src/com/android/rs/test/UnitTest.java
@@ -23,6 +23,7 @@
public int result;
private ScriptField_ListAllocs_s.Item mItem;
private RSTestCore mRSTC;
+ private boolean msgHandled;
/* These constants must match those in shared.rsh */
public static final int RS_MSG_TEST_PASSED = 100;
@@ -35,6 +36,7 @@
super();
mRSTC = rstc;
name = n;
+ msgHandled = false;
result = initResult;
testID = numTests++;
}
@@ -67,6 +69,7 @@
if (mItem != null) {
mItem.result = result;
+ msgHandled = true;
try {
mRSTC.refreshTestResults();
}
@@ -79,6 +82,12 @@
}
};
+ public void waitForMessage() {
+ while (!msgHandled) {
+ yield();
+ }
+ }
+
public void setItem(ScriptField_ListAllocs_s.Item item) {
mItem = item;
}
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 2b7928f..a4752f4 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -288,6 +288,11 @@
param RsSamplerValue value
}
+SamplerSet2 {
+ param RsSamplerParam p
+ param float value
+ }
+
SamplerCreate {
ret RsSampler
}
diff --git a/libs/rs/rsAdapter.cpp b/libs/rs/rsAdapter.cpp
index b4ec250..ef69b75 100644
--- a/libs/rs/rsAdapter.cpp
+++ b/libs/rs/rsAdapter.cpp
@@ -183,7 +183,6 @@
uint32_t eSize = mAllocation.get()->getType()->getElementSizeBytes();
uint32_t lineSize = eSize * w;
- uint32_t destW = getDimX();
const uint8_t *src = static_cast<const uint8_t *>(data);
for (uint32_t line=yoff; line < (yoff+h); line++) {
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 87c4f2b..0356e4d 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -293,7 +293,6 @@
}
for (uint32_t line=yoff; line < (yoff+h); line++) {
- uint8_t * ptr = static_cast<uint8_t *>(mPtr);
if (mType->getElement()->getHasReferences()) {
incRefs(src, w);
decRefs(dst, w);
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index b4d5014..6940033 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -241,6 +241,10 @@
return true;
}
+void Context::setupProgramStore() {
+ mFragmentStore->setupGL2(this, &mStateFragmentStore);
+}
+
static bool getProp(const char *str)
{
char buf[PROPERTY_VALUE_MAX];
@@ -282,18 +286,19 @@
rsc->props.mLogShadersUniforms = getProp("debug.rs.shader.uniforms");
rsc->props.mLogVisual = getProp("debug.rs.visual");
- ScriptTLSStruct *tlsStruct = new ScriptTLSStruct;
- if (!tlsStruct) {
+ rsc->mTlsStruct = new ScriptTLSStruct;
+ if (!rsc->mTlsStruct) {
LOGE("Error allocating tls storage");
return NULL;
}
- tlsStruct->mContext = rsc;
- tlsStruct->mScript = NULL;
- int status = pthread_setspecific(rsc->gThreadTLSKey, tlsStruct);
+ rsc->mTlsStruct->mContext = rsc;
+ rsc->mTlsStruct->mScript = NULL;
+ int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
if (status) {
LOGE("pthread_setspecific %i", status);
}
+ rsc->mScriptC.init(rsc);
if (rsc->mIsGraphicsContext) {
rsc->mStateRaster.init(rsc);
rsc->setRaster(NULL);
@@ -360,6 +365,7 @@
rsc->deinitEGL();
pthread_mutex_unlock(&gInitMutex);
}
+ delete rsc->mTlsStruct;
LOGV("%p, RS Thread exited", rsc);
return NULL;
@@ -386,6 +392,11 @@
#endif
setpriority(PRIO_PROCESS, rsc->mWorkers.mNativeThreadId[idx], rsc->mThreadPriority);
+ int status = pthread_setspecific(rsc->gThreadTLSKey, rsc->mTlsStruct);
+ if (status) {
+ LOGE("pthread_setspecific %i", status);
+ }
+
while(rsc->mRunning) {
rsc->mWorkers.mLaunchSignals[idx].wait();
if (rsc->mWorkers.mLaunchCallback) {
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 2e84930..dabe196 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -49,6 +49,24 @@
namespace renderscript {
+#if 0
+#define CHECK_OBJ(o) { \
+ GET_TLS(); \
+ if(!ObjectBase::isValid(rsc, (const ObjectBase *)o)) { \
+ LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__); \
+ } \
+}
+#define CHECK_OBJ_OR_NULL(o) { \
+ GET_TLS(); \
+ if(o && !ObjectBase::isValid(rsc, (const ObjectBase *)o)) { \
+ LOGE("Bad object %p at %s, %i", o, __FILE__, __LINE__); \
+ } \
+}
+#else
+#define CHECK_OBJ(o)
+#define CHECK_OBJ_OR_NULL(o)
+#endif
+
class Context
{
public:
@@ -64,6 +82,7 @@
Context * mContext;
Script * mScript;
};
+ ScriptTLSStruct *mTlsStruct;
typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
@@ -99,6 +118,7 @@
Font * getFont() {return mFont.get();}
bool setupCheck();
+ void setupProgramStore();
bool checkDriver() const {return mEGL.mSurface != 0;}
void pause();
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index f30915e..06298e8 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -111,6 +111,9 @@
bool mLogScripts;
bool mLogObjects;
bool mLogShaders;
+ bool mLogShadersAttr;
+ bool mLogShadersUniforms;
+ bool mLogVisual;
} props;
void dumpDebug() const { }
@@ -120,6 +123,7 @@
mutable const ObjectBase * mObjHead;
bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+ float ext_texture_max_aniso() const {return 1.0f;}
uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 4f8d8df..c516ea9 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -23,6 +23,7 @@
#include "rsFont.h"
#include "rsProgramFragment.h"
+#include <cutils/properties.h>
#include FT_BITMAP_H
#include <GLES/gl.h>
@@ -268,6 +269,44 @@
mRSC = NULL;
mLibrary = NULL;
setFontColor(0.1f, 0.1f, 0.1f, 1.0f);
+
+ // Get the renderer properties
+ char property[PROPERTY_VALUE_MAX];
+
+ // Get the gamma
+ float gamma = DEFAULT_TEXT_GAMMA;
+ if (property_get(PROPERTY_TEXT_GAMMA, property, NULL) > 0) {
+ LOGD(" Setting text gamma to %s", property);
+ gamma = atof(property);
+ } else {
+ LOGD(" Using default text gamma of %.2f", DEFAULT_TEXT_GAMMA);
+ }
+
+ // Get the black gamma threshold
+ int blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
+ if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, NULL) > 0) {
+ LOGD(" Setting text black gamma threshold to %s", property);
+ blackThreshold = atoi(property);
+ } else {
+ LOGD(" Using default text black gamma threshold of %d",
+ DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD);
+ }
+ mBlackThreshold = (float)(blackThreshold) / 255.0f;
+
+ // Get the white gamma threshold
+ int whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
+ if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, NULL) > 0) {
+ LOGD(" Setting text white gamma threshold to %s", property);
+ whiteThreshold = atoi(property);
+ } else {
+ LOGD(" Using default white black gamma threshold of %d",
+ DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD);
+ }
+ mWhiteThreshold = (float)(whiteThreshold) / 255.0f;
+
+ // Compute the gamma tables
+ mBlackGamma = gamma;
+ mWhiteGamma = 1.0f / gamma;
}
FontState::~FontState()
@@ -391,12 +430,15 @@
shaderString.append("void main() {\n");
shaderString.append(" lowp vec4 col = UNI_Color;\n");
shaderString.append(" col.a = texture2D(UNI_Tex0, varTex0.xy).a;\n");
+ shaderString.append(" col.a = pow(col.a, UNI_Gamma);\n");
shaderString.append(" gl_FragColor = col;\n");
shaderString.append("}\n");
const Element *colorElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
+ const Element *gammaElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
mRSC->mStateElement.elementBuilderBegin();
mRSC->mStateElement.elementBuilderAdd(colorElem, "Color", 1);
+ mRSC->mStateElement.elementBuilderAdd(gammaElem, "Gamma", 1);
const Element *constInput = mRSC->mStateElement.elementBuilderCreate(mRSC);
Type *inputType = new Type(mRSC);
@@ -558,9 +600,9 @@
ObjectBaseRef<const ProgramStore> tmpPS(mRSC->getFragmentStore());
mRSC->setFragmentStore(mFontProgramStore.get());
- if(mFontColorDirty) {
- mFontShaderFConstant->data(mRSC, &mFontColor, 4*sizeof(float));
- mFontColorDirty = false;
+ if(mConstantsDirty) {
+ mFontShaderFConstant->data(mRSC, &mConstants, sizeof(mConstants));
+ mConstantsDirty = false;
}
if (!mRSC->setupCheck()) {
@@ -725,24 +767,34 @@
}
void FontState::setFontColor(float r, float g, float b, float a) {
- mFontColor[0] = r;
- mFontColor[1] = g;
- mFontColor[2] = b;
- mFontColor[3] = a;
- mFontColorDirty = true;
+ mConstants.mFontColor[0] = r;
+ mConstants.mFontColor[1] = g;
+ mConstants.mFontColor[2] = b;
+ mConstants.mFontColor[3] = a;
+
+ mConstants.mGamma = 1.0f;
+ const int luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
+ if (luminance <= mBlackThreshold) {
+ mConstants.mGamma = mBlackGamma;
+ } else if (luminance >= mWhiteThreshold) {
+ mConstants.mGamma = mWhiteGamma;
+ }
+ mConstantsDirty = true;
}
void FontState::getFontColor(float *r, float *g, float *b, float *a) const {
- *r = mFontColor[0];
- *g = mFontColor[1];
- *b = mFontColor[2];
- *a = mFontColor[3];
+ *r = mConstants.mFontColor[0];
+ *g = mConstants.mFontColor[1];
+ *b = mConstants.mFontColor[2];
+ *a = mConstants.mFontColor[3];
}
void FontState::deinit(Context *rsc)
{
mInitialized = false;
+ mFontShaderFConstant.clear();
+
mIndexBuffer.clear();
mVertexArray.clear();
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index 027ed1d..16009eff 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -31,6 +31,15 @@
namespace renderscript {
+// Gamma (>= 1.0, <= 10.0)
+#define PROPERTY_TEXT_GAMMA "ro.text_gamma"
+#define PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD "ro.text_gamma.black_threshold"
+#define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "ro.text_gamma.white_threshold"
+
+#define DEFAULT_TEXT_GAMMA 1.4f
+#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
+#define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192
+
class FontState;
class Font : public ObjectBase
@@ -162,8 +171,17 @@
Context *mRSC;
- float mFontColor[4];
- bool mFontColorDirty;
+ struct {
+ float mFontColor[4];
+ float mGamma;
+ } mConstants;
+ bool mConstantsDirty;
+
+ float mBlackGamma;
+ float mWhiteGamma;
+
+ float mBlackThreshold;
+ float mWhiteThreshold;
// Free type library, we only need one copy
FT_Library mLibrary;
diff --git a/libs/rs/rsObjectBase.cpp b/libs/rs/rsObjectBase.cpp
index 48f1fee..f69cb15 100644
--- a/libs/rs/rsObjectBase.cpp
+++ b/libs/rs/rsObjectBase.cpp
@@ -61,6 +61,7 @@
if (mRSC) {
remove();
}
+ rsAssert(rsc);
mRSC = rsc;
if (rsc) {
add();
@@ -194,3 +195,15 @@
}
}
+bool ObjectBase::isValid(const Context *rsc, const ObjectBase *obj)
+{
+ const ObjectBase * o = rsc->mObjHead;
+ while (o) {
+ if (o == obj) {
+ return true;
+ }
+ o = o->mNext;
+ }
+ return false;
+}
+
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index ad95b81..59fb4a6 100644
--- a/libs/rs/rsObjectBase.h
+++ b/libs/rs/rsObjectBase.h
@@ -56,6 +56,8 @@
virtual void serialize(OStream *stream) const = 0;
virtual RsA3DClassID getClassId() const = 0;
+ static bool isValid(const Context *rsc, const ObjectBase *obj);
+
protected:
const char *mAllocFile;
uint32_t mAllocLine;
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 2531a9b..10e00e6 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -115,6 +115,14 @@
Program::~Program()
{
+ if(mRSC->props.mLogShaders) {
+ LOGV("Program::~Program with shader id %u", mShaderID);
+ }
+
+ if(mShaderID) {
+ glDeleteShader(mShaderID);
+ }
+
for (uint32_t ct=0; ct < MAX_UNIFORMS; ct++) {
bindAllocation(NULL, NULL, ct);
}
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 275a1df..c94f294 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -49,6 +49,9 @@
ProgramFragment::~ProgramFragment()
{
+ if(mShaderID) {
+ mRSC->mShaderCache.cleanupFragment(mShaderID);
+ }
}
void ProgramFragment::setConstantColor(Context *rsc, float r, float g, float b, float a)
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index bd12989..d3dbfb2 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -45,15 +45,9 @@
ProgramVertex::~ProgramVertex()
{
-}
-
-static void logMatrix(const char *txt, const float *f)
-{
- LOGV("Matrix %s, %p", txt, f);
- LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[0], f[4], f[8], f[12]);
- LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[1], f[5], f[9], f[13]);
- LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[2], f[6], f[10], f[14]);
- LOGV("%6.4f, %6.4f, %6.4f, %6.4f", f[3], f[7], f[11], f[15]);
+ if(mShaderID) {
+ mRSC->mShaderCache.cleanupVertex(mShaderID);
+ }
}
void ProgramVertex::loadShader(Context *rsc) {
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index c6a848c..180d78e 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -44,7 +44,8 @@
RsSamplerValue minFilter,
RsSamplerValue wrapS,
RsSamplerValue wrapT,
- RsSamplerValue wrapR) : ObjectBase(rsc)
+ RsSamplerValue wrapR,
+ float aniso) : ObjectBase(rsc)
{
mAllocFile = __FILE__;
mAllocLine = __LINE__;
@@ -53,6 +54,7 @@
mWrapS = wrapS;
mWrapT = wrapT;
mWrapR = wrapR;
+ mAniso = aniso;
}
Sampler::~Sampler()
@@ -93,6 +95,11 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
}
+ float anisoValue = rsMin(rsc->ext_texture_max_aniso(), mAniso);
+ if(rsc->ext_texture_max_aniso() > 1.0f) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoValue);
+ }
+
rsc->checkError("Sampler::setupGL2 tex env");
}
@@ -147,6 +154,7 @@
ss->mWrapS = RS_SAMPLER_WRAP;
ss->mWrapT = RS_SAMPLER_WRAP;
ss->mWrapR = RS_SAMPLER_WRAP;
+ ss->mAniso = 1.0f;
}
void rsi_SamplerSet(Context *rsc, RsSamplerParam param, RsSamplerValue value)
@@ -169,21 +177,37 @@
case RS_SAMPLER_WRAP_R:
ss->mWrapR = value;
break;
+ default:
+ LOGE("Attempting to set invalid value on sampler");
+ break;
}
+}
+void rsi_SamplerSet2(Context *rsc, RsSamplerParam param, float value)
+{
+ SamplerState * ss = &rsc->mStateSampler;
+
+ switch(param) {
+ case RS_SAMPLER_ANISO:
+ ss->mAniso = value;
+ break;
+ default:
+ LOGE("Attempting to set invalid value on sampler");
+ break;
+ }
}
RsSampler rsi_SamplerCreate(Context *rsc)
{
SamplerState * ss = &rsc->mStateSampler;
-
Sampler * s = new Sampler(rsc,
ss->mMagFilter,
ss->mMinFilter,
ss->mWrapS,
ss->mWrapT,
- ss->mWrapR);
+ ss->mWrapR,
+ ss->mAniso);
s->incUserRef();
return s;
}
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 32a8efd..4946355 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -36,7 +36,8 @@
RsSamplerValue minFilter,
RsSamplerValue wrapS,
RsSamplerValue wrapT,
- RsSamplerValue wrapR);
+ RsSamplerValue wrapR,
+ float aniso = 1.0f);
virtual ~Sampler();
@@ -56,6 +57,7 @@
RsSamplerValue mWrapS;
RsSamplerValue mWrapT;
RsSamplerValue mWrapR;
+ float mAniso;
int32_t mBoundSlot;
@@ -74,6 +76,7 @@
RsSamplerValue mWrapS;
RsSamplerValue mWrapT;
RsSamplerValue mWrapR;
+ float mAniso;
ObjectBaseRef<Sampler> mSamplers[RS_MAX_SAMPLER_SLOT];
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index e9621b9..c6418be 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -349,25 +349,29 @@
ScriptCState::ScriptCState()
{
- mScript = NULL;
- clear();
+ mScript.clear();
}
ScriptCState::~ScriptCState()
{
- delete mScript;
- mScript = NULL;
+ mScript.clear();
}
-void ScriptCState::clear()
+void ScriptCState::init(Context *rsc)
{
+ clear(rsc);
+}
+
+void ScriptCState::clear(Context *rsc)
+{
+ rsAssert(rsc);
for (uint32_t ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
mConstantBufferTypes[ct].clear();
mSlotWritable[ct] = false;
}
- delete mScript;
- mScript = new ScriptC(NULL);
+ mScript.clear();
+ mScript.set(new ScriptC(rsc));
}
static BCCvoid* symbolLookup(BCCvoid* pContext, const BCCchar* name)
@@ -503,7 +507,7 @@
void rsi_ScriptCBegin(Context * rsc)
{
ScriptCState *ss = &rsc->mScriptC;
- ss->clear();
+ ss->clear(rsc);
}
void rsi_ScriptCSetText(Context *rsc, const char *text, uint32_t len)
@@ -522,10 +526,10 @@
{
ScriptCState *ss = &rsc->mScriptC;
- ScriptC *s = ss->mScript;
- ss->mScript = NULL;
+ ObjectBaseRef<ScriptC> s = ss->mScript.get();
+ ss->mScript.clear();
- ss->runCompiler(rsc, s);
+ ss->runCompiler(rsc, s.get());
s->incUserRef();
s->setContext(rsc);
for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
@@ -533,8 +537,8 @@
s->mSlotWritable[ct] = ss->mSlotWritable[ct];
}
- ss->clear();
- return s;
+ ss->clear(rsc);
+ return s.get();
}
}
diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h
index 9d09b0b..7ec80aa 100644
--- a/libs/rs/rsScriptC.h
+++ b/libs/rs/rsScriptC.h
@@ -79,14 +79,16 @@
ScriptCState();
~ScriptCState();
- ScriptC *mScript;
+ ObjectBaseRef<ScriptC> mScript;
ObjectBaseRef<const Type> mConstantBufferTypes[MAX_SCRIPT_BANKS];
//String8 mSlotNames[MAX_SCRIPT_BANKS];
bool mSlotWritable[MAX_SCRIPT_BANKS];
//String8 mInvokableNames[MAX_SCRIPT_BANKS];
- void clear();
+ void init(Context *rsc);
+
+ void clear(Context *rsc);
void runCompiler(Context *rsc, ScriptC *s);
struct SymbolTable_t {
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 22fd421..e0de867 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -203,45 +203,46 @@
static uint32_t SC_allocGetDimX(RsAllocation va)
{
- GET_TLS();
const Allocation *a = static_cast<const Allocation *>(va);
- //LOGE("SC_allocGetDimX a=%p", a);
- //LOGE(" type=%p", a->getType());
+ CHECK_OBJ(a);
+ //LOGE("SC_allocGetDimX a=%p type=%p", a, a->getType());
return a->getType()->getDimX();
}
static uint32_t SC_allocGetDimY(RsAllocation va)
{
- GET_TLS();
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
return a->getType()->getDimY();
}
static uint32_t SC_allocGetDimZ(RsAllocation va)
{
- GET_TLS();
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
return a->getType()->getDimZ();
}
static uint32_t SC_allocGetDimLOD(RsAllocation va)
{
- GET_TLS();
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
return a->getType()->getDimLOD();
}
static uint32_t SC_allocGetDimFaces(RsAllocation va)
{
- GET_TLS();
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
return a->getType()->getDimFaces();
}
static const void * SC_getElementAtX(RsAllocation va, uint32_t x)
{
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
const Type *t = a->getType();
+ CHECK_OBJ(t);
const uint8_t *p = (const uint8_t *)a->getPtr();
return &p[t->getElementSizeBytes() * x];
}
@@ -249,7 +250,9 @@
static const void * SC_getElementAtXY(RsAllocation va, uint32_t x, uint32_t y)
{
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
const Type *t = a->getType();
+ CHECK_OBJ(t);
const uint8_t *p = (const uint8_t *)a->getPtr();
return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
}
@@ -257,7 +260,9 @@
static const void * SC_getElementAtXYZ(RsAllocation va, uint32_t x, uint32_t y, uint32_t z)
{
const Allocation *a = static_cast<const Allocation *>(va);
+ CHECK_OBJ(a);
const Type *t = a->getType();
+ CHECK_OBJ(t);
const uint8_t *p = (const uint8_t *)a->getPtr();
return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
}
@@ -265,9 +270,11 @@
static void SC_setObject(void **vdst, void * vsrc) {
//LOGE("SC_setObject %p,%p %p", vdst, *vdst, vsrc);
if (vsrc) {
+ CHECK_OBJ(vsrc);
static_cast<ObjectBase *>(vsrc)->incSysRef();
}
if (vdst[0]) {
+ CHECK_OBJ(vdst[0]);
static_cast<ObjectBase *>(vdst[0])->decSysRef();
}
*vdst = vsrc;
@@ -276,6 +283,7 @@
static void SC_clearObject(void **vdst) {
//LOGE("SC_clearObject %p,%p", vdst, *vdst);
if (vdst[0]) {
+ CHECK_OBJ(vdst[0]);
static_cast<ObjectBase *>(vdst[0])->decSysRef();
}
*vdst = NULL;
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index fd4c379..88db761 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -44,6 +44,8 @@
static void SC_bindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va)
{
+ CHECK_OBJ_OR_NULL(va);
+ CHECK_OBJ(vpf);
GET_TLS();
rsi_ProgramBindTexture(rsc,
static_cast<ProgramFragment *>(vpf),
@@ -54,6 +56,8 @@
static void SC_bindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs)
{
+ CHECK_OBJ_OR_NULL(vs);
+ CHECK_OBJ(vpf);
GET_TLS();
rsi_ProgramBindSampler(rsc,
static_cast<ProgramFragment *>(vpf),
@@ -64,24 +68,28 @@
static void SC_bindProgramStore(RsProgramStore pfs)
{
+ CHECK_OBJ_OR_NULL(pfs);
GET_TLS();
rsi_ContextBindProgramStore(rsc, pfs);
}
static void SC_bindProgramFragment(RsProgramFragment pf)
{
+ CHECK_OBJ_OR_NULL(pf);
GET_TLS();
rsi_ContextBindProgramFragment(rsc, pf);
}
static void SC_bindProgramVertex(RsProgramVertex pv)
{
+ CHECK_OBJ_OR_NULL(pv);
GET_TLS();
rsi_ContextBindProgramVertex(rsc, pv);
}
static void SC_bindProgramRaster(RsProgramRaster pv)
{
+ CHECK_OBJ_OR_NULL(pv);
GET_TLS();
rsi_ContextBindProgramRaster(rsc, pv);
}
@@ -112,6 +120,7 @@
static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
{
GET_TLS();
+ CHECK_OBJ(vpf);
ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
pf->setConstantColor(rsc, r, g, b, a);
}
@@ -228,6 +237,7 @@
static void SC_drawMesh(RsMesh vsm)
{
+ CHECK_OBJ(vsm);
GET_TLS();
Mesh *sm = static_cast<Mesh *>(vsm);
if (!rsc->setupCheck()) {
@@ -238,6 +248,7 @@
static void SC_drawMeshPrimitive(RsMesh vsm, uint32_t primIndex)
{
+ CHECK_OBJ(vsm);
GET_TLS();
Mesh *sm = static_cast<Mesh *>(vsm);
if (!rsc->setupCheck()) {
@@ -248,6 +259,7 @@
static void SC_drawMeshPrimitiveRange(RsMesh vsm, uint32_t primIndex, uint32_t start, uint32_t len)
{
+ CHECK_OBJ(vsm);
GET_TLS();
Mesh *sm = static_cast<Mesh *>(vsm);
if (!rsc->setupCheck()) {
@@ -259,6 +271,7 @@
static void SC_meshComputeBoundingBox(RsMesh vsm, float *minX, float *minY, float *minZ,
float *maxX, float *maxY, float *maxZ)
{
+ CHECK_OBJ(vsm);
GET_TLS();
Mesh *sm = static_cast<Mesh *>(vsm);
sm->computeBBox();
@@ -285,17 +298,20 @@
static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
{
+ CHECK_OBJ(va);
GET_TLS();
rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
}
static void SC_uploadToTexture(RsAllocation va)
{
+ CHECK_OBJ(va);
GET_TLS();
rsi_AllocationUploadToTexture(rsc, va, false, 0);
}
static void SC_uploadToBufferObject(RsAllocation va)
{
+ CHECK_OBJ(va);
GET_TLS();
rsi_AllocationUploadToBufferObject(rsc, va);
}
@@ -303,9 +319,7 @@
static void SC_ClearColor(float r, float g, float b, float a)
{
GET_TLS();
- if (!rsc->setupCheck()) {
- return;
- }
+ rsc->setupProgramStore();
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
@@ -314,9 +328,7 @@
static void SC_ClearDepth(float v)
{
GET_TLS();
- if (!rsc->setupCheck()) {
- return;
- }
+ rsc->setupProgramStore();
glClearDepthf(v);
glClear(GL_DEPTH_BUFFER_BIT);
@@ -336,6 +348,7 @@
static void SC_DrawTextAlloc(RsAllocation va, int x, int y)
{
+ CHECK_OBJ(va);
GET_TLS();
Allocation *alloc = static_cast<Allocation *>(va);
rsc->mStateFont.renderText(alloc, x, y);
@@ -349,6 +362,7 @@
static void SC_BindFont(RsFont font)
{
+ CHECK_OBJ(font);
GET_TLS();
rsi_ContextBindFont(rsc, font);
}
diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp
index 28e3b1d..45f6207 100644
--- a/libs/rs/rsShaderCache.cpp
+++ b/libs/rs/rsShaderCache.cpp
@@ -29,20 +29,15 @@
ShaderCache::ShaderCache()
{
- mEntryCount = 0;
- mEntryAllocationCount = 16;
- mEntries = (entry_t *)calloc(mEntryAllocationCount, sizeof(entry_t));
+ mEntries.setCapacity(16);
}
ShaderCache::~ShaderCache()
{
- for (uint32_t ct=0; ct < mEntryCount; ct++) {
- glDeleteProgram(mEntries[ct].program);
+ for (uint32_t ct=0; ct < mEntries.size(); ct++) {
+ glDeleteProgram(mEntries[ct]->program);
+ free(mEntries[ct]);
}
-
- mEntryCount = 0;
- mEntryAllocationCount = 0;
- free(mEntries);
}
bool ShaderCache::lookup(Context *rsc, ProgramVertex *vtx, ProgramFragment *frag)
@@ -59,44 +54,30 @@
return false;
}
//LOGV("ShaderCache lookup vtx %i, frag %i", vtx->getShaderID(), frag->getShaderID());
+ uint32_t entryCount = mEntries.size();
+ for(uint32_t ct = 0; ct < entryCount; ct ++) {
+ if ((mEntries[ct]->vtx == vtx->getShaderID()) &&
+ (mEntries[ct]->frag == frag->getShaderID())) {
- for (uint32_t ct=0; ct < mEntryCount; ct++) {
- if ((mEntries[ct].vtx == vtx->getShaderID()) &&
- (mEntries[ct].frag == frag->getShaderID())) {
-
- //LOGV("SC using program %i", mEntries[ct].program);
- glUseProgram(mEntries[ct].program);
- mCurrent = &mEntries[ct];
+ //LOGV("SC using program %i", mEntries[ct]->program);
+ glUseProgram(mEntries[ct]->program);
+ mCurrent = mEntries[ct];
//LOGV("ShaderCache hit, using %i", ct);
rsc->checkError("ShaderCache::lookup (hit)");
return true;
}
}
- // Not in cache, add it.
- if (mEntryAllocationCount == mEntryCount) {
- // Out of space, make some.
- mEntryAllocationCount *= 2;
- entry_t *e = (entry_t *)calloc(mEntryAllocationCount, sizeof(entry_t));
- if (!e) {
- LOGE("Out of memory for ShaderCache::lookup");
- return false;
- }
- memcpy(e, mEntries, sizeof(entry_t) * mEntryCount);
- free(mEntries);
- mEntries = e;
- }
-
- //LOGV("ShaderCache miss, using %i", mEntryCount);
+ //LOGV("ShaderCache miss");
//LOGE("e0 %x", glGetError());
-
- entry_t *e = &mEntries[mEntryCount];
+ entry_t *e = (entry_t *)malloc(sizeof(entry_t));
+ mEntries.push(e);
mCurrent = e;
e->vtx = vtx->getShaderID();
e->frag = frag->getShaderID();
e->program = glCreateProgram();
e->vtxAttrCount = vtx->getAttribCount();
- if (mEntries[mEntryCount].program) {
+ if (e->program) {
GLuint pgm = e->program;
glAttachShader(pgm, vtx->getShaderID());
//LOGE("e1 %x", glGetError());
@@ -155,7 +136,6 @@
e->mIsValid = true;
//LOGV("SC made program %i", e->program);
glUseProgram(e->program);
- mEntryCount++;
rsc->checkError("ShaderCache::lookup (miss)");
return true;
}
@@ -171,10 +151,32 @@
void ShaderCache::cleanupVertex(uint32_t id)
{
+ int32_t numEntries = (int32_t)mEntries.size();
+ for(int32_t ct = 0; ct < numEntries; ct ++) {
+ if (mEntries[ct]->vtx == id) {
+ glDeleteProgram(mEntries[ct]->program);
+
+ free(mEntries[ct]);
+ mEntries.removeAt(ct);
+ numEntries = (int32_t)mEntries.size();
+ ct --;
+ }
+ }
}
void ShaderCache::cleanupFragment(uint32_t id)
{
+ int32_t numEntries = (int32_t)mEntries.size();
+ for(int32_t ct = 0; ct < numEntries; ct ++) {
+ if (mEntries[ct]->frag == id) {
+ glDeleteProgram(mEntries[ct]->program);
+
+ free(mEntries[ct]);
+ mEntries.removeAt(ct);
+ numEntries = (int32_t)mEntries.size();
+ ct --;
+ }
+ }
}
void ShaderCache::cleanupAll()
diff --git a/libs/rs/rsShaderCache.h b/libs/rs/rsShaderCache.h
index 312c251..35ff95b 100644
--- a/libs/rs/rsShaderCache.h
+++ b/libs/rs/rsShaderCache.h
@@ -58,11 +58,12 @@
int32_t mFragUniformSlots[Program::MAX_UNIFORMS];
bool mIsValid;
} entry_t;
- entry_t *mEntries;
+ //entry_t *mEntries;
+ Vector<entry_t*> mEntries;
entry_t *mCurrent;
- uint32_t mEntryCount;
- uint32_t mEntryAllocationCount;
+ /*uint32_t mEntryCount;
+ uint32_t mEntryAllocationCount;*/
};
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 1ee07cd..fc037a3 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -321,8 +321,6 @@
break;
}
-
- int32_t arrayNum = dim - RS_DIMENSION_ARRAY_0;
if ((dim < 0) || (dim > RS_DIMENSION_MAX)) {
LOGE("rsTypeAdd: Bad dimension");
//error
diff --git a/libs/surfaceflinger_client/ISurfaceComposer.cpp b/libs/surfaceflinger_client/ISurfaceComposer.cpp
index 040060e..d676f5e 100644
--- a/libs/surfaceflinger_client/ISurfaceComposer.cpp
+++ b/libs/surfaceflinger_client/ISurfaceComposer.cpp
@@ -126,11 +126,14 @@
virtual status_t captureScreen(DisplayID dpy,
sp<IMemoryHeap>* heap,
- uint32_t* width, uint32_t* height, PixelFormat* format)
+ uint32_t* width, uint32_t* height, PixelFormat* format,
+ uint32_t reqWidth, uint32_t reqHeight)
{
Parcel data, reply;
data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
data.writeInt32(dpy);
+ data.writeInt32(reqWidth);
+ data.writeInt32(reqHeight);
remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply);
*heap = interface_cast<IMemoryHeap>(reply.readStrongBinder());
*width = reply.readInt32();
@@ -208,10 +211,13 @@
case CAPTURE_SCREEN: {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
DisplayID dpy = data.readInt32();
+ uint32_t reqWidth = data.readInt32();
+ uint32_t reqHeight = data.readInt32();
sp<IMemoryHeap> heap;
uint32_t w, h;
PixelFormat f;
- status_t res = captureScreen(dpy, &heap, &w, &h, &f);
+ status_t res = captureScreen(dpy, &heap, &w, &h, &f,
+ reqWidth, reqHeight);
reply->writeStrongBinder(heap->asBinder());
reply->writeInt32(w);
reply->writeInt32(h);
diff --git a/libs/surfaceflinger_client/SurfaceComposerClient.cpp b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
index 4096ac6..f270461 100644
--- a/libs/surfaceflinger_client/SurfaceComposerClient.cpp
+++ b/libs/surfaceflinger_client/SurfaceComposerClient.cpp
@@ -545,5 +545,55 @@
}
// ----------------------------------------------------------------------------
+
+ScreenshotClient::ScreenshotClient()
+ : mWidth(0), mHeight(0), mFormat(PIXEL_FORMAT_NONE) {
+}
+
+status_t ScreenshotClient::update() {
+ sp<ISurfaceComposer> s(ComposerService::getComposerService());
+ if (s == NULL) return NO_INIT;
+ mHeap = 0;
+ return s->captureScreen(0, &mHeap,
+ &mWidth, &mHeight, &mFormat, 0, 0);
+}
+
+status_t ScreenshotClient::update(uint32_t reqWidth, uint32_t reqHeight) {
+ sp<ISurfaceComposer> s(ComposerService::getComposerService());
+ if (s == NULL) return NO_INIT;
+ mHeap = 0;
+ return s->captureScreen(0, &mHeap,
+ &mWidth, &mHeight, &mFormat, reqWidth, reqHeight);
+}
+
+void ScreenshotClient::release() {
+ mHeap = 0;
+}
+
+void const* ScreenshotClient::getPixels() const {
+ return mHeap->getBase();
+}
+
+uint32_t ScreenshotClient::getWidth() const {
+ return mWidth;
+}
+
+uint32_t ScreenshotClient::getHeight() const {
+ return mHeight;
+}
+
+PixelFormat ScreenshotClient::getFormat() const {
+ return mFormat;
+}
+
+uint32_t ScreenshotClient::getStride() const {
+ return mWidth;
+}
+
+size_t ScreenshotClient::getSize() const {
+ return mHeap->getSize();
+}
+
+// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 1d38b4b..c0be3a0 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -73,6 +73,10 @@
#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
#endif
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
namespace android {
static const char *WAKE_LOCK_ID = "KeyEvents";
@@ -84,6 +88,10 @@
return (v1 > v2) ? v1 : v2;
}
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
EventHub::device_t::device_t(int32_t _id, const char* _path, const char* name)
: id(_id), path(_path), name(name), classes(0)
, keyBitmask(NULL), layoutMap(new KeyLayoutMap()), fd(-1), next(NULL) {
@@ -98,7 +106,7 @@
: mError(NO_INIT), mHaveFirstKeyboard(false), mFirstKeyboardId(0)
, mDevicesById(0), mNumDevicesById(0)
, mOpeningDevices(0), mClosingDevices(0)
- , mDevices(0), mFDs(0), mFDCount(0), mOpened(false)
+ , mDevices(0), mFDs(0), mFDCount(0), mOpened(false), mNeedToSendFinishedDeviceScan(false)
, mInputBufferIndex(0), mInputBufferCount(0), mInputDeviceIndex(0)
{
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
@@ -124,7 +132,7 @@
String8 EventHub::getDeviceName(int32_t deviceId) const
{
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device == NULL) return String8();
return device->name;
}
@@ -132,7 +140,7 @@
uint32_t EventHub::getDeviceClasses(int32_t deviceId) const
{
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device == NULL) return 0;
return device->classes;
}
@@ -142,7 +150,7 @@
outAxisInfo->clear();
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device == NULL) return -1;
struct input_absinfo info;
@@ -167,7 +175,7 @@
if (scanCode >= 0 && scanCode <= KEY_MAX) {
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device != NULL) {
return getScanCodeStateLocked(device, scanCode);
}
@@ -188,7 +196,7 @@
int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device != NULL) {
return getKeyCodeStateLocked(device, keyCode);
}
@@ -225,7 +233,7 @@
if (sw >= 0 && sw <= SW_MAX) {
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device != NULL) {
return getSwitchStateLocked(device, sw);
}
@@ -248,7 +256,7 @@
const int32_t* keyCodes, uint8_t* outFlags) const {
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device != NULL) {
return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags);
}
@@ -284,7 +292,7 @@
int32_t* outKeycode, uint32_t* outFlags) const
{
AutoMutex _l(mLock);
- device_t* device = getDevice(deviceId);
+ device_t* device = getDeviceLocked(deviceId);
if (device != NULL && device->layoutMap != NULL) {
status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
@@ -294,7 +302,7 @@
}
if (mHaveFirstKeyboard) {
- device = getDevice(mFirstKeyboardId);
+ device = getDeviceLocked(mFirstKeyboardId);
if (device != NULL && device->layoutMap != NULL) {
status_t err = device->layoutMap->map(scancode, outKeycode, outFlags);
@@ -311,11 +319,13 @@
void EventHub::addExcludedDevice(const char* deviceName)
{
+ AutoMutex _l(mLock);
+
String8 name(deviceName);
mExcludedDevices.push_back(name);
}
-EventHub::device_t* EventHub::getDevice(int32_t deviceId) const
+EventHub::device_t* EventHub::getDeviceLocked(int32_t deviceId) const
{
if (deviceId == 0) deviceId = mFirstKeyboardId;
int32_t id = deviceId & ID_MASK;
@@ -344,6 +354,7 @@
if (!mOpened) {
mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
mOpened = true;
+ mNeedToSendFinishedDeviceScan = true;
}
for (;;) {
@@ -360,6 +371,7 @@
}
outEvent->type = DEVICE_REMOVED;
delete device;
+ mNeedToSendFinishedDeviceScan = true;
return true;
}
@@ -374,6 +386,13 @@
outEvent->deviceId = device->id;
}
outEvent->type = DEVICE_ADDED;
+ mNeedToSendFinishedDeviceScan = true;
+ return true;
+ }
+
+ if (mNeedToSendFinishedDeviceScan) {
+ mNeedToSendFinishedDeviceScan = false;
+ outEvent->type = FINISHED_DEVICE_SCAN;
return true;
}
@@ -441,10 +460,10 @@
}
}
- // read_notify() will modify mFDs and mFDCount, so this must be done after
+ // readNotify() will modify mFDs and mFDCount, so this must be done after
// processing all other events.
if(mFDs[0].revents & POLLIN) {
- read_notify(mFDs[0].fd);
+ readNotify(mFDs[0].fd);
}
// Poll for events. Mind the wake lock dance!
@@ -500,10 +519,9 @@
mFDs[0].fd = -1;
#endif
- res = scan_dir(device_path);
+ res = scanDir(device_path);
if(res < 0) {
LOGE("scan dir failed for %s\n", device_path);
- //open_device("/dev/input/event0");
}
return true;
@@ -531,8 +549,7 @@
AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE
};
-int EventHub::open_device(const char *deviceName)
-{
+int EventHub::openDevice(const char *deviceName) {
int version;
int fd;
struct pollfd *new_mFDs;
@@ -782,22 +799,22 @@
property_set(propName, name);
// 'Q' key support = cheap test of whether this is an alpha-capable kbd
- if (hasKeycode(device, AKEYCODE_Q)) {
+ if (hasKeycodeLocked(device, AKEYCODE_Q)) {
device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
}
// See if this device has a DPAD.
- if (hasKeycode(device, AKEYCODE_DPAD_UP) &&
- hasKeycode(device, AKEYCODE_DPAD_DOWN) &&
- hasKeycode(device, AKEYCODE_DPAD_LEFT) &&
- hasKeycode(device, AKEYCODE_DPAD_RIGHT) &&
- hasKeycode(device, AKEYCODE_DPAD_CENTER)) {
+ if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
device->classes |= INPUT_DEVICE_CLASS_DPAD;
}
// See if this device has a gamepad.
for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES); i++) {
- if (hasKeycode(device, GAMEPAD_KEYCODES[i])) {
+ if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
break;
}
@@ -830,7 +847,7 @@
return 0;
}
-bool EventHub::hasKeycode(device_t* device, int keycode) const
+bool EventHub::hasKeycodeLocked(device_t* device, int keycode) const
{
if (device->keyBitmask == NULL || device->layoutMap == NULL) {
return false;
@@ -849,10 +866,9 @@
return false;
}
-int EventHub::close_device(const char *deviceName)
-{
+int EventHub::closeDevice(const char *deviceName) {
AutoMutex _l(mLock);
-
+
int i;
for(i = 1; i < mFDCount; i++) {
if(strcmp(mDevices[i]->path.string(), deviceName) == 0) {
@@ -902,8 +918,7 @@
return -1;
}
-int EventHub::read_notify(int nfd)
-{
+int EventHub::readNotify(int nfd) {
#ifdef HAVE_INOTIFY
int res;
char devname[PATH_MAX];
@@ -913,7 +928,7 @@
int event_pos = 0;
struct inotify_event *event;
- LOGV("EventHub::read_notify nfd: %d\n", nfd);
+ LOGV("EventHub::readNotify nfd: %d\n", nfd);
res = read(nfd, event_buf, sizeof(event_buf));
if(res < (int)sizeof(*event)) {
if(errno == EINTR)
@@ -933,10 +948,10 @@
if(event->len) {
strcpy(filename, event->name);
if(event->mask & IN_CREATE) {
- open_device(devname);
+ openDevice(devname);
}
else {
- close_device(devname);
+ closeDevice(devname);
}
}
event_size = sizeof(*event) + event->len;
@@ -948,7 +963,7 @@
}
-int EventHub::scan_dir(const char *dirname)
+int EventHub::scanDir(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
@@ -966,10 +981,38 @@
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
- open_device(devname);
+ openDevice(devname);
}
closedir(dir);
return 0;
}
+void EventHub::dump(String8& dump) {
+ dump.append("Event Hub State:\n");
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ dump.appendFormat(INDENT "HaveFirstKeyboard: %s\n", toString(mHaveFirstKeyboard));
+ dump.appendFormat(INDENT "FirstKeyboardId: 0x%x\n", mFirstKeyboardId);
+
+ dump.append(INDENT "Devices:\n");
+
+ for (int i = 0; i < mNumDevicesById; i++) {
+ const device_t* device = mDevicesById[i].device;
+ if (device) {
+ if (mFirstKeyboardId == device->id) {
+ dump.appendFormat(INDENT2 "0x%x: %s (aka device 0 - first keyboard)\n",
+ device->id, device->name.string());
+ } else {
+ dump.appendFormat(INDENT2 "0x%x: %s\n", device->id, device->name.string());
+ }
+ dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+ dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+ dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n", device->keylayoutFilename.string());
+ }
+ }
+ } // release lock
+}
+
}; // namespace android
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 16ce24b..a6f5a1b 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -46,6 +46,9 @@
#include <errno.h>
#include <limits.h>
+#define INDENT " "
+#define INDENT2 " "
+
namespace android {
// Delay between reporting long touch events to the power manager.
@@ -2554,74 +2557,96 @@
}
void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
- dump.appendFormat(" dispatchEnabled: %d\n", mDispatchEnabled);
- dump.appendFormat(" dispatchFrozen: %d\n", mDispatchFrozen);
+ dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
if (mFocusedApplication) {
- dump.appendFormat(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
mFocusedApplication->name.string(),
mFocusedApplication->dispatchingTimeout / 1000000.0);
} else {
- dump.append(" focusedApplication: <null>\n");
+ dump.append(INDENT "FocusedApplication: <null>\n");
}
- dump.appendFormat(" focusedWindow: name='%s'\n",
+ dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>");
- dump.appendFormat(" touchState: down=%s, split=%s\n", toString(mTouchState.down),
- toString(mTouchState.split));
- for (size_t i = 0; i < mTouchState.windows.size(); i++) {
- const TouchedWindow& touchedWindow = mTouchState.windows[i];
- dump.appendFormat(" touchedWindow[%d]: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
- i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value,
- touchedWindow.targetFlags);
- }
- for (size_t i = 0; i < mWindows.size(); i++) {
- dump.appendFormat(" windows[%d]: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
- "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
- "frame=[%d,%d][%d,%d], "
- "visibleFrame=[%d,%d][%d,%d], "
- "touchableArea=[%d,%d][%d,%d], "
- "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
- i, mWindows[i].name.string(),
- toString(mWindows[i].paused),
- toString(mWindows[i].hasFocus),
- toString(mWindows[i].hasWallpaper),
- toString(mWindows[i].visible),
- toString(mWindows[i].canReceiveKeys),
- mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
- mWindows[i].layer,
- mWindows[i].frameLeft, mWindows[i].frameTop,
- mWindows[i].frameRight, mWindows[i].frameBottom,
- mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
- mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
- mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
- mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
- mWindows[i].ownerPid, mWindows[i].ownerUid,
- mWindows[i].dispatchingTimeout / 1000000.0);
+
+ dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+ dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+ if (!mTouchState.windows.isEmpty()) {
+ dump.append(INDENT "TouchedWindows:\n");
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+ i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value,
+ touchedWindow.targetFlags);
+ }
+ } else {
+ dump.append(INDENT "TouchedWindows: <none>\n");
}
- for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
- const sp<InputChannel>& channel = mMonitoringChannels[i];
- dump.appendFormat(" monitoringChannel[%d]: '%s'\n",
- i, channel->getName().string());
+ if (!mWindows.isEmpty()) {
+ dump.append(INDENT "Windows:\n");
+ for (size_t i = 0; i < mWindows.size(); i++) {
+ const InputWindow& window = mWindows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+ "frame=[%d,%d][%d,%d], "
+ "visibleFrame=[%d,%d][%d,%d], "
+ "touchableArea=[%d,%d][%d,%d], "
+ "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ i, window.name.string(),
+ toString(window.paused),
+ toString(window.hasFocus),
+ toString(window.hasWallpaper),
+ toString(window.visible),
+ toString(window.canReceiveKeys),
+ window.layoutParamsFlags, window.layoutParamsType,
+ window.layer,
+ window.frameLeft, window.frameTop,
+ window.frameRight, window.frameBottom,
+ window.visibleFrameLeft, window.visibleFrameTop,
+ window.visibleFrameRight, window.visibleFrameBottom,
+ window.touchableAreaLeft, window.touchableAreaTop,
+ window.touchableAreaRight, window.touchableAreaBottom,
+ window.ownerPid, window.ownerUid,
+ window.dispatchingTimeout / 1000000.0);
+ }
+ } else {
+ dump.append(INDENT "Windows: <none>\n");
}
- dump.appendFormat(" inboundQueue: length=%u", mInboundQueue.count());
+ if (!mMonitoringChannels.isEmpty()) {
+ dump.append(INDENT "MonitoringChannels:\n");
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+ }
+ } else {
+ dump.append(INDENT "MonitoringChannels: <none>\n");
+ }
- for (size_t i = 0; i < mActiveConnections.size(); i++) {
- const Connection* connection = mActiveConnections[i];
- dump.appendFormat(" activeConnection[%d]: '%s', status=%s, outboundQueueLength=%u"
- "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
- i, connection->getInputChannelName(), connection->getStatusLabel(),
- connection->outboundQueue.count(),
- toString(connection->inputState.isNeutral()),
- toString(connection->inputState.isOutOfSync()));
+ dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+
+ if (!mActiveConnections.isEmpty()) {
+ dump.append(INDENT "ActiveConnections:\n");
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ const Connection* connection = mActiveConnections[i];
+ dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u"
+ "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
+ i, connection->getInputChannelName(), connection->getStatusLabel(),
+ connection->outboundQueue.count(),
+ toString(connection->inputState.isNeutral()),
+ toString(connection->inputState.isOutOfSync()));
+ }
+ } else {
+ dump.append(INDENT "ActiveConnections: <none>\n");
}
if (isAppSwitchPendingLocked()) {
- dump.appendFormat(" appSwitch: pending, due in %01.1fms\n",
+ dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n",
(mAppSwitchDueTime - now()) / 1000000.0);
} else {
- dump.append(" appSwitch: not pending\n");
+ dump.append(INDENT "AppSwitch: not pending\n");
}
}
@@ -2838,6 +2863,7 @@
}
void InputDispatcher::dump(String8& dump) {
+ dump.append("Input Dispatcher State:\n");
dumpDispatchStateLocked(dump);
}
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index f2b029a..8e173aa 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -33,6 +33,9 @@
#include <math.h>
#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
namespace android {
@@ -63,6 +66,10 @@
return sqrtf(x * x + y * y);
}
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
int32_t mask;
@@ -219,11 +226,15 @@
void InputReader::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
- addDevice(rawEvent->when, rawEvent->deviceId);
+ addDevice(rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
- removeDevice(rawEvent->when, rawEvent->deviceId);
+ removeDevice(rawEvent->deviceId);
+ break;
+
+ case EventHubInterface::FINISHED_DEVICE_SCAN:
+ handleConfigurationChanged();
break;
default:
@@ -232,20 +243,18 @@
}
}
-void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
+void InputReader::addDevice(int32_t deviceId) {
String8 name = mEventHub->getDeviceName(deviceId);
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
- // Write a log message about the added device as a heading for subsequent log messages.
- LOGI("Device added: id=0x%x, name=%s", deviceId, name.string());
-
InputDevice* device = createDevice(deviceId, name, classes);
device->configure();
if (device->isIgnored()) {
- LOGI(INDENT "Sources: none (device is ignored)");
+ LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", deviceId, name.string());
} else {
- LOGI(INDENT "Sources: 0x%08x", device->getSources());
+ LOGI("Device added: id=0x%x, name=%s, sources=%08x", deviceId, name.string(),
+ device->getSources());
}
bool added = false;
@@ -264,11 +273,9 @@
delete device;
return;
}
-
- handleConfigurationChanged(when);
}
-void InputReader::removeDevice(nsecs_t when, int32_t deviceId) {
+void InputReader::removeDevice(int32_t deviceId) {
bool removed = false;
InputDevice* device = NULL;
{ // acquire device registry writer lock
@@ -287,7 +294,6 @@
return;
}
- // Write a log message about the removed device as a heading for subsequent log messages.
if (device->isIgnored()) {
LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
device->getId(), device->getName().string());
@@ -299,8 +305,6 @@
device->reset();
delete device;
-
- handleConfigurationChanged(when);
}
InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
@@ -368,7 +372,7 @@
} // release device registry reader lock
}
-void InputReader::handleConfigurationChanged(nsecs_t when) {
+void InputReader::handleConfigurationChanged() {
// Reset global meta state because it depends on the list of all configured devices.
updateGlobalMetaState();
@@ -376,6 +380,7 @@
updateInputConfiguration();
// Enqueue configuration changed.
+ nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
mDispatcher->notifyConfigurationChanged(when);
}
@@ -571,59 +576,20 @@
}
void InputReader::dump(String8& dump) {
- dumpDeviceInfo(dump);
-}
+ mEventHub->dump(dump);
+ dump.append("\n");
-static void dumpMotionRange(String8& dump,
- const char* name, const InputDeviceInfo::MotionRange* range) {
- if (range) {
- dump.appendFormat(" %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
- name, range->min, range->max, range->flat, range->fuzz);
- }
-}
+ dump.append("Input Reader State:\n");
-#define DUMP_MOTION_RANGE(range) \
- dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
-void InputReader::dumpDeviceInfo(String8& dump) {
- Vector<int32_t> deviceIds;
- getInputDeviceIds(deviceIds);
-
- InputDeviceInfo deviceInfo;
- for (size_t i = 0; i < deviceIds.size(); i++) {
- int32_t deviceId = deviceIds[i];
-
- status_t result = getInputDeviceInfo(deviceId, & deviceInfo);
- if (result == NAME_NOT_FOUND) {
- continue;
- } else if (result != OK) {
- dump.appendFormat(" ** Unexpected error %d getting information about input devices.\n",
- result);
- continue;
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ mDevices.valueAt(i)->dump(dump);
}
-
- dump.appendFormat(" Device %d: '%s'\n",
- deviceInfo.getId(), deviceInfo.getName().string());
- dump.appendFormat(" sources = 0x%08x\n",
- deviceInfo.getSources());
- dump.appendFormat(" keyboardType = %d\n",
- deviceInfo.getKeyboardType());
-
- dump.append(" motion ranges:\n");
- DUMP_MOTION_RANGE(X);
- DUMP_MOTION_RANGE(Y);
- DUMP_MOTION_RANGE(PRESSURE);
- DUMP_MOTION_RANGE(SIZE);
- DUMP_MOTION_RANGE(TOUCH_MAJOR);
- DUMP_MOTION_RANGE(TOUCH_MINOR);
- DUMP_MOTION_RANGE(TOOL_MAJOR);
- DUMP_MOTION_RANGE(TOOL_MINOR);
- DUMP_MOTION_RANGE(ORIENTATION);
- }
+ } // release device registy reader lock
}
-#undef DUMP_MOTION_RANGE
-
// --- InputReaderThread ---
@@ -654,6 +620,43 @@
mMappers.clear();
}
+static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
+ int32_t rangeType, const char* name) {
+ const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
+ if (range) {
+ dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+ name, range->min, range->max, range->flat, range->fuzz);
+ }
+}
+
+void InputDevice::dump(String8& dump) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(& deviceInfo);
+
+ dump.appendFormat(INDENT "Device 0x%x: %s\n", deviceInfo.getId(),
+ deviceInfo.getName().string());
+ dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+ dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+ if (!deviceInfo.getMotionRanges().isEmpty()) {
+ dump.append(INDENT2 "Motion Ranges:\n");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation");
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->dump(dump);
+ }
+}
+
void InputDevice::addMapper(InputMapper* mapper) {
mMappers.add(mapper);
}
@@ -763,6 +766,9 @@
info->addSource(getSources());
}
+void InputMapper::dump(String8& dump) {
+}
+
void InputMapper::configure() {
}
@@ -856,6 +862,18 @@
info->setKeyboardType(mKeyboardType);
}
+void KeyboardInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Keyboard Input Mapper:\n");
+ dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+ dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+ dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size());
+ dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState);
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+ } // release lock
+}
+
void KeyboardInputMapper::reset() {
for (;;) {
int32_t keyCode, scanCode;
@@ -980,7 +998,10 @@
int32_t keyEventAction = down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP;
int32_t keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM;
if (policyFlags & POLICY_FLAG_WOKE_HERE) {
- keyEventFlags = keyEventFlags | AKEY_EVENT_FLAG_WOKE_HERE;
+ keyEventFlags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+ if (policyFlags & POLICY_FLAG_VIRTUAL) {
+ keyEventFlags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
}
getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
@@ -1044,6 +1065,18 @@
info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
}
+void TrackballInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Trackball Input Mapper:\n");
+ dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down));
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+ } // release lock
+}
+
void TrackballInputMapper::initializeLocked() {
mAccumulator.clear();
@@ -1275,6 +1308,21 @@
} // release lock
}
+void TouchInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Touch Input Mapper:\n");
+ dump.appendFormat(INDENT3 "AssociatedDisplayId: %d\n", mAssociatedDisplayId);
+ dumpParameters(dump);
+ dumpVirtualKeysLocked(dump);
+ dumpRawAxes(dump);
+ dumpCalibration(dump);
+ dumpSurfaceLocked(dump);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mLocked.xPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mLocked.yPrecision);
+ } // release lock
+}
+
void TouchInputMapper::initializeLocked() {
mCurrentTouch.clear();
mLastTouch.clear();
@@ -1301,16 +1349,13 @@
// Configure basic parameters.
configureParameters();
- logParameters();
// Configure absolute axis information.
configureRawAxes();
- logRawAxes();
// Prepare input device calibration.
parseCalibration();
resolveCalibration();
- logCalibration();
{ // acquire lock
AutoMutex _l(mLock);
@@ -1326,16 +1371,13 @@
mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
}
-void TouchInputMapper::logParameters() {
- if (mParameters.useBadTouchFilter) {
- LOGI(INDENT "Bad touch filter enabled.");
- }
- if (mParameters.useAveragingTouchFilter) {
- LOGI(INDENT "Averaging touch filter enabled.");
- }
- if (mParameters.useJumpyTouchFilter) {
- LOGI(INDENT "Jumpy touch filter enabled.");
- }
+void TouchInputMapper::dumpParameters(String8& dump) {
+ dump.appendFormat(INDENT3 "UseBadTouchFilter: %s\n",
+ toString(mParameters.useBadTouchFilter));
+ dump.appendFormat(INDENT3 "UseAveragingTouchFilter: %s\n",
+ toString(mParameters.useAveragingTouchFilter));
+ dump.appendFormat(INDENT3 "UseJumpyTouchFilter: %s\n",
+ toString(mParameters.useJumpyTouchFilter));
}
void TouchInputMapper::configureRawAxes() {
@@ -1349,24 +1391,25 @@
mRawAxes.orientation.clear();
}
-static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
+static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
if (axis.valid) {
- LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
+ dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
} else {
- LOGI(INDENT "Raw %s axis: unknown range", name);
+ dump.appendFormat(INDENT4 "%s: unknown range\n", name);
}
}
-void TouchInputMapper::logRawAxes() {
- logAxisInfo(mRawAxes.x, "x");
- logAxisInfo(mRawAxes.y, "y");
- logAxisInfo(mRawAxes.pressure, "pressure");
- logAxisInfo(mRawAxes.touchMajor, "touchMajor");
- logAxisInfo(mRawAxes.touchMinor, "touchMinor");
- logAxisInfo(mRawAxes.toolMajor, "toolMajor");
- logAxisInfo(mRawAxes.toolMinor, "toolMinor");
- logAxisInfo(mRawAxes.orientation, "orientation");
+void TouchInputMapper::dumpRawAxes(String8& dump) {
+ dump.append(INDENT3 "Raw Axes:\n");
+ dumpAxisInfo(dump, mRawAxes.x, "X");
+ dumpAxisInfo(dump, mRawAxes.y, "Y");
+ dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
+ dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+ dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+ dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+ dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+ dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
}
bool TouchInputMapper::configureSurfaceLocked() {
@@ -1391,10 +1434,8 @@
bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
if (sizeChanged) {
- LOGI("Device reconfigured (display size changed): id=0x%x, name=%s",
- getDeviceId(), getDeviceName().string());
- LOGI(INDENT "Width: %dpx", width);
- LOGI(INDENT "Height: %dpx", height);
+ LOGI("Device reconfigured: id=0x%x, name=%s, display size is now %dx%d",
+ getDeviceId(), getDeviceName().string(), width, height);
mLocked.surfaceWidth = width;
mLocked.surfaceHeight = height;
@@ -1562,39 +1603,13 @@
mLocked.orientedRanges.y.fuzz = orientedYScale;
}
- if (sizeChanged) {
- logMotionRangesLocked();
- }
-
return true;
}
-static void logMotionRangeInfo(InputDeviceInfo::MotionRange* range, const char* name) {
- if (range) {
- LOGI(INDENT "Output %s range: min=%f, max=%f, flat=%f, fuzz=%f",
- name, range->min, range->max, range->flat, range->fuzz);
- } else {
- LOGI(INDENT "Output %s range: unsupported", name);
- }
-}
-
-void TouchInputMapper::logMotionRangesLocked() {
- logMotionRangeInfo(& mLocked.orientedRanges.x, "x");
- logMotionRangeInfo(& mLocked.orientedRanges.y, "y");
- logMotionRangeInfo(mLocked.orientedRanges.havePressure
- ? & mLocked.orientedRanges.pressure : NULL, "pressure");
- logMotionRangeInfo(mLocked.orientedRanges.haveSize
- ? & mLocked.orientedRanges.size : NULL, "size");
- logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
- ? & mLocked.orientedRanges.touchMajor : NULL, "touchMajor");
- logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
- ? & mLocked.orientedRanges.touchMinor : NULL, "touchMinor");
- logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
- ? & mLocked.orientedRanges.toolMajor : NULL, "toolMajor");
- logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
- ? & mLocked.orientedRanges.toolMinor : NULL, "toolMinor");
- logMotionRangeInfo(mLocked.orientedRanges.haveOrientation
- ? & mLocked.orientedRanges.orientation : NULL, "orientation");
+void TouchInputMapper::dumpSurfaceLocked(String8& dump) {
+ dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth);
+ dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight);
+ dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation);
}
void TouchInputMapper::configureVirtualKeysLocked() {
@@ -1651,9 +1666,21 @@
virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
* touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
- LOGI(INDENT "VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
- virtualKey.scanCode, virtualKey.keyCode,
- virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+ }
+}
+
+void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) {
+ if (!mLocked.virtualKeys.isEmpty()) {
+ dump.append(INDENT3 "Virtual Keys:\n");
+
+ for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i);
+ dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+ "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+ i, virtualKey.scanCode, virtualKey.keyCode,
+ virtualKey.hitLeft, virtualKey.hitRight,
+ virtualKey.hitTop, virtualKey.hitBottom);
+ }
}
}
@@ -1861,19 +1888,19 @@
}
}
-void TouchInputMapper::logCalibration() {
- LOGI(INDENT "Calibration:");
+void TouchInputMapper::dumpCalibration(String8& dump) {
+ dump.append(INDENT3 "Calibration:\n");
// Touch Area
switch (mCalibration.touchAreaCalibration) {
case Calibration::TOUCH_AREA_CALIBRATION_NONE:
- LOGI(INDENT INDENT "touch.touchArea.calibration: none");
+ dump.append(INDENT4 "touch.touchArea.calibration: none\n");
break;
case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
- LOGI(INDENT INDENT "touch.touchArea.calibration: geometric");
+ dump.append(INDENT4 "touch.touchArea.calibration: geometric\n");
break;
case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
- LOGI(INDENT INDENT "touch.touchArea.calibration: pressure");
+ dump.append(INDENT4 "touch.touchArea.calibration: pressure\n");
break;
default:
assert(false);
@@ -1882,40 +1909,43 @@
// Tool Area
switch (mCalibration.toolAreaCalibration) {
case Calibration::TOOL_AREA_CALIBRATION_NONE:
- LOGI(INDENT INDENT "touch.toolArea.calibration: none");
+ dump.append(INDENT4 "touch.toolArea.calibration: none\n");
break;
case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
- LOGI(INDENT INDENT "touch.toolArea.calibration: geometric");
+ dump.append(INDENT4 "touch.toolArea.calibration: geometric\n");
break;
case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
- LOGI(INDENT INDENT "touch.toolArea.calibration: linear");
+ dump.append(INDENT4 "touch.toolArea.calibration: linear\n");
break;
default:
assert(false);
}
if (mCalibration.haveToolAreaLinearScale) {
- LOGI(INDENT INDENT "touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+ dump.appendFormat(INDENT4 "touch.toolArea.linearScale: %0.3f\n",
+ mCalibration.toolAreaLinearScale);
}
if (mCalibration.haveToolAreaLinearBias) {
- LOGI(INDENT INDENT "touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+ dump.appendFormat(INDENT4 "touch.toolArea.linearBias: %0.3f\n",
+ mCalibration.toolAreaLinearBias);
}
if (mCalibration.haveToolAreaIsSummed) {
- LOGI(INDENT INDENT "touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+ dump.appendFormat(INDENT4 "touch.toolArea.isSummed: %d\n",
+ mCalibration.toolAreaIsSummed);
}
// Pressure
switch (mCalibration.pressureCalibration) {
case Calibration::PRESSURE_CALIBRATION_NONE:
- LOGI(INDENT INDENT "touch.pressure.calibration: none");
+ dump.append(INDENT4 "touch.pressure.calibration: none\n");
break;
case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
- LOGI(INDENT INDENT "touch.pressure.calibration: physical");
+ dump.append(INDENT4 "touch.pressure.calibration: physical\n");
break;
case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
- LOGI(INDENT INDENT "touch.pressure.calibration: amplitude");
+ dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
break;
default:
assert(false);
@@ -1923,10 +1953,10 @@
switch (mCalibration.pressureSource) {
case Calibration::PRESSURE_SOURCE_PRESSURE:
- LOGI(INDENT INDENT "touch.pressure.source: pressure");
+ dump.append(INDENT4 "touch.pressure.source: pressure\n");
break;
case Calibration::PRESSURE_SOURCE_TOUCH:
- LOGI(INDENT INDENT "touch.pressure.source: touch");
+ dump.append(INDENT4 "touch.pressure.source: touch\n");
break;
case Calibration::PRESSURE_SOURCE_DEFAULT:
break;
@@ -1935,16 +1965,17 @@
}
if (mCalibration.havePressureScale) {
- LOGI(INDENT INDENT "touch.pressure.scale: %f", mCalibration.pressureScale);
+ dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+ mCalibration.pressureScale);
}
// Size
switch (mCalibration.sizeCalibration) {
case Calibration::SIZE_CALIBRATION_NONE:
- LOGI(INDENT INDENT "touch.size.calibration: none");
+ dump.append(INDENT4 "touch.size.calibration: none\n");
break;
case Calibration::SIZE_CALIBRATION_NORMALIZED:
- LOGI(INDENT INDENT "touch.size.calibration: normalized");
+ dump.append(INDENT4 "touch.size.calibration: normalized\n");
break;
default:
assert(false);
@@ -1953,10 +1984,10 @@
// Orientation
switch (mCalibration.orientationCalibration) {
case Calibration::ORIENTATION_CALIBRATION_NONE:
- LOGI(INDENT INDENT "touch.orientation.calibration: none");
+ dump.append(INDENT4 "touch.orientation.calibration: none\n");
break;
case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
- LOGI(INDENT INDENT "touch.orientation.calibration: interpolated");
+ dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
break;
default:
assert(false);
@@ -2139,10 +2170,7 @@
int32_t keyCode, int32_t scanCode, nsecs_t downTime) {
int32_t metaState = mContext->getGlobalMetaState();
- if (keyEventAction == AKEY_EVENT_ACTION_DOWN) {
- getPolicy()->virtualKeyDownFeedback();
- }
-
+ policyFlags |= POLICY_FLAG_VIRTUAL;
int32_t policyActions = getPolicy()->interceptKey(when, getDeviceId(),
keyEventAction == AKEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags);
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index 9fcae72..bee86b2 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -86,6 +86,16 @@
*/
#define kZipEntryAdj 10000
+ZipFileRO::~ZipFileRO() {
+ free(mHashTable);
+ if (mDirectoryMap)
+ mDirectoryMap->release();
+ if (mFd >= 0)
+ TEMP_FAILURE_RETRY(close(mFd));
+ if (mFileName)
+ free(mFileName);
+}
+
/*
* Convert a ZipEntryRO to a hash table index, verifying that it's in a
* valid range.
@@ -122,7 +132,7 @@
mFileLength = lseek(fd, 0, SEEK_END);
if (mFileLength < kEOCDLen) {
- close(fd);
+ TEMP_FAILURE_RETRY(close(fd));
return UNKNOWN_ERROR;
}
@@ -152,7 +162,7 @@
bail:
free(mFileName);
mFileName = NULL;
- close(fd);
+ TEMP_FAILURE_RETRY(close(fd));
return UNKNOWN_ERROR;
}
@@ -512,12 +522,14 @@
LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
return false;
}
- }
- if (get4LE(lfhBuf) != kLFHSignature) {
- LOGW("didn't find signature at start of lfh, offset=%ld (got 0x%08lx, expected 0x%08x)\n",
- localHdrOffset, get4LE(lfhBuf), kLFHSignature);
- return false;
+ if (get4LE(lfhBuf) != kLFHSignature) {
+ off_t actualOffset = lseek(mFd, 0, SEEK_CUR);
+ LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+ "got: offset=%zd data=0x%08lx\n",
+ localHdrOffset, kLFHSignature, (size_t)actualOffset, get4LE(lfhBuf));
+ return false;
+ }
}
off_t dataOffset = localHdrOffset + kLFHLen
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
index 468ba2a..8da7eaa 100755
--- a/media/java/android/media/videoeditor/AudioTrack.java
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -18,23 +18,29 @@
import java.io.IOException;
+import android.util.Log;
+
/**
* This class allows to handle an audio track. This audio file is mixed with the
* audio samples of the MediaItems.
* {@hide}
*/
public class AudioTrack {
+ // Logging
+ private static final String TAG = "AudioTrack";
+
// Instance variables
private final String mUniqueId;
private final String mFilename;
- private final long mDurationMs;
private long mStartTimeMs;
private long mTimelineDurationMs;
private int mVolumePercent;
private long mBeginBoundaryTimeMs;
private long mEndBoundaryTimeMs;
private boolean mLoop;
+ private boolean mMuted;
+ private final long mDurationMs;
private final int mAudioChannels;
private final int mAudioType;
private final int mAudioBitrate;
@@ -47,6 +53,129 @@
// The audio waveform filename
private String mAudioWaveformFilename;
+ private PlaybackThread mPlaybackThread;
+
+ /**
+ * This listener interface is used by the AudioTrack to emit playback
+ * progress notifications.
+ */
+ public interface PlaybackProgressListener {
+ /**
+ * This method notifies the listener of the current time position while
+ * playing an audio track
+ *
+ * @param audioTrack The audio track
+ * @param timeMs The current playback position (expressed in milliseconds
+ * since the beginning of the audio track).
+ * @param end true if the end of the audio track was reached
+ */
+ public void onProgress(AudioTrack audioTrack, long timeMs, boolean end);
+ }
+
+ /**
+ * The playback thread
+ */
+ private class PlaybackThread extends Thread {
+ // Instance variables
+ private final PlaybackProgressListener mListener;
+ private final long mFromMs, mToMs;
+ private boolean mRun;
+ private final boolean mLoop;
+ private long mPositionMs;
+
+ /**
+ * Constructor
+ *
+ * @param fromMs The time (relative to the beginning of the audio track)
+ * at which the playback will start
+ * @param toMs The time (relative to the beginning of the audio track) at
+ * which the playback will stop. Use -1 to play to the end of
+ * the audio track
+ * @param loop true if the playback should be looped once it reaches the
+ * end
+ * @param listener The listener which will be notified of the playback
+ * progress
+ */
+ public PlaybackThread(long fromMs, long toMs, boolean loop,
+ PlaybackProgressListener listener) {
+ mPositionMs = mFromMs = fromMs;
+ if (toMs < 0) {
+ mToMs = mDurationMs;
+ } else {
+ mToMs = toMs;
+ }
+ mLoop = loop;
+ mListener = listener;
+ mRun = true;
+ }
+
+ /*
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "===> PlaybackThread.run enter");
+ }
+
+ while (mRun) {
+ try {
+ sleep(100);
+ } catch (InterruptedException ex) {
+ break;
+ }
+
+ mPositionMs += 100;
+
+ if (mPositionMs >= mToMs) {
+ if (!mLoop) {
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, true);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PlaybackThread.run playback complete");
+ }
+ break;
+ } else {
+ // Fire a notification for the end of the clip
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mToMs, false);
+ }
+
+ // Rewind
+ mPositionMs = mFromMs;
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, false);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PlaybackThread.run playback complete");
+ }
+ }
+ } else {
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, false);
+ }
+ }
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "===> PlaybackThread.run exit");
+ }
+ }
+
+ /**
+ * Stop the playback
+ *
+ * @return The stop position
+ */
+ public long stopPlayback() {
+ mRun = false;
+ try {
+ join();
+ } catch (InterruptedException ex) {
+ }
+ return mPositionMs;
+ }
+ };
/**
* An object of this type cannot be instantiated by using the default
@@ -59,7 +188,7 @@
/**
* Constructor
- * @param audioTrackId The AudioTrack id
+ * @param audioTrackId The audio track id
* @param filename The absolute file name
*
* @throws IOException if file is not found
@@ -89,6 +218,9 @@
// By default loop is disabled
mLoop = false;
+ // By default the audio track is not muted
+ mMuted = false;
+
// Ducking is enabled by default
mDuckingThreshold = 0;
mDuckingLowVolume = 0;
@@ -99,7 +231,53 @@
}
/**
- * @return The id of the media item
+ * Constructor
+ *
+ * @param audioTrackId The audio track id
+ * @param filename The audio filename
+ * @param startTimeMs the start time in milliseconds (relative to the
+ * timeline)
+ * @param beginMs start time in the audio track in milliseconds (relative to
+ * the beginning of the audio track)
+ * @param endMs end time in the audio track in milliseconds (relative to the
+ * beginning of the audio track)
+ * @param loop true to loop the audio track
+ * @param volume The volume in percentage
+ * @param muted true if the audio track is muted
+ * @param audioWaveformFilename The name of the waveform file
+ *
+ * @throws IOException if file is not found
+ */
+ AudioTrack(String audioTrackId, String filename, long startTimeMs, long beginMs, long endMs,
+ boolean loop, int volume, boolean muted, String audioWaveformFilename) throws IOException {
+ mUniqueId = audioTrackId;
+ mFilename = filename;
+ mStartTimeMs = startTimeMs;
+
+ // TODO: This value represents to the duration of the audio file
+ mDurationMs = 300000;
+
+ // TODO: This value needs to be read from the audio track of the source
+ // file
+ mAudioChannels = 2;
+ mAudioType = MediaProperties.ACODEC_AAC_LC;
+ mAudioBitrate = 128000;
+ mAudioSamplingFrequency = 44100;
+
+ mTimelineDurationMs = endMs - beginMs;
+ mVolumePercent = volume;
+
+ mBeginBoundaryTimeMs = beginMs;
+ mEndBoundaryTimeMs = endMs;
+
+ mLoop = loop;
+ mMuted = muted;
+
+ mAudioWaveformFilename = audioWaveformFilename;
+ }
+
+ /**
+ * @return The id of the audio track
*/
public String getId() {
return mUniqueId;
@@ -169,6 +347,20 @@
}
/**
+ * @param muted true to mute the audio track
+ */
+ public void setMute(boolean muted) {
+ mMuted = muted;
+ }
+
+ /**
+ * @return true if the audio track is muted
+ */
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ /**
* Set the start time of this audio track relative to the storyboard
* timeline. Default value is 0.
*
@@ -312,6 +504,50 @@
}
/**
+ * Start the playback of this audio track. This method does not block (does
+ * not wait for the playback to complete).
+ *
+ * @param fromMs The time (relative to the beginning of the audio track) at
+ * which the playback will start
+ * @param toMs The time (relative to the beginning of the audio track) at
+ * which the playback will stop. Use -1 to play to the end of the
+ * audio track
+ * @param loop true if the playback should be looped once it reaches the end
+ * @param listener The listener which will be notified of the playback
+ * progress
+ * @throws IllegalArgumentException if fromMs or toMs is beyond the playback
+ * duration
+ * @throws IllegalStateException if a playback, preview or an export is
+ * already in progress
+ */
+ public void startPlayback(long fromMs, long toMs, boolean loop,
+ PlaybackProgressListener listener) {
+ if (fromMs >= mDurationMs) {
+ return;
+ }
+ mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener);
+ mPlaybackThread.start();
+ }
+
+ /**
+ * Stop the audio track playback. This method blocks until the ongoing
+ * playback is stopped.
+ *
+ * @return The accurate current time when stop is effective expressed in
+ * milliseconds
+ */
+ public long stopPlayback() {
+ final long stopTimeMs;
+ if (mPlaybackThread != null) {
+ stopTimeMs = mPlaybackThread.stopPlayback();
+ mPlaybackThread = null;
+ } else {
+ stopTimeMs = 0;
+ }
+ return stopTimeMs;
+ }
+
+ /**
* This API allows to generate a file containing the sample volume levels of
* this audio track object. This function may take significant time and is
* blocking. The filename can be retrieved using getAudioWaveformFilename().
diff --git a/media/java/android/media/videoeditor/EffectKenBurns.java b/media/java/android/media/videoeditor/EffectKenBurns.java
index fd2da5c..ae2e70d 100755
--- a/media/java/android/media/videoeditor/EffectKenBurns.java
+++ b/media/java/android/media/videoeditor/EffectKenBurns.java
@@ -16,8 +16,6 @@
package android.media.videoeditor;
-import java.io.IOException;
-
import android.graphics.Rect;
/**
@@ -34,7 +32,7 @@
* constructor
*/
@SuppressWarnings("unused")
- private EffectKenBurns() throws IOException {
+ private EffectKenBurns() {
this(null, null, null, null, 0, 0);
}
@@ -49,9 +47,8 @@
* @param durationMs The duration of the Ken Burns effect in milliseconds
*/
public EffectKenBurns(MediaItem mediaItem, String effectId, Rect startRect, Rect endRect,
- long startTime, long durationMs)
- throws IOException {
- super(mediaItem, effectId, startTime, durationMs);
+ long startTimeMs, long durationMs) {
+ super(mediaItem, effectId, startTimeMs, durationMs);
mStartRect = startRect;
mEndRect = endRect;
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index 47d4fa0..cc0a155 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -20,7 +20,6 @@
import java.util.List;
import android.graphics.Bitmap;
-import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -50,6 +49,7 @@
private long mBeginBoundaryTimeMs;
private long mEndBoundaryTimeMs;
private int mVolumePercentage;
+ private boolean mMuted;
private String mAudioWaveformFilename;
private PlaybackThread mPlaybackThread;
@@ -207,7 +207,7 @@
*/
public MediaVideoItem(String mediaItemId, String filename, int renderingMode)
throws IOException {
- this(mediaItemId, filename, renderingMode, null);
+ this(mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);
}
/**
@@ -216,11 +216,19 @@
* @param mediaItemId The MediaItem id
* @param filename The image file name
* @param renderingMode The rendering mode
+ * @param beginMs Start time in milliseconds. Set to 0 to extract from the
+ * beginning
+ * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
+ * extract until the end
+ * @param volumePercent in %/. 100% means no change; 50% means half value, 200%
+ * means double, 0% means silent.
+ * @param muted true if the audio is muted
* @param audioWaveformFilename The name of the audio waveform file
*
* @throws IOException if the file cannot be opened for reading
*/
MediaVideoItem(String mediaItemId, String filename, int renderingMode,
+ long beginMs, long endMs, int volumePercent, boolean muted,
String audioWaveformFilename) throws IOException {
super(mediaItemId, filename, renderingMode);
// TODO: Set these variables correctly
@@ -228,7 +236,7 @@
mHeight = 720;
mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
mFileType = MediaProperties.FILE_MP4;
- mVideoType = MediaRecorder.VideoEncoder.H264;
+ mVideoType = MediaProperties.VCODEC_H264BP;
// Do we have predefined values for this variable?
mVideoProfile = 0;
// Can video and audio duration be different?
@@ -240,9 +248,10 @@
mAudioChannels = 2;
mAudioSamplingFrequency = 16000;
- mBeginBoundaryTimeMs = 0;
- mEndBoundaryTimeMs = mDurationMs;
- mVolumePercentage = 100;
+ mBeginBoundaryTimeMs = beginMs;
+ mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs;
+ mVolumePercentage = volumePercent;
+ mMuted = muted;
mAudioWaveformFilename = audioWaveformFilename;
}
@@ -540,6 +549,20 @@
}
/**
+ * @param muted true to mute the media item
+ */
+ public void setMute(boolean muted) {
+ mMuted = muted;
+ }
+
+ /**
+ * @return true if the media item is muted
+ */
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ /**
* @return The video type
*/
public int getVideoType() {
diff --git a/media/java/android/media/videoeditor/OverlayFrame.java b/media/java/android/media/videoeditor/OverlayFrame.java
index c7dfc39..dcac4ba 100755
--- a/media/java/android/media/videoeditor/OverlayFrame.java
+++ b/media/java/android/media/videoeditor/OverlayFrame.java
@@ -118,18 +118,18 @@
/**
* Save the overlay to the project folder
*
- * @param editor The video editor
+ * @param path The path where the overlay will be saved
*
- * @return
+ * @return The filename
* @throws FileNotFoundException if the bitmap cannot be saved
* @throws IOException if the bitmap file cannot be saved
*/
- String save(VideoEditor editor) throws FileNotFoundException, IOException {
+ String save(String path) throws FileNotFoundException, IOException {
if (mFilename != null) {
return mFilename;
}
- mFilename = editor.getPath() + "/" + getId() + ".png";
+ mFilename = path + "/" + getId() + ".png";
// Save the image to a local file
final FileOutputStream out = new FileOutputStream(mFilename);
mBitmap.compress(CompressFormat.PNG, 100, out);
diff --git a/media/java/android/media/videoeditor/VideoEditorFactory.java b/media/java/android/media/videoeditor/VideoEditorFactory.java
index 2c56fc2..0a377e2 100755
--- a/media/java/android/media/videoeditor/VideoEditorFactory.java
+++ b/media/java/android/media/videoeditor/VideoEditorFactory.java
@@ -19,6 +19,8 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
/**
@@ -28,12 +30,19 @@
* {@hide}
*/
public class VideoEditorFactory {
+ // VideoEditor implementation classes
+ public static final String TEST_CLASS_IMPLEMENTATION
+ = "android.media.videoeditor.VideoEditorTestImpl";
+ public static final String DEFAULT_CLASS_IMPLEMENTATION
+ = "android.media.videoeditor.VideoEditorImpl";
+
/**
* This is the factory method for creating a new VideoEditor instance.
*
* @param projectPath The path where all VideoEditor internal
* files are stored. When a project is deleted the application is
* responsible for deleting the path and its contents.
+ * @param className The implementation class name
*
* @return The VideoEditor instance
*
@@ -41,8 +50,14 @@
* not be accessed in read/write mode
* @throws IllegalStateException if a previous VideoEditor instance has not
* been released
+ * @throws ClassNotFoundException, NoSuchMethodException,
+ * InvocationTargetException, IllegalAccessException,
+ * InstantiationException if the implementation class cannot
+ * be instantiated.
*/
- public static VideoEditor create(String projectPath) throws IOException {
+ public static VideoEditor create(String projectPath, String className) throws IOException,
+ ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+ IllegalAccessException, InstantiationException {
// If the project path does not exist create it
final File dir = new File(projectPath);
if (!dir.exists()) {
@@ -50,7 +65,15 @@
throw new FileNotFoundException("Cannot create project path: " + projectPath);
}
}
- return new VideoEditorTestImpl(projectPath);
+
+ final Class<?> cls = Class.forName(className);
+ final Class<?> partypes[] = new Class[1];
+ partypes[0] = String.class;
+ final Constructor<?> ct = cls.getConstructor(partypes);
+ final Object arglist[] = new Object[1];
+ arglist[0] = projectPath;
+
+ return (VideoEditor)ct.newInstance(arglist);
}
/**
@@ -61,6 +84,7 @@
* @param projectPath The path where all VideoEditor internal files
* are stored. When a project is deleted the application is
* responsible for deleting the path and its contents.
+ * @param className The implementation class name
* @param generatePreview if set to true the
* {@link MediaEditor#generatePreview()} will be called internally to
* generate any needed transitions.
@@ -73,8 +97,17 @@
* @throws IllegalStateException if a previous VideoEditor instance has not
* been released
*/
- public static VideoEditor load(String projectPath, boolean generatePreview) throws IOException {
- final VideoEditorTestImpl videoEditor = new VideoEditorTestImpl(projectPath);
+ public static VideoEditor load(String projectPath, String className, boolean generatePreview)
+ throws IOException, ClassNotFoundException, NoSuchMethodException,
+ InvocationTargetException, IllegalAccessException, InstantiationException {
+ final Class<?> cls = Class.forName(className);
+ final Class<?> partypes[] = new Class[1];
+ partypes[0] = String.class;
+ final Constructor<?> ct = cls.getConstructor(partypes);
+ final Object arglist[] = new Object[1];
+ arglist[0] = projectPath;
+
+ final VideoEditor videoEditor = (VideoEditor)ct.newInstance(arglist);
if (generatePreview) {
videoEditor.generatePreview();
}
diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
index 5df4ea5..b39d9d8 100644
--- a/media/java/android/media/videoeditor/VideoEditorTestImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -31,6 +31,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.graphics.Rect;
import android.util.Log;
import android.util.Xml;
import android.view.SurfaceHolder;
@@ -54,6 +55,10 @@
private static final String TAG_OVERLAYS = "overlays";
private static final String TAG_OVERLAY = "overlay";
private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
+ private static final String TAG_EFFECTS = "effects";
+ private static final String TAG_EFFECT = "effect";
+ private static final String TAG_AUDIO_TRACKS = "audio_tracks";
+ private static final String TAG_AUDIO_TRACK = "audio_track";
private static final String ATTR_ID = "id";
private static final String ATTR_FILENAME = "filename";
@@ -62,7 +67,8 @@
private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
private static final String ATTR_TYPE = "type";
private static final String ATTR_DURATION = "duration";
- private static final String ATTR_BEGIN_TIME = "start_time";
+ private static final String ATTR_START_TIME = "start_time";
+ private static final String ATTR_BEGIN_TIME = "begin_time";
private static final String ATTR_END_TIME = "end_time";
private static final String ATTR_VOLUME = "volume";
private static final String ATTR_BEHAVIOR = "behavior";
@@ -72,6 +78,18 @@
private static final String ATTR_MASK = "mask";
private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
+ private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
+ private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
+ private static final String ATTR_START_RECT_L = "start_l";
+ private static final String ATTR_START_RECT_T = "start_t";
+ private static final String ATTR_START_RECT_R = "start_r";
+ private static final String ATTR_START_RECT_B = "start_b";
+ private static final String ATTR_END_RECT_L = "end_l";
+ private static final String ATTR_END_RECT_T = "end_t";
+ private static final String ATTR_END_RECT_R = "end_r";
+ private static final String ATTR_END_RECT_B = "end_b";
+ private static final String ATTR_LOOP = "loop";
+ private static final String ATTR_MUTED = "muted";
// Instance variables
private long mDurationMs;
@@ -524,16 +542,13 @@
* {@inheritDoc}
*/
public AudioTrack getAudioTrack(String audioTrackId) {
- if (mPreviewThread != null) {
- throw new IllegalStateException("Previewing is in progress");
+ for (AudioTrack at : mAudioTracks) {
+ if (at.getId().equals(audioTrackId)) {
+ return at;
+ }
}
- final AudioTrack audioTrack = getAudioTrack(audioTrackId);
- if (audioTrack != null) {
- mAudioTracks.remove(audioTrack);
- }
-
- return audioTrack;
+ return null;
}
/*
@@ -568,6 +583,7 @@
.attribute("", ATTR_BEGIN_TIME, Long.toString(mvi.getBoundaryBeginTime()));
serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime()));
serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume()));
+ serializer.attribute("", ATTR_MUTED, Boolean.toString(mvi.isMuted()));
if (mvi.getAudioWaveformFilename() != null) {
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
mvi.getAudioWaveformFilename());
@@ -589,7 +605,7 @@
serializer.attribute("", ATTR_DURATION, Long.toString(overlay.getDuration()));
if (overlay instanceof OverlayFrame) {
final OverlayFrame overlayFrame = (OverlayFrame)overlay;
- overlayFrame.save(this);
+ overlayFrame.save(getPath());
if (overlayFrame.getFilename() != null) {
serializer.attribute("", ATTR_FILENAME, overlayFrame.getFilename());
}
@@ -611,6 +627,48 @@
serializer.endTag("", TAG_OVERLAYS);
}
+ final List<Effect> effects = mediaItem.getAllEffects();
+ if (effects.size() > 0) {
+ serializer.startTag("", TAG_EFFECTS);
+ for (Effect effect : effects) {
+ serializer.startTag("", TAG_EFFECT);
+ serializer.attribute("", ATTR_ID, effect.getId());
+ serializer.attribute("", ATTR_TYPE, effect.getClass().getSimpleName());
+ serializer.attribute("", ATTR_BEGIN_TIME,
+ Long.toString(effect.getStartTime()));
+ serializer.attribute("", ATTR_DURATION, Long.toString(effect.getDuration()));
+ if (effect instanceof EffectColor) {
+ final EffectColor colorEffect = (EffectColor)effect;
+ serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
+ Integer.toString(colorEffect.getType()));
+ if (colorEffect.getType() == EffectColor.TYPE_COLOR) {
+ serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
+ Integer.toString(colorEffect.getParam()));
+ }
+ } else if (effect instanceof EffectKenBurns) {
+ final Rect startRect = ((EffectKenBurns)effect).getStartRect();
+ serializer.attribute("", ATTR_START_RECT_L,
+ Integer.toString(startRect.left));
+ serializer.attribute("", ATTR_START_RECT_T,
+ Integer.toString(startRect.top));
+ serializer.attribute("", ATTR_START_RECT_R,
+ Integer.toString(startRect.right));
+ serializer.attribute("", ATTR_START_RECT_B,
+ Integer.toString(startRect.bottom));
+
+ final Rect endRect = ((EffectKenBurns)effect).getEndRect();
+ serializer.attribute("", ATTR_END_RECT_L, Integer.toString(endRect.left));
+ serializer.attribute("", ATTR_END_RECT_T, Integer.toString(endRect.top));
+ serializer.attribute("", ATTR_END_RECT_R, Integer.toString(endRect.right));
+ serializer.attribute("", ATTR_END_RECT_B,
+ Integer.toString(endRect.bottom));
+ }
+
+ serializer.endTag("", TAG_EFFECT);
+ }
+ serializer.endTag("", TAG_EFFECTS);
+ }
+
serializer.endTag("", TAG_MEDIA_ITEM);
}
serializer.endTag("", TAG_MEDIA_ITEMS);
@@ -648,6 +706,26 @@
}
serializer.endTag("", TAG_TRANSITIONS);
+ serializer.startTag("", TAG_AUDIO_TRACKS);
+ for (AudioTrack at : mAudioTracks) {
+ serializer.startTag("", TAG_AUDIO_TRACK);
+ serializer.attribute("", ATTR_ID, at.getId());
+ serializer.attribute("", ATTR_FILENAME, at.getFilename());
+ serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
+ serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
+ serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
+ serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
+ serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
+ serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
+ if (at.getAudioWaveformFilename() != null) {
+ serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
+ at.getAudioWaveformFilename());
+ }
+
+ serializer.endTag("", TAG_AUDIO_TRACK);
+ }
+ serializer.endTag("", TAG_AUDIO_TRACKS);
+
serializer.endTag("", TAG_PROJECT);
serializer.endDocument();
@@ -690,10 +768,19 @@
currentMediaItem = new MediaImageItem(mediaItemId, filename,
durationMs, renderingMode);
} else if (MediaVideoItem.class.getSimpleName().equals(type)) {
+ final long beginMs = Long.parseLong(parser.getAttributeValue("",
+ ATTR_BEGIN_TIME));
+ final long endMs = Long.parseLong(parser.getAttributeValue("",
+ ATTR_END_TIME));
+ final int volume = Integer.parseInt(parser.getAttributeValue("",
+ ATTR_VOLUME));
+ final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
+ ATTR_MUTED));
final String audioWaveformFilename = parser.getAttributeValue("",
ATTR_AUDIO_WAVEFORM_FILENAME);
currentMediaItem = new MediaVideoItem(mediaItemId, filename,
- renderingMode, audioWaveformFilename);
+ renderingMode, beginMs, endMs, volume, muted,
+ audioWaveformFilename);
final long beginTimeMs = Long.parseLong(parser.getAttributeValue("",
ATTR_BEGIN_TIME));
@@ -733,6 +820,18 @@
parser.getAttributeValue(i));
}
}
+ } else if (TAG_EFFECT.equals(name)) {
+ if (currentMediaItem != null) {
+ final Effect effect = parseEffect(parser, currentMediaItem);
+ if (effect != null) {
+ currentMediaItem.addEffect(effect);
+ }
+ }
+ } else if (TAG_AUDIO_TRACK.equals(name)) {
+ final AudioTrack audioTrack = parseAudioTrack(parser);
+ if (audioTrack != null) {
+ addAudioTrack(audioTrack);
+ }
}
break;
}
@@ -854,6 +953,79 @@
return overlay;
}
+ /**
+ * Parse the effect
+ *
+ * @param parser The parser
+ * @param mediaItem The media item owner
+ *
+ * @return The effect
+ */
+ private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
+ final String effectId = parser.getAttributeValue("", ATTR_ID);
+ final String type = parser.getAttributeValue("", ATTR_TYPE);
+ final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
+ final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+
+ final Effect effect;
+ if (EffectColor.class.getSimpleName().equals(type)) {
+ final int colorEffectType =
+ Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_TYPE));
+ final int color;
+ if (colorEffectType == EffectColor.TYPE_COLOR) {
+ color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
+ } else {
+ color = 0;
+ }
+ effect = new EffectColor(mediaItem, effectId, startTimeMs, durationMs,
+ colorEffectType, color);
+ } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
+ final Rect startRect = new Rect(
+ Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_L)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_T)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_R)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_B)));
+ final Rect endRect = new Rect(
+ Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_L)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_T)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_R)),
+ Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_B)));
+ effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect, startTimeMs,
+ durationMs);
+ } else {
+ effect = null;
+ }
+
+ return effect;
+ }
+
+ /**
+ * Parse the audio track
+ *
+ * @param parser The parser
+ *
+ * @return The audio track
+ */
+ private AudioTrack parseAudioTrack(XmlPullParser parser) {
+ final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
+ final String filename = parser.getAttributeValue("", ATTR_FILENAME);
+ final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
+ final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+ final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
+ final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
+ final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
+ final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
+ final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
+ try {
+ final AudioTrack audioTrack = new AudioTrack(audioTrackId, filename, startTimeMs,
+ beginMs, endMs, loop, volume, muted, waveformFilename);
+
+ return audioTrack;
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
public void cancelExport(String filename) {
}
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 890786e..587c8ff 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -316,6 +316,7 @@
mNewPosition = mCblk->server + mUpdatePeriod;
mCblk->bufferTimeoutMs = MAX_STARTUP_TIMEOUT_MS;
mCblk->waitTimeMs = 0;
+ mCblk->flags &= ~CBLK_DISABLED_ON;
if (t != 0) {
t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);
} else {
@@ -842,6 +843,13 @@
cblk->lock.unlock();
}
+ // restart track if it was disabled by audioflinger due to previous underrun
+ if (cblk->flags & CBLK_DISABLED_MSK) {
+ cblk->flags &= ~CBLK_DISABLED_ON;
+ LOGW("obtainBuffer() track %p disabled, restarting", this);
+ mAudioTrack->start();
+ }
+
cblk->waitTimeMs = 0;
if (framesReq > framesAvail) {
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 97c9003..31c03ad 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -276,20 +276,6 @@
status_t AwesomePlayer::setDataSource(
int fd, int64_t offset, int64_t length) {
-#if 0
- // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/sl.m3u8");
- return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/0440.m3u8");
- // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/0640.m3u8");
- // return setDataSource("httplive://qthttp.apple.com.edgesuite.net/1009qpeijrfn/1240_vod.m3u8");
- // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_96.m3u8");
- // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_1500.m3u8");
- // return setDataSource("httplive://iphone.video.hsn.com/iPhone_high.m3u8");
- // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/iphonewebcast/webcast090209_all/webcast090209_all.m3u8");
- // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_062209_iphone/hi/prog_index.m3u8");
- // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_googmaps/hi/prog_index.m3u8");
- // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/mtv/ni_spo_25a_rt74137_clip_syn/hi/prog_index.m3u8");
-#endif
-
Mutex::Autolock autoLock(mLock);
reset_l();
@@ -334,11 +320,17 @@
setAudioSource(extractor->getTrack(i));
haveAudio = true;
- sp<MetaData> fileMeta = extractor->getMetaData();
- int32_t loop;
- if (fileMeta != NULL
- && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
- mFlags |= AUTO_LOOPING;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
+ // Only do this for vorbis audio, none of the other audio
+ // formats even support this ringtone specific hack and
+ // retrieving the metadata on some extractors may turn out
+ // to be very expensive.
+ sp<MetaData> fileMeta = extractor->getMetaData();
+ int32_t loop;
+ if (fileMeta != NULL
+ && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
+ mFlags |= AUTO_LOOPING;
+ }
}
}
@@ -495,6 +487,10 @@
if (eos) {
notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ if (mFlags & PREPARING) {
+ LOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
} else {
off_t size;
if (mDurationUs >= 0 && mCachedSource->getSize(&size) == OK) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index e6c2f7e..546df47 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1118,7 +1118,7 @@
startTimeUs = 0;
}
- mIsRealTimeRecording = true;
+ mIsRealTimeRecording = false;
{
int32_t isNotRealTime;
if (params && params->findInt32(kKeyNotRealTime, &isNotRealTime)) {
diff --git a/media/libstagefright/avc_utils.cpp b/media/libstagefright/avc_utils.cpp
index 511ae12..a8f1104 100644
--- a/media/libstagefright/avc_utils.cpp
+++ b/media/libstagefright/avc_utils.cpp
@@ -21,7 +21,7 @@
namespace android {
-static unsigned parseUE(ABitReader *br) {
+unsigned parseUE(ABitReader *br) {
unsigned numZeroes = 0;
while (br->getBits(1) == 0) {
++numZeroes;
diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
index 3c0b736..868c514 100644
--- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
+++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp
@@ -31,6 +31,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/hexdump.h>
namespace android {
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 001afc4..9103927 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -93,7 +93,7 @@
}
if (mLastFetchTimeUs < 0) {
- mPlaylistIndex = mPlaylist->size() / 2;
+ mPlaylistIndex = 0;
} else {
if (nextSequenceNumber < mFirstItemSequenceNumber
|| nextSequenceNumber
diff --git a/media/libstagefright/include/avc_utils.h b/media/libstagefright/include/avc_utils.h
index cc405b5..6602852 100644
--- a/media/libstagefright/include/avc_utils.h
+++ b/media/libstagefright/include/avc_utils.h
@@ -22,9 +22,13 @@
namespace android {
+struct ABitReader;
+
void FindAVCDimensions(
const sp<ABuffer> &seqParamSet, int32_t *width, int32_t *height);
+unsigned parseUE(ABitReader *br);
+
} // namespace android
#endif // AVC_UTILS_H_
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 47cca80..bcaab9f 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -21,6 +21,7 @@
#include "ATSParser.h"
#include "AnotherPacketSource.h"
+#include "ESQueue.h"
#include "include/avc_utils.h"
#include <media/stagefright/foundation/ABitReader.h>
@@ -79,6 +80,8 @@
sp<AnotherPacketSource> mSource;
bool mPayloadStarted;
+ ElementaryStreamQueue mQueue;
+
void flush();
void parsePES(ABitReader *br);
@@ -232,7 +235,9 @@
: mElementaryPID(elementaryPID),
mStreamType(streamType),
mBuffer(new ABuffer(128 * 1024)),
- mPayloadStarted(false) {
+ mPayloadStarted(false),
+ mQueue(streamType == 0x1b
+ ? ElementaryStreamQueue::H264 : ElementaryStreamQueue::AAC) {
mBuffer->setRange(0, 0);
}
@@ -433,373 +438,31 @@
mBuffer->setRange(0, 0);
}
-static sp<ABuffer> FindNAL(
- const uint8_t *data, size_t size, unsigned nalType,
- size_t *stopOffset) {
- bool foundStart = false;
- size_t startOffset = 0;
-
- size_t offset = 0;
- for (;;) {
- while (offset + 3 < size
- && memcmp("\x00\x00\x00\x01", &data[offset], 4)) {
- ++offset;
- }
-
- if (foundStart) {
- size_t nalSize;
- if (offset + 3 >= size) {
- nalSize = size - startOffset;
- } else {
- nalSize = offset - startOffset;
- }
-
- sp<ABuffer> nal = new ABuffer(nalSize);
- memcpy(nal->data(), &data[startOffset], nalSize);
-
- if (stopOffset != NULL) {
- *stopOffset = startOffset + nalSize;
- }
-
- return nal;
- }
-
- if (offset + 4 >= size) {
- return NULL;
- }
-
- if ((data[offset + 4] & 0x1f) == nalType) {
- foundStart = true;
- startOffset = offset + 4;
- }
-
- offset += 4;
- }
-}
-
-static sp<ABuffer> MakeAVCCodecSpecificData(
- const sp<ABuffer> &buffer, int32_t *width, int32_t *height) {
- const uint8_t *data = buffer->data();
- size_t size = buffer->size();
-
- sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
- if (seqParamSet == NULL) {
- return NULL;
- }
-
- FindAVCDimensions(seqParamSet, width, height);
-
- size_t stopOffset;
- sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
- CHECK(picParamSet != NULL);
-
- buffer->setRange(stopOffset, size - stopOffset);
- LOGV("buffer has %d bytes left.", buffer->size());
-
- size_t csdSize =
- 1 + 3 + 1 + 1
- + 2 * 1 + seqParamSet->size()
- + 1 + 2 * 1 + picParamSet->size();
-
- sp<ABuffer> csd = new ABuffer(csdSize);
- uint8_t *out = csd->data();
-
- *out++ = 0x01; // configurationVersion
- memcpy(out, seqParamSet->data() + 1, 3); // profile/level...
- out += 3;
- *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes
- *out++ = 0xe0 | 1;
-
- *out++ = seqParamSet->size() >> 8;
- *out++ = seqParamSet->size() & 0xff;
- memcpy(out, seqParamSet->data(), seqParamSet->size());
- out += seqParamSet->size();
-
- *out++ = 1;
-
- *out++ = picParamSet->size() >> 8;
- *out++ = picParamSet->size() & 0xff;
- memcpy(out, picParamSet->data(), picParamSet->size());
-
- return csd;
-}
-
-static bool getNextNALUnit(
- const uint8_t **_data, size_t *_size,
- const uint8_t **nalStart, size_t *nalSize) {
- const uint8_t *data = *_data;
- size_t size = *_size;
-
- // hexdump(data, size);
-
- *nalStart = NULL;
- *nalSize = 0;
-
- if (size == 0) {
- return false;
- }
-
- size_t offset = 0;
- for (;;) {
- CHECK_LT(offset + 2, size);
-
- if (!memcmp("\x00\x00\x01", &data[offset], 3)) {
- break;
- }
-
- CHECK_EQ((unsigned)data[offset], 0x00u);
- ++offset;
- }
-
- offset += 3;
- size_t startOffset = offset;
-
- while (offset + 2 < size
- && memcmp("\x00\x00\x00", &data[offset], 3)
- && memcmp("\x00\x00\x01", &data[offset], 3)) {
- ++offset;
- }
-
- if (offset + 2 >= size) {
- *nalStart = &data[startOffset];
- *nalSize = size - startOffset;
-
- *_data = NULL;
- *_size = 0;
-
- return true;
- }
-
- size_t endOffset = offset;
-
- while (offset + 2 < size && memcmp("\x00\x00\x01", &data[offset], 3)) {
- CHECK_EQ((unsigned)data[offset], 0x00u);
- ++offset;
- }
-
- *nalStart = &data[startOffset];
- *nalSize = endOffset - startOffset;
-
- if (offset + 2 < size) {
- *_data = &data[offset];
- *_size = size - offset;
- } else {
- *_data = NULL;
- *_size = 0;
- }
-
- return true;
-}
-
-sp<ABuffer> MakeCleanAVCData(const uint8_t *data, size_t size) {
- // hexdump(data, size);
-
- const uint8_t *tmpData = data;
- size_t tmpSize = size;
-
- size_t totalSize = 0;
- const uint8_t *nalStart;
- size_t nalSize;
- while (getNextNALUnit(&tmpData, &tmpSize, &nalStart, &nalSize)) {
- // hexdump(nalStart, nalSize);
- totalSize += 4 + nalSize;
- }
-
- sp<ABuffer> buffer = new ABuffer(totalSize);
- size_t offset = 0;
- while (getNextNALUnit(&data, &size, &nalStart, &nalSize)) {
- memcpy(buffer->data() + offset, "\x00\x00\x00\x01", 4);
- memcpy(buffer->data() + offset + 4, nalStart, nalSize);
-
- offset += 4 + nalSize;
- }
-
- return buffer;
-}
-
-static sp<ABuffer> FindMPEG2ADTSConfig(
- const sp<ABuffer> &buffer, int32_t *sampleRate, int32_t *channelCount) {
- ABitReader br(buffer->data(), buffer->size());
-
- CHECK_EQ(br.getBits(12), 0xfffu);
- CHECK_EQ(br.getBits(1), 0u);
- CHECK_EQ(br.getBits(2), 0u);
- br.getBits(1); // protection_absent
- unsigned profile = br.getBits(2);
- LOGV("profile = %u", profile);
- CHECK_NE(profile, 3u);
- unsigned sampling_freq_index = br.getBits(4);
- br.getBits(1); // private_bit
- unsigned channel_configuration = br.getBits(3);
- CHECK_NE(channel_configuration, 0u);
-
- LOGV("sampling_freq_index = %u", sampling_freq_index);
- LOGV("channel_configuration = %u", channel_configuration);
-
- CHECK_LE(sampling_freq_index, 11u);
- static const int32_t kSamplingFreq[] = {
- 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
- 16000, 12000, 11025, 8000
- };
- *sampleRate = kSamplingFreq[sampling_freq_index];
-
- *channelCount = channel_configuration;
-
- static const uint8_t kStaticESDS[] = {
- 0x03, 22,
- 0x00, 0x00, // ES_ID
- 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
-
- 0x04, 17,
- 0x40, // Audio ISO/IEC 14496-3
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
-
- 0x05, 2,
- // AudioSpecificInfo follows
-
- // oooo offf fccc c000
- // o - audioObjectType
- // f - samplingFreqIndex
- // c - channelConfig
- };
- sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
- memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
-
- csd->data()[sizeof(kStaticESDS)] =
- ((profile + 1) << 3) | (sampling_freq_index >> 1);
-
- csd->data()[sizeof(kStaticESDS) + 1] =
- ((sampling_freq_index << 7) & 0x80) | (channel_configuration << 3);
-
- // hexdump(csd->data(), csd->size());
- return csd;
-}
-
void ATSParser::Stream::onPayloadData(
unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS,
const uint8_t *data, size_t size) {
LOGV("onPayloadData mStreamType=0x%02x", mStreamType);
- sp<ABuffer> buffer;
-
- if (mStreamType == 0x1b) {
- buffer = MakeCleanAVCData(data, size);
- } else {
- // hexdump(data, size);
-
- buffer = new ABuffer(size);
- memcpy(buffer->data(), data, size);
- }
-
- if (mSource == NULL) {
- sp<MetaData> meta = new MetaData;
-
- if (mStreamType == 0x1b) {
- meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
-
- int32_t width, height;
- sp<ABuffer> csd = MakeAVCCodecSpecificData(buffer, &width, &height);
-
- if (csd == NULL) {
- return;
- }
-
- meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
- meta->setInt32(kKeyWidth, width);
- meta->setInt32(kKeyHeight, height);
- } else {
- CHECK_EQ(mStreamType, 0x0fu);
-
- meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
-
- int32_t sampleRate, channelCount;
- sp<ABuffer> csd =
- FindMPEG2ADTSConfig(buffer, &sampleRate, &channelCount);
-
- LOGV("sampleRate = %d", sampleRate);
- LOGV("channelCount = %d", channelCount);
-
- meta->setInt32(kKeySampleRate, sampleRate);
- meta->setInt32(kKeyChannelCount, channelCount);
-
- meta->setData(kKeyESDS, 0, csd->data(), csd->size());
- }
-
- LOGV("created source!");
- mSource = new AnotherPacketSource(meta);
-
- // fall through
- }
-
CHECK(PTS_DTS_flags == 2 || PTS_DTS_flags == 3);
- buffer->meta()->setInt64("time", (PTS * 100) / 9);
+ int64_t timeUs = (PTS * 100) / 9;
- if (mStreamType == 0x0f) {
- extractAACFrames(buffer);
- }
+ status_t err = mQueue.appendData(data, size, timeUs);
+ CHECK_EQ(err, (status_t)OK);
- mSource->queueAccessUnit(buffer);
-}
+ sp<ABuffer> accessUnit;
+ while ((accessUnit = mQueue.dequeueAccessUnit()) != NULL) {
+ if (mSource == NULL) {
+ sp<MetaData> meta = mQueue.getFormat();
-// Disassemble one or more ADTS frames into their constituent parts and
-// leave only the concatenated raw_data_blocks in the buffer.
-void ATSParser::Stream::extractAACFrames(const sp<ABuffer> &buffer) {
- size_t dstOffset = 0;
-
- size_t offset = 0;
- while (offset < buffer->size()) {
- CHECK_LE(offset + 7, buffer->size());
-
- ABitReader bits(buffer->data() + offset, buffer->size() - offset);
-
- // adts_fixed_header
-
- CHECK_EQ(bits.getBits(12), 0xfffu);
- bits.skipBits(3); // ID, layer
- bool protection_absent = bits.getBits(1) != 0;
-
- // profile_ObjectType, sampling_frequency_index, private_bits,
- // channel_configuration, original_copy, home
- bits.skipBits(12);
-
- // adts_variable_header
-
- // copyright_identification_bit, copyright_identification_start
- bits.skipBits(2);
-
- unsigned aac_frame_length = bits.getBits(13);
-
- bits.skipBits(11); // adts_buffer_fullness
-
- unsigned number_of_raw_data_blocks_in_frame = bits.getBits(2);
-
- if (number_of_raw_data_blocks_in_frame == 0) {
- size_t scan = offset + aac_frame_length;
-
- offset += 7;
- if (!protection_absent) {
- offset += 2;
+ if (meta != NULL) {
+ LOGV("created source!");
+ mSource = new AnotherPacketSource(meta);
+ mSource->queueAccessUnit(accessUnit);
}
-
- CHECK_LE(scan, buffer->size());
-
- LOGV("found aac raw data block at [0x%08x ; 0x%08x)", offset, scan);
-
- memmove(&buffer->data()[dstOffset], &buffer->data()[offset],
- scan - offset);
-
- dstOffset += scan - offset;
- offset = scan;
} else {
- // To be implemented.
- TRESPASS();
+ mSource->queueAccessUnit(accessUnit);
}
}
- CHECK_EQ(offset, buffer->size());
-
- buffer->setRange(buffer->offset(), dstOffset);
}
sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk
index 3544b4c..4dfc0f7 100644
--- a/media/libstagefright/mpeg2ts/Android.mk
+++ b/media/libstagefright/mpeg2ts/Android.mk
@@ -5,6 +5,7 @@
LOCAL_SRC_FILES:= \
AnotherPacketSource.cpp \
ATSParser.cpp \
+ ESQueue.cpp \
MPEG2TSExtractor.cpp \
LOCAL_C_INCLUDES:= \
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
new file mode 100644
index 0000000..d87040b
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ESQueue"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include "ESQueue.h"
+
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+
+#include "include/avc_utils.h"
+
+namespace android {
+
+ElementaryStreamQueue::ElementaryStreamQueue(Mode mode)
+ : mMode(mode) {
+}
+
+sp<MetaData> ElementaryStreamQueue::getFormat() {
+ return mFormat;
+}
+
+static status_t getNextNALUnit(
+ const uint8_t **_data, size_t *_size,
+ const uint8_t **nalStart, size_t *nalSize,
+ bool startCodeFollows = false) {
+ const uint8_t *data = *_data;
+ size_t size = *_size;
+
+ *nalStart = NULL;
+ *nalSize = 0;
+
+ if (size == 0) {
+ return -EAGAIN;
+ }
+
+ // Skip any number of leading 0x00.
+
+ size_t offset = 0;
+ while (offset < size && data[offset] == 0x00) {
+ ++offset;
+ }
+
+ if (offset == size) {
+ return -EAGAIN;
+ }
+
+ // A valid startcode consists of at least two 0x00 bytes followed by 0x01.
+
+ if (offset < 2 || data[offset] != 0x01) {
+ return ERROR_MALFORMED;
+ }
+
+ ++offset;
+
+ size_t startOffset = offset;
+
+ for (;;) {
+ while (offset < size && data[offset] != 0x01) {
+ ++offset;
+ }
+
+ if (offset == size) {
+ if (startCodeFollows) {
+ offset = size + 2;
+ break;
+ }
+
+ return -EAGAIN;
+ }
+
+ if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) {
+ break;
+ }
+
+ ++offset;
+ }
+
+ size_t endOffset = offset - 2;
+ while (data[endOffset - 1] == 0x00) {
+ --endOffset;
+ }
+
+ *nalStart = &data[startOffset];
+ *nalSize = endOffset - startOffset;
+
+ if (offset + 2 < size) {
+ *_data = &data[offset - 2];
+ *_size = size - offset + 2;
+ } else {
+ *_data = NULL;
+ *_size = 0;
+ }
+
+ return OK;
+}
+
+status_t ElementaryStreamQueue::appendData(
+ const void *data, size_t size, int64_t timeUs) {
+ if (mBuffer == NULL || mBuffer->size() == 0) {
+ switch (mMode) {
+ case H264:
+ {
+ if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
+ return ERROR_MALFORMED;
+ }
+ break;
+ }
+
+ case AAC:
+ {
+ uint8_t *ptr = (uint8_t *)data;
+
+ if (size < 2 || ptr[0] != 0xff || (ptr[1] >> 4) != 0x0f) {
+ return ERROR_MALFORMED;
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+ }
+
+ size_t neededSize = (mBuffer == NULL ? 0 : mBuffer->size()) + size;
+ if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
+ neededSize = (neededSize + 65535) & ~65535;
+
+ LOGI("resizing buffer to size %d", neededSize);
+
+ sp<ABuffer> buffer = new ABuffer(neededSize);
+ if (mBuffer != NULL) {
+ memcpy(buffer->data(), mBuffer->data(), mBuffer->size());
+ buffer->setRange(0, mBuffer->size());
+ } else {
+ buffer->setRange(0, 0);
+ }
+
+ mBuffer = buffer;
+ }
+
+ memcpy(mBuffer->data() + mBuffer->size(), data, size);
+ mBuffer->setRange(0, mBuffer->size() + size);
+
+ mTimestamps.push_back(timeUs);
+
+ return OK;
+}
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() {
+ if (mMode == H264) {
+ return dequeueAccessUnitH264();
+ } else {
+ CHECK_EQ((unsigned)mMode, (unsigned)AAC);
+ return dequeueAccessUnitAAC();
+ }
+}
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() {
+ Vector<size_t> frameOffsets;
+ Vector<size_t> frameSizes;
+ size_t auSize = 0;
+
+ size_t offset = 0;
+ while (offset + 7 <= mBuffer->size()) {
+ ABitReader bits(mBuffer->data() + offset, mBuffer->size() - offset);
+
+ // adts_fixed_header
+
+ CHECK_EQ(bits.getBits(12), 0xfffu);
+ bits.skipBits(3); // ID, layer
+ bool protection_absent = bits.getBits(1) != 0;
+
+ if (mFormat == NULL) {
+ unsigned profile = bits.getBits(2);
+ CHECK_NE(profile, 3u);
+ unsigned sampling_freq_index = bits.getBits(4);
+ bits.getBits(1); // private_bit
+ unsigned channel_configuration = bits.getBits(3);
+ CHECK_NE(channel_configuration, 0u);
+ bits.skipBits(2); // original_copy, home
+
+ mFormat = MakeAACCodecSpecificData(
+ profile, sampling_freq_index, channel_configuration);
+ } else {
+ // profile_ObjectType, sampling_frequency_index, private_bits,
+ // channel_configuration, original_copy, home
+ bits.skipBits(12);
+ }
+
+ // adts_variable_header
+
+ // copyright_identification_bit, copyright_identification_start
+ bits.skipBits(2);
+
+ unsigned aac_frame_length = bits.getBits(13);
+
+ bits.skipBits(11); // adts_buffer_fullness
+
+ unsigned number_of_raw_data_blocks_in_frame = bits.getBits(2);
+
+ if (number_of_raw_data_blocks_in_frame != 0) {
+ // To be implemented.
+ TRESPASS();
+ }
+
+ if (offset + aac_frame_length > mBuffer->size()) {
+ break;
+ }
+
+ size_t headerSize = protection_absent ? 7 : 9;
+
+ frameOffsets.push(offset + headerSize);
+ frameSizes.push(aac_frame_length - headerSize);
+ auSize += aac_frame_length - headerSize;
+
+ offset += aac_frame_length;
+ }
+
+ if (offset == 0) {
+ return NULL;
+ }
+
+ sp<ABuffer> accessUnit = new ABuffer(auSize);
+ size_t dstOffset = 0;
+ for (size_t i = 0; i < frameOffsets.size(); ++i) {
+ memcpy(accessUnit->data() + dstOffset,
+ mBuffer->data() + frameOffsets.itemAt(i),
+ frameSizes.itemAt(i));
+
+ dstOffset += frameSizes.itemAt(i);
+ }
+
+ memmove(mBuffer->data(), mBuffer->data() + offset,
+ mBuffer->size() - offset);
+ mBuffer->setRange(0, mBuffer->size() - offset);
+
+ CHECK_GT(mTimestamps.size(), 0u);
+ int64_t timeUs = *mTimestamps.begin();
+ mTimestamps.erase(mTimestamps.begin());
+
+ accessUnit->meta()->setInt64("time", timeUs);
+
+ return accessUnit;
+}
+
+// static
+sp<MetaData> ElementaryStreamQueue::MakeAACCodecSpecificData(
+ unsigned profile, unsigned sampling_freq_index,
+ unsigned channel_configuration) {
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+
+ CHECK_LE(sampling_freq_index, 11u);
+ static const int32_t kSamplingFreq[] = {
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000
+ };
+ meta->setInt32(kKeySampleRate, kSamplingFreq[sampling_freq_index]);
+ meta->setInt32(kKeyChannelCount, channel_configuration);
+
+ static const uint8_t kStaticESDS[] = {
+ 0x03, 22,
+ 0x00, 0x00, // ES_ID
+ 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+ 0x04, 17,
+ 0x40, // Audio ISO/IEC 14496-3
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x05, 2,
+ // AudioSpecificInfo follows
+
+ // oooo offf fccc c000
+ // o - audioObjectType
+ // f - samplingFreqIndex
+ // c - channelConfig
+ };
+ sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2);
+ memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS));
+
+ csd->data()[sizeof(kStaticESDS)] =
+ ((profile + 1) << 3) | (sampling_freq_index >> 1);
+
+ csd->data()[sizeof(kStaticESDS) + 1] =
+ ((sampling_freq_index << 7) & 0x80) | (channel_configuration << 3);
+
+ meta->setData(kKeyESDS, 0, csd->data(), csd->size());
+
+ return meta;
+}
+
+struct NALPosition {
+ size_t nalOffset;
+ size_t nalSize;
+};
+
+sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() {
+ const uint8_t *data = mBuffer->data();
+ size_t size = mBuffer->size();
+
+ Vector<NALPosition> nals;
+
+ size_t totalSize = 0;
+
+ status_t err;
+ const uint8_t *nalStart;
+ size_t nalSize;
+ bool foundSlice = false;
+ while ((err = getNextNALUnit(&data, &size, &nalStart, &nalSize)) == OK) {
+ CHECK_GT(nalSize, 0u);
+
+ unsigned nalType = nalStart[0] & 0x1f;
+ bool flush = false;
+
+ if (nalType == 1 || nalType == 5) {
+ if (foundSlice) {
+ ABitReader br(nalStart + 1, nalSize);
+ unsigned first_mb_in_slice = parseUE(&br);
+
+ if (first_mb_in_slice == 0) {
+ // This slice starts a new frame.
+
+ flush = true;
+ }
+ }
+
+ foundSlice = true;
+ } else if ((nalType == 9 || nalType == 7) && foundSlice) {
+ // Access unit delimiter and SPS will be associated with the
+ // next frame.
+
+ flush = true;
+ }
+
+ if (flush) {
+ // The access unit will contain all nal units up to, but excluding
+ // the current one, separated by 0x00 0x00 0x00 0x01 startcodes.
+
+ size_t auSize = 4 * nals.size() + totalSize;
+ sp<ABuffer> accessUnit = new ABuffer(auSize);
+
+#if !LOG_NDEBUG
+ AString out;
+#endif
+
+ size_t dstOffset = 0;
+ for (size_t i = 0; i < nals.size(); ++i) {
+ const NALPosition &pos = nals.itemAt(i);
+
+ unsigned nalType = mBuffer->data()[pos.nalOffset] & 0x1f;
+
+#if !LOG_NDEBUG
+ char tmp[128];
+ sprintf(tmp, "0x%02x", nalType);
+ if (i > 0) {
+ out.append(", ");
+ }
+ out.append(tmp);
+#endif
+
+ memcpy(accessUnit->data() + dstOffset, "\x00\x00\x00\x01", 4);
+
+ memcpy(accessUnit->data() + dstOffset + 4,
+ mBuffer->data() + pos.nalOffset,
+ pos.nalSize);
+
+ dstOffset += pos.nalSize + 4;
+ }
+
+ LOGV("accessUnit contains nal types %s", out.c_str());
+
+ const NALPosition &pos = nals.itemAt(nals.size() - 1);
+ size_t nextScan = pos.nalOffset + pos.nalSize;
+
+ memmove(mBuffer->data(),
+ mBuffer->data() + nextScan,
+ mBuffer->size() - nextScan);
+
+ mBuffer->setRange(0, mBuffer->size() - nextScan);
+
+ CHECK_GT(mTimestamps.size(), 0u);
+ int64_t timeUs = *mTimestamps.begin();
+ mTimestamps.erase(mTimestamps.begin());
+
+ accessUnit->meta()->setInt64("time", timeUs);
+
+ if (mFormat == NULL) {
+ mFormat = MakeAVCCodecSpecificData(accessUnit);
+ }
+
+ return accessUnit;
+ }
+
+ NALPosition pos;
+ pos.nalOffset = nalStart - mBuffer->data();
+ pos.nalSize = nalSize;
+
+ nals.push(pos);
+
+ totalSize += nalSize;
+ }
+ CHECK_EQ(err, (status_t)-EAGAIN);
+
+ return NULL;
+}
+
+static sp<ABuffer> FindNAL(
+ const uint8_t *data, size_t size, unsigned nalType,
+ size_t *stopOffset) {
+ const uint8_t *nalStart;
+ size_t nalSize;
+ while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
+ if ((nalStart[0] & 0x1f) == nalType) {
+ sp<ABuffer> buffer = new ABuffer(nalSize);
+ memcpy(buffer->data(), nalStart, nalSize);
+ return buffer;
+ }
+ }
+
+ return NULL;
+}
+
+sp<MetaData> ElementaryStreamQueue::MakeAVCCodecSpecificData(
+ const sp<ABuffer> &accessUnit) {
+ const uint8_t *data = accessUnit->data();
+ size_t size = accessUnit->size();
+
+ sp<ABuffer> seqParamSet = FindNAL(data, size, 7, NULL);
+ if (seqParamSet == NULL) {
+ return NULL;
+ }
+
+ int32_t width, height;
+ FindAVCDimensions(seqParamSet, &width, &height);
+
+ size_t stopOffset;
+ sp<ABuffer> picParamSet = FindNAL(data, size, 8, &stopOffset);
+ CHECK(picParamSet != NULL);
+
+ size_t csdSize =
+ 1 + 3 + 1 + 1
+ + 2 * 1 + seqParamSet->size()
+ + 1 + 2 * 1 + picParamSet->size();
+
+ sp<ABuffer> csd = new ABuffer(csdSize);
+ uint8_t *out = csd->data();
+
+ *out++ = 0x01; // configurationVersion
+ memcpy(out, seqParamSet->data() + 1, 3); // profile/level...
+ out += 3;
+ *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes
+ *out++ = 0xe0 | 1;
+
+ *out++ = seqParamSet->size() >> 8;
+ *out++ = seqParamSet->size() & 0xff;
+ memcpy(out, seqParamSet->data(), seqParamSet->size());
+ out += seqParamSet->size();
+
+ *out++ = 1;
+
+ *out++ = picParamSet->size() >> 8;
+ *out++ = picParamSet->size() & 0xff;
+ memcpy(out, picParamSet->data(), picParamSet->size());
+
+#if 0
+ LOGI("AVC seq param set");
+ hexdump(seqParamSet->data(), seqParamSet->size());
+#endif
+
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+
+ meta->setData(kKeyAVCC, 0, csd->data(), csd->size());
+ meta->setInt32(kKeyWidth, width);
+ meta->setInt32(kKeyHeight, height);
+
+ return meta;
+}
+
+} // namespace android
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
new file mode 100644
index 0000000..d2e87f2
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ES_QUEUE_H_
+
+#define ES_QUEUE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/List.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+struct MetaData;
+
+struct ElementaryStreamQueue {
+ enum Mode {
+ H264,
+ AAC
+ };
+ ElementaryStreamQueue(Mode mode);
+
+ status_t appendData(const void *data, size_t size, int64_t timeUs);
+
+ sp<ABuffer> dequeueAccessUnit();
+
+ sp<MetaData> getFormat();
+
+private:
+ Mode mMode;
+
+ sp<ABuffer> mBuffer;
+ List<int64_t> mTimestamps;
+
+ sp<MetaData> mFormat;
+
+ sp<ABuffer> dequeueAccessUnitH264();
+ sp<ABuffer> dequeueAccessUnitAAC();
+
+ static sp<MetaData> MakeAACCodecSpecificData(
+ unsigned profile, unsigned sampling_freq_index,
+ unsigned channel_configuration);
+
+ static sp<MetaData> MakeAVCCodecSpecificData(
+ const sp<ABuffer> &accessUnit);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue);
+};
+
+} // namespace android
+
+#endif // ES_QUEUE_H_
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index 2417305..c5257bb 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -157,7 +157,7 @@
}
}
- if (++numPacketsParsed > 1500) {
+ if (++numPacketsParsed > 2500) {
break;
}
}
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 79b9b1e..99e8f97 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -79,8 +79,8 @@
ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
- ACONFIGURATION_UI_MODE_NIGHT_NO = 0x10,
- ACONFIGURATION_UI_MODE_NIGHT_YES = 0x20,
+ ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
+ ACONFIGURATION_UI_MODE_NIGHT_YES = 0x2,
ACONFIGURATION_MCC = 0x0001,
ACONFIGURATION_MNC = 0x0002,
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index eb86277..ce10f5b 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -159,7 +159,7 @@
try {
return ObbScanner.getObbInfo(filename);
} catch (IOException e) {
- Log.d(TAG, "Couldn't get OBB info", e);
+ Log.d(TAG, "Couldn't get OBB info for " + filename);
return null;
}
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 3980189..8de507e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -80,7 +80,7 @@
<bool name="def_accessibility_script_injection">false</bool>
<!-- Default for Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS -->
- <string name="def_accessibility_web_content_key_bindings">
+ <string name="def_accessibility_web_content_key_bindings" translatable="false">
<!-- DPAD/Trackball UP maps to traverse previous on current axis and send an event. -->
0x13=0x01000100;
<!-- DPAD/Trackball DOWN maps to traverse next on current axis and send an event. -->
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index cfad939..2c13069 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -13,6 +13,6 @@
LOCAL_PACKAGE_NAME := SystemUI
LOCAL_CERTIFICATE := platform
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png b/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
index 744178f..d6c8a21 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
index c299e12..ae90cc8 100755
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
index 157491e..a0e59cf 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 3e317dd..2f66b1d 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
index aea18ed..1626895 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
old mode 100755
new mode 100644
index 1a25a2c..3c2e2b9
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
index 2134d49..bb41db0 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png
Binary files differ
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
new file mode 100644
index 0000000..a0def6b
--- /dev/null
+++ b/packages/SystemUI/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?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>
+ <drawable name="notification_number_text_color">#ffffffff</drawable>
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
index bf24a1f..16a3c17 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java
@@ -366,6 +366,7 @@
mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
mCarouselView.setDefaultBitmap(mLoadingBitmap);
mCarouselView.setLoadingBitmap(mLoadingBitmap);
+ mCarouselView.setRezInCardCount(3.0f);
mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
mNoRecentsView = (View) findViewById(R.id.no_applications_message);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 70d4d6a..d4491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -20,6 +20,8 @@
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews.RemoteView;
@@ -43,7 +45,7 @@
}
if (drawable instanceof AnimationDrawable) {
mAnim = (AnimationDrawable)drawable;
- if (mAttached) {
+ if (isShown()) {
mAnim.start();
}
} else {
@@ -67,9 +69,6 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mAnim != null) {
- mAnim.start();
- }
mAttached = true;
}
@@ -81,5 +80,17 @@
}
mAttached = false;
}
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int vis) {
+ super.onVisibilityChanged(changedView, vis);
+ if (mAnim != null) {
+ if (isShown()) {
+ mAnim.start();
+ } else {
+ mAnim.stop();
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
index 027bed4a..e87d003c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
@@ -23,28 +23,34 @@
import android.view.View;
import android.widget.LinearLayout;
+import com.android.internal.statusbar.StatusBarIcon;
+
import com.android.systemui.R;
public class IconMerger extends LinearLayout {
private static final String TAG = "IconMerger";
+ private int mIconSize;
private StatusBarIconView mMoreView;
+ private StatusBarIcon mMoreIcon = new StatusBarIcon(null, R.drawable.stat_notify_more, 0);
public IconMerger(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ mIconSize = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size);
+
+ mMoreView = new StatusBarIconView(context, "more");
+ mMoreView.set(mMoreIcon);
+ addView(mMoreView, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
- public void addMoreView(StatusBarIconView v, LinearLayout.LayoutParams lp) {
- super.addView(v, lp);
- mMoreView = v;
- }
-
- public void addView(StatusBarIconView v, int index, LinearLayout.LayoutParams lp) {
+ public void addView(StatusBarIconView v, int index) {
if (index == 0) {
throw new RuntimeException("Attempt to put view before the more view: " + v);
}
- super.addView(v, index, lp);
+ addView(v, index, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
@Override
@@ -127,28 +133,8 @@
}
}
}
-
- // BUG: Updating the text during the layout here doesn't seem to cause
- // the view to be redrawn fully. The text view gets resized correctly, but the
- // text contents aren't drawn properly. To work around this, we post a message
- // and provide the value later. We're the only one changing this value show it
- // should be ordered correctly.
- if (false) {
- // TODO this.moreIcon.update(number);
- } else {
- mBugWorkaroundNumber = number;
- mBugWorkaroundHandler.post(mBugWorkaroundRunnable);
- }
- }
- private int mBugWorkaroundNumber;
- private Handler mBugWorkaroundHandler = new Handler();
- private Runnable mBugWorkaroundRunnable = new Runnable() {
- public void run() {
- /* TODO
- IconMerger.this.moreIcon.update(mBugWorkaroundNumber);
- IconMerger.this.moreIcon.view.invalidate();
- */
- }
- };
+ mMoreIcon.number = number;
+ mMoreView.set(mMoreIcon);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index e945981..57ebd27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -274,12 +274,6 @@
mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
- // the more notifications icon
- StatusBarIconView moreView = new StatusBarIconView(this, "more");
- moreView.set(new StatusBarIcon(null, R.drawable.stat_notify_more, 0));
- mNotificationIcons.addMoreView(moreView,
- new LinearLayout.LayoutParams(mIconSize, mIconSize));
-
// set the inital view visibility
setAreThereNotifications();
mDateView.setVisibility(View.INVISIBLE);
@@ -579,8 +573,7 @@
parent.addView(row, viewIndex);
// Add the icon.
final int iconIndex = chooseIconIndex(isOngoing, viewIndex);
- mNotificationIcons.addView(iconView, iconIndex,
- new LinearLayout.LayoutParams(mIconSize, mIconSize));
+ mNotificationIcons.addView(iconView, iconIndex);
return iconView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 0ca0572..8419e56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -21,22 +21,37 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
import android.util.Slog;
import android.util.Log;
+import android.view.View;
import android.view.ViewDebug;
import android.widget.FrameLayout;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.R;
+
public class StatusBarIconView extends AnimatedImageView {
private static final String TAG = "StatusBarIconView";
private StatusBarIcon mIcon;
@ViewDebug.ExportedProperty private String mSlot;
+ private Drawable mNumberBackground;
+ private Paint mNumberPain;
+ private int mNumberX;
+ private int mNumberY;
+ private String mNumberText;
public StatusBarIconView(Context context, String slot) {
super(context);
+ final Resources res = context.getResources();
mSlot = slot;
+ mNumberPain = new Paint();
+ mNumberPain.setTextAlign(Paint.Align.CENTER);
+ mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color));
+ mNumberPain.setAntiAlias(true);
}
private static boolean streq(String a, String b) {
@@ -63,6 +78,9 @@
&& mIcon.iconLevel == icon.iconLevel;
final boolean visibilityEquals = mIcon != null
&& mIcon.visible == icon.visible;
+ final boolean numberEquals = mIcon != null
+ && mIcon.number == icon.number;
+ mIcon = icon.clone();
if (!iconEquals) {
Drawable drawable = getIcon(icon);
if (drawable == null) {
@@ -74,10 +92,22 @@
if (!levelEquals) {
setImageLevel(icon.iconLevel);
}
+ if (!numberEquals) {
+ if (icon.number > 0) {
+ if (mNumberBackground == null) {
+ mNumberBackground = getContext().getResources().getDrawable(
+ R.drawable.ic_notification_overlay);
+ }
+ placeNumber();
+ } else {
+ mNumberBackground = null;
+ mNumberText = null;
+ }
+ invalidate();
+ }
if (!visibilityEquals) {
setVisibility(icon.visible ? VISIBLE : GONE);
}
- mIcon = icon.clone();
return true;
}
@@ -126,9 +156,47 @@
return mIcon;
}
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mNumberBackground != null) {
+ placeNumber();
+ }
+ }
+
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mNumberBackground != null) {
+ mNumberBackground.draw(canvas);
+ canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
+ }
+ }
+
protected void debug(int depth) {
super.debug(depth);
Log.d("View", debugIndent(depth) + "slot=" + mSlot);
Log.d("View", debugIndent(depth) + "icon=" + mIcon);
}
+
+ void placeNumber() {
+ final String str = mNumberText = Integer.toString(mIcon.number);
+ final int w = getWidth();
+ final int h = getHeight();
+ final Rect r = new Rect();
+ mNumberPain.getTextBounds(str, 0, str.length(), r);
+ final int tw = r.right - r.left;
+ final int th = r.bottom - r.top;
+ mNumberBackground.getPadding(r);
+ int dw = r.left + tw + r.right;
+ if (dw < mNumberBackground.getMinimumWidth()) {
+ dw = mNumberBackground.getMinimumWidth();
+ }
+ mNumberX = w-r.right-((dw-r.right-r.left)/2);
+ int dh = r.top + th + r.bottom;
+ if (dh < mNumberBackground.getMinimumWidth()) {
+ dh = mNumberBackground.getMinimumWidth();
+ }
+ mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
+ mNumberBackground.setBounds(w-dw, h-dh, w, h);
+ }
}
diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk
index 75b26a2..a1a3b9f 100644
--- a/packages/TtsService/Android.mk
+++ b/packages/TtsService/Android.mk
@@ -8,7 +8,7 @@
LOCAL_PACKAGE_NAME := TtsService
LOCAL_CERTIFICATE := platform
-LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c047522..c70f5d4 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -89,6 +89,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
@@ -152,8 +153,11 @@
// responsible for power management when displayed.
static final int KEYGUARD_LAYER = 14;
static final int KEYGUARD_DIALOG_LAYER = 15;
+ // the drag layer: input for drag-and-drop is associated with this window,
+ // which sits above all other focusable windows
+ static final int DRAG_LAYER = 16;
// things in here CAN NOT take focus, but are shown on top of everything else.
- static final int SYSTEM_OVERLAY_LAYER = 16;
+ static final int SYSTEM_OVERLAY_LAYER = 17;
static final int APPLICATION_MEDIA_SUBLAYER = -2;
static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -839,6 +843,8 @@
return TOAST_LAYER;
case TYPE_WALLPAPER:
return WALLPAPER_LAYER;
+ case TYPE_DRAG:
+ return DRAG_LAYER;
}
Log.e(TAG, "Unknown window type: " + type);
return APPLICATION_LAYER;
@@ -1544,7 +1550,9 @@
}
if (attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type <= LAST_APPLICATION_WINDOW
- && win.fillsScreenLw(mW, mH, false, false)) {
+ && attrs.x == 0 && attrs.y == 0
+ && attrs.width == WindowManager.LayoutParams.MATCH_PARENT
+ && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
if (DEBUG_LAYOUT) Log.v(TAG, "Fullscreen window: " + win);
mTopFullscreenOpaqueWindowState = win;
if ((attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 97b8086..8527059 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1856,6 +1856,8 @@
if (--(track->mRetryCount) <= 0) {
LOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this);
tracksToRemove->add(track);
+ // indicate to client process that the track was disabled because of underrun
+ cblk->flags |= CBLK_DISABLED_ON;
} else if (mixerStatus != MIXER_TRACKS_READY) {
mixerStatus = MIXER_TRACKS_ENABLED;
}
@@ -2790,7 +2792,7 @@
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
// Force underrun condition to avoid false underrun callback until first data is
- // written to buffer
+ // written to buffer (other flags are cleared)
mCblk->flags = CBLK_UNDERRUN_ON;
} else {
mBuffer = sharedBuffer->pointer();
@@ -2813,7 +2815,7 @@
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
// Force underrun condition to avoid false underrun callback until first data is
- // written to buffer
+ // written to buffer (other flags are cleared)
mCblk->flags = CBLK_UNDERRUN_ON;
mBufferEnd = (uint8_t *)mBuffer + bufferSize;
}
@@ -3794,6 +3796,8 @@
AudioBufferProvider::Buffer buffer;
sp<RecordTrack> activeTrack;
+ nsecs_t lastWarning = 0;
+
// start recording
while (!exitPending()) {
@@ -3935,8 +3939,13 @@
}
// client isn't retrieving buffers fast enough
else {
- if (!mActiveTrack->setOverflow())
- LOGW("RecordThread: buffer overflow");
+ if (!mActiveTrack->setOverflow()) {
+ nsecs_t now = systemTime();
+ if ((now - lastWarning) > kWarningThrottle) {
+ LOGW("RecordThread: buffer overflow");
+ lastWarning = now;
+ }
+ }
// Release the processor for a while before asking for a new buffer.
// This will give the application more chance to read from the buffer and
// clear the overflow.
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 6095117..880befd 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -68,7 +68,7 @@
*/
public class ConnectivityService extends IConnectivityManager.Stub {
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final String TAG = "ConnectivityService";
// how long to wait before switching back to a radio's default network
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 0c3a0e6..28126b9 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -58,6 +58,7 @@
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;
@@ -605,6 +606,8 @@
Slog.w(TAG, "failed parsing " + file + " " + e);
} catch (XmlPullParserException e) {
Slog.w(TAG, "failed parsing " + file + " " + e);
+ } catch (FileNotFoundException e) {
+ // Don't be noisy, this is normal if we haven't defined any policies.
} catch (IOException e) {
Slog.w(TAG, "failed parsing " + file + " " + e);
} catch (IndexOutOfBoundsException e) {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 675760f..9a5423c 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -117,6 +117,7 @@
final Context mContext;
final Handler mHandler;
+ final InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
final StatusBarManagerService mStatusBar;
final IWindowManager mIWindowManager;
@@ -126,13 +127,8 @@
// All known input methods. mMethodMap also serves as the global
// lock for this class.
- final ArrayList<InputMethodInfo> mMethodList
- = new ArrayList<InputMethodInfo>();
- final HashMap<String, InputMethodInfo> mMethodMap
- = new HashMap<String, InputMethodInfo>();
-
- final TextUtils.SimpleStringSplitter mStringColonSplitter
- = new TextUtils.SimpleStringSplitter(':');
+ final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
+ final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
class SessionState {
final ClientState client;
@@ -483,27 +479,18 @@
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
+ // mSettings should be created before buildInputMethodListLocked
+ mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList);
buildInputMethodListLocked(mMethodList, mMethodMap);
+ mSettings.enableAllIMEsIfThereIsNoEnabledIME();
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- Slog.i(TAG, "Enabled input methods: " + enabledStr);
- final String defaultIme = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
- Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
+ if (TextUtils.isEmpty(Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
InputMethodInfo defIm = null;
- StringBuilder sb = new StringBuilder(256);
- final int N = mMethodList.size();
- for (int i=0; i<N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
+ for (InputMethodInfo imi: mMethodList) {
if (defIm == null && imi.getIsDefaultResourceId() != 0) {
try {
- Resources res = mContext.createPackageContext(
+ Resources res = context.createPackageContext(
imi.getPackageName(), 0).getResources();
if (res.getBoolean(imi.getIsDefaultResourceId())) {
defIm = imi;
@@ -514,12 +501,10 @@
}
}
}
- if (defIm == null && N > 0) {
+ if (defIm == null && mMethodList.size() > 0) {
defIm = mMethodList.get(0);
Slog.i(TAG, "No default found, using " + defIm.getId());
}
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
if (defIm != null) {
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
@@ -567,31 +552,10 @@
public List<InputMethodInfo> getEnabledInputMethodList() {
synchronized (mMethodMap) {
- return getEnabledInputMethodListLocked();
+ return mSettings.getEnabledInputMethodListLocked();
}
}
- List<InputMethodInfo> getEnabledInputMethodListLocked() {
- final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
-
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
-
- while (splitter.hasNext()) {
- InputMethodInfo info = mMethodMap.get(splitter.next());
- if (info != null) {
- res.add(info);
- }
- }
- }
-
- return res;
- }
-
public void addClient(IInputMethodClient client,
IInputContext inputContext, int uid, int pid) {
synchronized (mMethodMap) {
@@ -951,10 +915,11 @@
}
public void updateStatusIcon(IBinder token, String packageName, int iconId) {
+ int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
if (token == null || mCurToken != token) {
- Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
+ Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
return;
}
@@ -1048,6 +1013,7 @@
public boolean showSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
+ int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
@@ -1058,7 +1024,7 @@
// focus in the window manager, to allow this call to
// be made before input is started in it.
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
- Slog.w(TAG, "Ignoring showSoftInput of: " + client);
+ Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
return false;
}
} catch (RemoteException e) {
@@ -1112,6 +1078,7 @@
public boolean hideSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
+ int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
@@ -1122,7 +1089,8 @@
// focus in the window manager, to allow this call to
// be made before input is started in it.
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
- Slog.w(TAG, "Ignoring hideSoftInput of: " + client);
+ if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+ + uid + ": " + client);
return false;
}
} catch (RemoteException e) {
@@ -1257,7 +1225,8 @@
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
- Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
+ Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid "
+ + Binder.getCallingUid() + ": " + client);
}
mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
@@ -1290,7 +1259,8 @@
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
} else if (mCurToken != token) {
- Slog.w(TAG, "Ignoring setInputMethod of token: " + token);
+ Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+ + " token: " + token);
return;
}
@@ -1306,7 +1276,8 @@
public void hideMySoftInput(IBinder token, int flags) {
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
- Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
+ if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+ + Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
@@ -1321,7 +1292,8 @@
public void showMySoftInput(IBinder token, int flags) {
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
- Slog.w(TAG, "Ignoring hideInputMethod of token: " + token);
+ Slog.w(TAG, "Ignoring showMySoftInput of uid "
+ + Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
@@ -1463,7 +1435,7 @@
}
private boolean chooseNewDefaultIMELocked() {
- List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
+ List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
// We'd prefer to fall back on a system IME, since that is safer.
int i=enabled.size();
@@ -1642,7 +1614,7 @@
hideInputMethodMenu();
}
};
-
+
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
com.android.internal.R.attr.alertDialogStyle, 0);
@@ -1656,7 +1628,7 @@
.setIcon(a.getDrawable(
com.android.internal.R.styleable.DialogPreference_dialogTitle));
a.recycle();
-
+
mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -1730,81 +1702,45 @@
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
if (imm == null) {
- if (imm == null) {
- throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
- }
+ throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
- StringBuilder builder = new StringBuilder(256);
+ List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ .getEnabledInputMethodsAndSubtypeListLocked();
- boolean removed = false;
- String firstId = null;
-
- // Look through the currently enabled input methods.
- String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
- while (splitter.hasNext()) {
- String curId = splitter.next();
- if (curId.equals(id)) {
- if (enabled) {
- // We are enabling this input method, but it is
- // already enabled. Nothing to do. The previous
- // state was enabled.
- return true;
- }
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- removed = true;
- } else if (!enabled) {
- // We are building a new list of input methods that
- // doesn't contain the given one.
- if (firstId == null) firstId = curId;
- if (builder.length() > 0) builder.append(':');
- builder.append(curId);
+ if (enabled) {
+ for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
+ if (pair.first.equals(id)) {
+ // We are enabling this input method, but it is already enabled.
+ // Nothing to do. The previous state was enabled.
+ return true;
}
}
- }
-
- if (!enabled) {
- if (!removed) {
- // We are disabling the input method but it is already
- // disabled. Nothing to do. The previous state was
- // disabled.
+ mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+ // Previous state was disabled.
+ return false;
+ } else {
+ StringBuilder builder = new StringBuilder();
+ if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ builder, enabledInputMethodsList, id)) {
+ // Disabled input method is currently selected, switch to another one.
+ String selId = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (id.equals(selId)) {
+ Settings.Secure.putString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
+ enabledInputMethodsList.size() > 0
+ ? enabledInputMethodsList.get(0).first : "");
+ resetSelectedInputMethodSubtype();
+ }
+ // Previous state was enabled.
+ return true;
+ } else {
+ // We are disabling the input method but it is already disabled.
+ // Nothing to do. The previous state was disabled.
return false;
}
- // Update the setting with the new list of input methods.
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
- // We the disabled input method is currently selected, switch
- // to another one.
- String selId = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
- if (id.equals(selId)) {
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- firstId != null ? firstId : "");
- resetSelectedInputMethodSubtype();
- }
- // Previous state was enabled.
- return true;
}
-
- // Add in the newly enabled input method.
- if (enabledStr == null || enabledStr.length() == 0) {
- enabledStr = id;
- } else {
- enabledStr = enabledStr + ':' + id;
- }
-
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
-
- // Previous state was disabled.
- return false;
}
private void resetSelectedInputMethodSubtype() {
@@ -1854,6 +1790,161 @@
return mCurrentSubtype;
}
+ /**
+ * Utility class for putting and getting settings for InputMethod
+ * TODO: Move all putters and getters of settings to this class.
+ */
+ private static class InputMethodSettings {
+ // The string for enabled input method is saved as follows:
+ // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private final TextUtils.SimpleStringSplitter mStringSemiColonSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ private final ContentResolver mResolver;
+ private final HashMap<String, InputMethodInfo> mMethodMap;
+ private final ArrayList<InputMethodInfo> mMethodList;
+
+ private String mEnabledInputMethodsStrCache;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+ String id = pair.first;
+ ArrayList<String> subtypes = pair.second;
+ builder.append(id);
+ if (subtypes.size() > 0) {
+ builder.append(subtypes.get(0));
+ for (int i = 1; i < subtypes.size(); ++i) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypes.get(i));
+ }
+ }
+ }
+
+ public InputMethodSettings(
+ ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap,
+ ArrayList<InputMethodInfo> methodList) {
+ mResolver = resolver;
+ mMethodMap = methodMap;
+ mMethodList = methodList;
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ // At the initial boot, the settings for input methods are not set,
+ // so we need to enable IME in that case.
+ public void enableAllIMEsIfThereIsNoEnabledIME() {
+ if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+ StringBuilder sb = new StringBuilder();
+ final int N = mMethodList.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ Slog.i(TAG, "Adding: " + imi.getId());
+ if (i > 0) sb.append(':');
+ sb.append(imi.getId());
+ }
+ putEnabledInputMethodsStr(sb.toString());
+ }
+ }
+
+ public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ ArrayList<Pair<String, ArrayList<String>>> imsList
+ = new ArrayList<Pair<String, ArrayList<String>>>();
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ mStringColonSplitter.setString(enabledInputMethodsStr);
+ while (mStringColonSplitter.hasNext()) {
+ String nextImsStr = mStringColonSplitter.next();
+ mStringSemiColonSplitter.setString(nextImsStr);
+ if (mStringSemiColonSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<String>();
+ // The first element is ime id.
+ String imeId = mStringSemiColonSplitter.next();
+ while (mStringSemiColonSplitter.hasNext()) {
+ subtypeHashes.add(mStringSemiColonSplitter.next());
+ }
+ imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ if (reloadInputMethodStr) {
+ getEnabledInputMethodsStr();
+ }
+ if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+ // Add in the newly enabled input method.
+ putEnabledInputMethodsStr(id);
+ } else {
+ putEnabledInputMethodsStr(
+ mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+ }
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ * @return the specified id was removed or not.
+ */
+ public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ private void putEnabledInputMethodsStr(String str) {
+ Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+ mEnabledInputMethodsStrCache = str;
+ }
+
+ private String getEnabledInputMethodsStr() {
+ mEnabledInputMethodsStrCache = Settings.Secure.getString(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ return mEnabledInputMethodsStrCache;
+ }
+ }
+
// ----------------------------------------------------------------------
@Override
diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java
index 8ab65e9..e47de13 100644
--- a/services/java/com/android/server/IntentResolver.java
+++ b/services/java/com/android/server/IntentResolver.java
@@ -28,6 +28,7 @@
import java.util.Set;
import android.util.Log;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.LogPrinter;
import android.util.Printer;
@@ -92,10 +93,12 @@
}
boolean dumpMap(PrintWriter out, String titlePrefix, String title,
- String prefix, Map<String, ArrayList<F>> map, String packageName) {
+ String prefix, Map<String, ArrayList<F>> map, String packageName,
+ boolean printFilter) {
String eprefix = prefix + " ";
String fprefix = prefix + " ";
boolean printedSomething = false;
+ Printer printer = null;
for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) {
ArrayList<F> a = e.getValue();
final int N = a.size();
@@ -115,37 +118,44 @@
}
printedSomething = true;
dumpFilter(out, fprefix, filter);
+ if (printFilter) {
+ if (printer == null) {
+ printer = new PrintWriterPrinter(out);
+ }
+ filter.dump(printer, fprefix + " ");
+ }
}
}
return printedSomething;
}
- public boolean dump(PrintWriter out, String title, String prefix, String packageName) {
+ public boolean dump(PrintWriter out, String title, String prefix, String packageName,
+ boolean printFilter) {
String innerPrefix = prefix + " ";
String sepPrefix = "\n" + prefix;
String curPrefix = title + "\n" + prefix;
if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix,
- mTypeToFilter, packageName)) {
+ mTypeToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix,
- mBaseTypeToFilter, packageName)) {
+ mBaseTypeToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix,
- mWildTypeToFilter, packageName)) {
+ mWildTypeToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
if (dumpMap(out, curPrefix, "Schemes:", innerPrefix,
- mSchemeToFilter, packageName)) {
+ mSchemeToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix,
- mActionToFilter, packageName)) {
+ mActionToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix,
- mTypedActionToFilter, packageName)) {
+ mTypedActionToFilter, packageName, printFilter)) {
curPrefix = sepPrefix;
}
return curPrefix == sepPrefix;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index aa1bcf7..19ea4e1 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -58,6 +58,7 @@
import android.util.Slog;
import android.util.PrintWriterPrinter;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.server.location.GeocoderProxy;
@@ -116,13 +117,15 @@
private static boolean sProvidersLoaded = false;
private final Context mContext;
+ private final String mNetworkLocationProviderPackageName;
+ private final String mGeocodeProviderPackageName;
private GeocoderProxy mGeocodeProvider;
private IGpsStatusProvider mGpsStatusProvider;
private INetInitiatedListener mNetInitiatedListener;
private LocationWorkerHandler mLocationHandler;
// Cache the real providers for use in addTestProvider() and removeTestProvider()
- LocationProviderInterface mNetworkLocationProvider;
+ LocationProviderProxy mNetworkLocationProvider;
LocationProviderInterface mGpsLocationProvider;
// Handler messages
@@ -472,20 +475,18 @@
mEnabledProviders.add(passiveProvider.getName());
// initialize external network location and geocoder services
- PackageManager pm = mContext. getPackageManager();
- Resources resources = mContext.getResources();
- String serviceName = resources.getString(
- com.android.internal.R.string.config_networkLocationProvider);
- if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
+ PackageManager pm = mContext.getPackageManager();
+ if (mNetworkLocationProviderPackageName != null &&
+ pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
mNetworkLocationProvider =
new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
- serviceName, mLocationHandler);
+ mNetworkLocationProviderPackageName, mLocationHandler);
addProvider(mNetworkLocationProvider);
}
- serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
- if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
- mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
+ if (mGeocodeProviderPackageName != null &&
+ pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
+ mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
}
updateProvidersLocked();
@@ -497,6 +498,12 @@
public LocationManagerService(Context context) {
super();
mContext = context;
+ Resources resources = context.getResources();
+ mNetworkLocationProviderPackageName = resources.getString(
+ com.android.internal.R.string.config_networkLocationProvider);
+ mGeocodeProviderPackageName = resources.getString(
+ com.android.internal.R.string.config_geocodeProvider);
+ mPackageMonitor.register(context, true);
if (LOCAL_LOGV) {
Slog.v(TAG, "Constructed LocationManager Service");
@@ -1921,6 +1928,23 @@
}
};
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ String packageDot = packageName + ".";
+
+ // reconnect to external providers after their packages have been updated
+ if (mNetworkLocationProvider != null &&
+ mNetworkLocationProviderPackageName.startsWith(packageDot)) {
+ mNetworkLocationProvider.reconnect();
+ }
+ if (mGeocodeProvider != null &&
+ mGeocodeProviderPackageName.startsWith(packageDot)) {
+ mGeocodeProvider.reconnect();
+ }
+ }
+ };
+
// Wake locks
private void incrementPendingBroadcasts() {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index e6c6953..7870b06 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -17,6 +17,7 @@
package com.android.server;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.HexDump;
import com.android.server.am.ActivityManagerService;
import android.content.BroadcastReceiver;
@@ -44,15 +45,22 @@
import android.os.storage.IMountShutdownObserver;
import android.os.storage.IObbActionListener;
import android.os.storage.StorageResultCode;
+import android.security.MessageDigest;
import android.util.Slog;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/**
* MountService implements back-end services for platform storage
@@ -71,6 +79,8 @@
private static final String VOLD_TAG = "VoldConnector";
+ protected static final int MAX_OBBS = 8;
+
/*
* Internal vold volume state constants
*/
@@ -151,15 +161,19 @@
* Mounted OBB tracking information. Used to track the current state of all
* OBBs.
*/
- final private Map<IObbActionListener, List<ObbState>> mObbMounts = new HashMap<IObbActionListener, List<ObbState>>();
+ final private Map<Integer, Integer> mObbUidUsage = new HashMap<Integer, Integer>();
+ final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
class ObbState implements IBinder.DeathRecipient {
- public ObbState(String filename, IObbActionListener token, int callerUid) {
+ public ObbState(String filename, IObbActionListener token, int callerUid)
+ throws RemoteException {
this.filename = filename;
this.token = token;
this.callerUid = callerUid;
mounted = false;
+
+ getBinder().linkToDeath(this, 0);
}
// OBB source filename
@@ -174,14 +188,33 @@
// Whether this is mounted currently.
boolean mounted;
+ public IBinder getBinder() {
+ return token.asBinder();
+ }
+
@Override
public void binderDied() {
ObbAction action = new UnmountObbAction(this, true);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+ }
- removeObbState(this);
+ public void cleanUp() {
+ getBinder().unlinkToDeath(this, 0);
+ }
- token.asBinder().unlinkToDeath(this, 0);
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("ObbState{");
+ sb.append("filename=");
+ sb.append(filename);
+ sb.append(",token=");
+ sb.append(token.toString());
+ sb.append(",callerUid=");
+ sb.append(callerUid);
+ sb.append(",mounted=");
+ sb.append(mounted);
+ sb.append('}');
+ return sb.toString();
}
}
@@ -481,6 +514,34 @@
mPms.updateExternalMediaStatus(true, false);
}
}
+
+ // Remove all OBB mappings and listeners from this path
+ synchronized (mObbMounts) {
+ final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
+
+ final Iterator<Entry<String, ObbState>> i = mObbPathToStateMap.entrySet().iterator();
+ while (i.hasNext()) {
+ final Entry<String, ObbState> obbEntry = i.next();
+
+ // If this entry's source file is in the volume path that got
+ // unmounted, remove it because it's no longer valid.
+ if (obbEntry.getKey().startsWith(path)) {
+ obbStatesToRemove.add(obbEntry.getValue());
+ }
+ }
+
+ for (final ObbState obbState : obbStatesToRemove) {
+ removeObbState(obbState);
+
+ try {
+ obbState.token.onObbResult(obbState.filename, Environment.MEDIA_UNMOUNTED);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Couldn't send unmount notification for OBB: "
+ + obbState.filename);
+ }
+ }
+ }
+
String oldState = mLegacyState;
mLegacyState = state;
@@ -1464,11 +1525,6 @@
mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
}
- private boolean isCallerOwnerOfPackageOrSystem(String packageName) {
- final int callerUid = Binder.getCallingUid();
- return isUidOwnerOfPackageOrSystem(packageName, callerUid);
- }
-
private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
if (callerUid == android.os.Process.SYSTEM_UID) {
return true;
@@ -1512,33 +1568,69 @@
public boolean isObbMounted(String filename) {
synchronized (mObbMounts) {
- return mObbPathToStateMap.containsKey(filename);
+ final ObbState obbState = mObbPathToStateMap.get(filename);
+ if (obbState != null) {
+ synchronized (obbState) {
+ return obbState.mounted;
+ }
+ }
}
+ return false;
}
- public void mountObb(String filename, String key, IObbActionListener token) {
+ public void mountObb(String filename, String key, IObbActionListener token)
+ throws RemoteException {
waitForReady();
warnOnNotMounted();
+ if (filename == null) {
+ throw new IllegalArgumentException("filename cannot be null");
+ } else if (token == null) {
+ throw new IllegalArgumentException("token cannot be null");
+ }
+
final ObbState obbState;
synchronized (mObbMounts) {
if (isObbMounted(filename)) {
- throw new IllegalArgumentException("OBB file is already mounted");
+ try {
+ token.onObbResult(filename, Environment.MEDIA_MOUNTED);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Could not send unmount notification for: " + filename);
+ }
+ return;
}
final int callerUid = Binder.getCallingUid();
+
+ final Integer uidUsage = mObbUidUsage.get(callerUid);
+ if (uidUsage != null && uidUsage > MAX_OBBS) {
+ throw new IllegalStateException("Maximum number of OBBs mounted!");
+ }
+
obbState = new ObbState(filename, token, callerUid);
addObbState(obbState);
}
- try {
- token.asBinder().linkToDeath(obbState, 0);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Failed to link to listener death");
+ String hashedKey = null;
+ if (key != null) {
+ final MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ Slog.e(TAG, "Could not load MD5 algorithm", e);
+ try {
+ token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
+ } catch (RemoteException e1) {
+ Slog.d(TAG, "Could not send unmount notification for: " + filename);
+ }
+ return;
+ }
+
+ hashedKey = HexDump.toHexString(md.digest(key.getBytes()));
}
- MountObbAction action = new MountObbAction(obbState, key);
+ ObbAction action = new MountObbAction(obbState, hashedKey);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
if (DEBUG_OBB)
@@ -1546,16 +1638,34 @@
}
public void unmountObb(String filename, boolean force, IObbActionListener token) {
+ if (filename == null) {
+ throw new IllegalArgumentException("filename cannot be null");
+ } else if (token == null) {
+ throw new IllegalArgumentException("token cannot be null");
+ }
+
final ObbState obbState;
synchronized (mObbMounts) {
if (!isObbMounted(filename)) {
- throw new IllegalArgumentException("OBB is not mounted");
+ try {
+ token.onObbResult(filename, Environment.MEDIA_UNMOUNTED);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Could not send unmount notification for: " + filename);
+ }
+ return;
}
+
obbState = mObbPathToStateMap.get(filename);
+
+ if (Binder.getCallingUid() != obbState.callerUid) {
+ throw new SecurityException("caller UID does not match original mount caller UID");
+ } else if (!token.asBinder().equals(obbState.getBinder())) {
+ throw new SecurityException("caller does not match original mount caller");
+ }
}
- UnmountObbAction action = new UnmountObbAction(obbState, force);
+ ObbAction action = new UnmountObbAction(obbState, force);
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
if (DEBUG_OBB)
@@ -1564,26 +1674,57 @@
private void addObbState(ObbState obbState) {
synchronized (mObbMounts) {
- List<ObbState> obbStates = mObbMounts.get(obbState.token);
+ List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
if (obbStates == null) {
obbStates = new ArrayList<ObbState>();
- mObbMounts.put(obbState.token, obbStates);
+ mObbMounts.put(obbState.getBinder(), obbStates);
}
obbStates.add(obbState);
mObbPathToStateMap.put(obbState.filename, obbState);
+
+ // Track the number of OBBs used by this UID.
+ final int uid = obbState.callerUid;
+ final Integer uidUsage = mObbUidUsage.get(uid);
+ if (uidUsage == null) {
+ mObbUidUsage.put(uid, 1);
+ } else {
+ mObbUidUsage.put(uid, uidUsage + 1);
+ }
}
}
private void removeObbState(ObbState obbState) {
synchronized (mObbMounts) {
- final List<ObbState> obbStates = mObbMounts.get(obbState.token);
+ final List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
if (obbStates != null) {
obbStates.remove(obbState);
}
if (obbStates == null || obbStates.isEmpty()) {
- mObbMounts.remove(obbState.token);
+ mObbMounts.remove(obbState.getBinder());
+ obbState.cleanUp();
}
mObbPathToStateMap.remove(obbState.filename);
+
+ // Track the number of OBBs used by this UID.
+ final int uid = obbState.callerUid;
+ final Integer uidUsage = mObbUidUsage.get(uid);
+ if (uidUsage == null) {
+ Slog.e(TAG, "Called removeObbState for UID that isn't in map: " + uid);
+ } else {
+ final int newUsage = uidUsage - 1;
+ if (newUsage == 0) {
+ mObbUidUsage.remove(uid);
+ } else {
+ mObbUidUsage.put(uid, newUsage);
+ }
+ }
+ }
+ }
+
+ private void replaceObbState(ObbState oldObbState, ObbState newObbState) {
+ synchronized (mObbMounts) {
+ removeObbState(oldObbState);
+ addObbState(newObbState);
}
}
@@ -1614,20 +1755,16 @@
Slog.e(TAG, "Failed to bind to media container service");
action.handleError();
return;
- } else {
- // Once we bind to the service, the first
- // pending request will be processed.
- mActions.add(action);
- }
- } else {
- // Already bound to the service. Just make
- // sure we trigger off processing the first request.
- if (mActions.size() == 0) {
- mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
}
mActions.add(action);
+ break;
}
+
+ // Once we bind to the service, the first
+ // pending request will be processed.
+ mActions.add(action);
+ mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
break;
}
case OBB_MCS_BOUND: {
@@ -1760,6 +1897,29 @@
abstract void handleExecute() throws RemoteException, IOException;
abstract void handleError();
+
+ protected ObbInfo getObbInfo() throws IOException {
+ ObbInfo obbInfo;
+ try {
+ obbInfo = mContainerService.getObbInfo(mObbState.filename);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
+ + mObbState.filename);
+ obbInfo = null;
+ }
+ if (obbInfo == null) {
+ throw new IOException("Couldn't read OBB file: " + mObbState.filename);
+ }
+ return obbInfo;
+ }
+
+ protected void sendNewStatusOrIgnore(String filename, String status) {
+ try {
+ mObbState.token.onObbResult(filename, status);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+ }
+ }
}
class MountObbAction extends ObbAction {
@@ -1770,56 +1930,90 @@
mKey = key;
}
- public void handleExecute() throws RemoteException, IOException {
- ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
- if (obbInfo == null) {
- throw new IOException("Couldn't read OBB file");
+ public void handleExecute() throws IOException, RemoteException {
+ final ObbInfo obbInfo = getObbInfo();
+
+ /*
+ * If someone tried to trick us with some weird characters, rectify
+ * it here.
+ */
+ if (!mObbState.filename.equals(obbInfo.filename)) {
+ if (DEBUG_OBB)
+ Slog.i(TAG, "OBB filename " + mObbState.filename + " is actually "
+ + obbInfo.filename);
+
+ synchronized (mObbMounts) {
+ /*
+ * If the real filename is already mounted, discard this
+ * state and notify the caller that the OBB is already
+ * mounted.
+ */
+ if (isObbMounted(obbInfo.filename)) {
+ if (DEBUG_OBB)
+ Slog.i(TAG, "OBB already mounted as " + obbInfo.filename);
+
+ removeObbState(mObbState);
+ sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_MOUNTED);
+ return;
+ }
+
+ /*
+ * It's not already mounted, so we have to replace the state
+ * with the state containing the actual filename.
+ */
+ ObbState newObbState = new ObbState(obbInfo.filename, mObbState.token,
+ mObbState.callerUid);
+ replaceObbState(mObbState, newObbState);
+ mObbState = newObbState;
+ }
}
if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
throw new IllegalArgumentException("Caller package does not match OBB file");
}
- if (mKey == null) {
- mKey = "none";
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- String cmd = String.format("obb mount %s %s %d", mObbState.filename, mKey,
- mObbState.callerUid);
- try {
- mConnector.doCommand(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code != VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedInternalError;
+ boolean mounted = false;
+ int rc;
+ synchronized (mObbState) {
+ if (mObbState.mounted) {
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
+ return;
}
- }
- if (rc == StorageResultCode.OperationSucceeded) {
+ rc = StorageResultCode.OperationSucceeded;
+ String cmd = String.format("obb mount %s %s %d", mObbState.filename,
+ mKey != null ? mKey : "none",
+ mObbState.callerUid);
try {
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_MOUNTED);
- } catch (RemoteException e) {
- Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+ mConnector.doCommand(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ int code = e.getCode();
+ if (code != VoldResponseCode.OpFailedStorageBusy) {
+ rc = StorageResultCode.OperationFailedInternalError;
+ }
}
+
+ if (rc == StorageResultCode.OperationSucceeded) {
+ mObbState.mounted = mounted = true;
+ }
+ }
+
+ if (mounted) {
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
} else {
Slog.e(TAG, "Couldn't mount OBB file: " + rc);
// We didn't succeed, so remove this from the mount-set.
removeObbState(mObbState);
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
}
}
public void handleError() {
removeObbState(mObbState);
- try {
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
- } catch (RemoteException e) {
- Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename);
- }
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
}
@Override
@@ -1845,55 +2039,69 @@
mForceUnmount = force;
}
- public void handleExecute() throws RemoteException, IOException {
- ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename);
- if (obbInfo == null) {
- throw new IOException("Couldn't read OBB file");
- }
+ public void handleExecute() throws IOException {
+ final ObbInfo obbInfo = getObbInfo();
- if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) {
- throw new IllegalArgumentException("Caller package does not match OBB file");
- }
+ /*
+ * If someone tried to trick us with some weird characters, rectify
+ * it here.
+ */
+ synchronized (mObbMounts) {
+ if (!isObbMounted(obbInfo.filename)) {
+ removeObbState(mObbState);
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
+ return;
+ }
- int rc = StorageResultCode.OperationSucceeded;
- String cmd = String.format("obb unmount %s%s", mObbState.filename,
- (mForceUnmount ? " force" : ""));
- try {
- mConnector.doCommand(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
+ if (!mObbState.filename.equals(obbInfo.filename)) {
+ removeObbState(mObbState);
+ mObbState = mObbPathToStateMap.get(obbInfo.filename);
}
}
- if (rc == StorageResultCode.OperationSucceeded) {
+ boolean unmounted = false;
+ synchronized (mObbState) {
+ if (!mObbState.mounted) {
+ sendNewStatusOrIgnore(obbInfo.filename, Environment.MEDIA_UNMOUNTED);
+ return;
+ }
+
+ int rc = StorageResultCode.OperationSucceeded;
+ String cmd = String.format("obb unmount %s%s", mObbState.filename,
+ (mForceUnmount ? " force" : ""));
+ try {
+ mConnector.doCommand(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ int code = e.getCode();
+ if (code == VoldResponseCode.OpFailedStorageBusy) {
+ rc = StorageResultCode.OperationFailedStorageBusy;
+ } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
+ // If it's not mounted then we've already won.
+ rc = StorageResultCode.OperationSucceeded;
+ } else {
+ rc = StorageResultCode.OperationFailedInternalError;
+ }
+ }
+
+ if (rc == StorageResultCode.OperationSucceeded) {
+ mObbState.mounted = false;
+ unmounted = true;
+ }
+ }
+
+ if (unmounted) {
removeObbState(mObbState);
- try {
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_UNMOUNTED);
- } catch (RemoteException e) {
- Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
- }
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_UNMOUNTED);
} else {
- try {
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
- } catch (RemoteException e) {
- Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
- }
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_MOUNTED);
}
}
public void handleError() {
removeObbState(mObbState);
- try {
- mObbState.token.onObbResult(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
- } catch (RemoteException e) {
- Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename);
- }
+ sendNewStatusOrIgnore(mObbState.filename, Environment.MEDIA_BAD_REMOVAL);
}
@Override
@@ -1908,9 +2116,33 @@
sb.append(mObbState.callerUid);
sb.append(",token=");
sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
+ sb.append(",binder=");
+ sb.append(mObbState.getBinder().toString());
sb.append('}');
return sb.toString();
}
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump ActivityManager from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+
+ pw.println(" mObbMounts:");
+
+ synchronized (mObbMounts) {
+ final Collection<List<ObbState>> obbStateLists = mObbMounts.values();
+
+ for (final List<ObbState> obbStates : obbStateLists) {
+ for (final ObbState obbState : obbStates) {
+ pw.print(" "); pw.println(obbState.toString());
+ }
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 05abd99..8dbd3e7 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -55,7 +55,7 @@
class NetworkManagementService extends INetworkManagementService.Stub {
private static final String TAG = "NetworkManagmentService";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final String NETD_TAG = "NetdConnector";
class NetdResponseCode {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 8f90756..c4d2d4d 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -1102,27 +1102,6 @@
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
readPermissionsFromXml(permFile);
-
- StringBuilder sb = new StringBuilder(128);
- sb.append("Libs:");
- Iterator<String> it = mSharedLibraries.keySet().iterator();
- while (it.hasNext()) {
- sb.append(' ');
- String name = it.next();
- sb.append(name);
- sb.append(':');
- sb.append(mSharedLibraries.get(name));
- }
- Log.i(TAG, sb.toString());
-
- sb.setLength(0);
- sb.append("Features:");
- it = mAvailableFeatures.keySet().iterator();
- while (it.hasNext()) {
- sb.append(' ');
- sb.append(it.next());
- }
- Log.i(TAG, sb.toString());
}
private void readPermissionsFromXml(File permFile) {
@@ -2632,7 +2611,7 @@
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
Log.i(TAG, "Package " + ps.name + " at " + scanFile
- + "ignored: updated version " + ps.versionCode
+ + " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
return null;
@@ -5487,7 +5466,7 @@
deletePackageLI(
pkgName, false,
dataDirExists ? PackageManager.DONT_DELETE_DATA : 0,
- res.removedInfo);
+ res.removedInfo, true);
}
}
}
@@ -5532,7 +5511,7 @@
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA,
- res.removedInfo)) {
+ res.removedInfo, true)) {
// If the existing package was'nt successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
deletedPkg = false;
@@ -5562,7 +5541,7 @@
deletePackageLI(
pkgName, true,
PackageManager.DONT_DELETE_DATA,
- res.removedInfo);
+ res.removedInfo, true);
}
// Since we failed to install the new package we need to restore the old
// package that we deleted.
@@ -6030,7 +6009,7 @@
synchronized (mInstallLock) {
res = deletePackageLI(packageName, deleteCodeAndResources,
- flags | REMOVE_CHATTY, info);
+ flags | REMOVE_CHATTY, info, true);
}
if(res && sendBroadCast) {
@@ -6091,7 +6070,7 @@
* delete a partially installed application.
*/
private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo,
- int flags) {
+ int flags, boolean writeSettings) {
String packageName = p.packageName;
if (outInfo != null) {
outInfo.removedPackage = packageName;
@@ -6144,8 +6123,10 @@
mSettings.mPreferredActivities.removeFilter(pa);
}
}
- // Save settings now
- mSettings.writeLP();
+ if (writeSettings) {
+ // Save settings now
+ mSettings.writeLP();
+ }
}
}
@@ -6153,7 +6134,7 @@
* Tries to delete system package.
*/
private boolean deleteSystemPackageLI(PackageParser.Package p,
- int flags, PackageRemovedInfo outInfo) {
+ int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
//applicable for non-partially installed applications only
if (applicationInfo == null) {
@@ -6185,7 +6166,8 @@
deleteCodeAndResources = false;
flags |= PackageManager.DONT_DELETE_DATA;
}
- boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
if (!ret) {
return false;
}
@@ -6206,13 +6188,16 @@
}
synchronized (mPackages) {
updatePermissionsLP(newPkg.packageName, newPkg, true, true, false);
- mSettings.writeLP();
+ if (writeSettings) {
+ mSettings.writeLP();
+ }
}
return true;
}
private boolean deleteInstalledPackageLI(PackageParser.Package p,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
if (applicationInfo == null) {
Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
@@ -6223,7 +6208,7 @@
}
// Delete package data from internal structures and also remove data if flag is set
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
// Delete application code and resources
if (deleteCodeAndResources) {
@@ -6240,7 +6225,8 @@
* This method handles package deletion in general
*/
private boolean deletePackageLI(String packageName,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
if (packageName == null) {
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
@@ -6267,7 +6253,7 @@
if (dataOnly) {
// Delete application data first
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
return true;
}
// At this point the package should have ApplicationInfo associated with it
@@ -6280,12 +6266,13 @@
Log.i(TAG, "Removing system package:"+p.packageName);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- ret = deleteSystemPackageLI(p, flags, outInfo);
+ ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings);
} else {
Log.i(TAG, "Removing non-system package:"+p.packageName);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, p.applicationInfo.uid);
- ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
}
return ret;
}
@@ -6866,7 +6853,18 @@
return;
}
+ boolean dumpStar = true;
+ boolean dumpLibs = false;
+ boolean dumpFeatures = false;
+ boolean dumpResolvers = false;
+ boolean dumpPermissions = false;
+ boolean dumpPackages = false;
+ boolean dumpSharedUsers = false;
+ boolean dumpMessages = false;
+ boolean dumpProviders = false;
+
String packageName = null;
+ boolean showFilters = false;
int opti = 0;
while (opti < args.length) {
@@ -6879,10 +6877,22 @@
// Right now we only know how to print all.
} else if ("-h".equals(opt)) {
pw.println("Package manager dump options:");
- pw.println(" [-h] [cmd] ...");
+ pw.println(" [-h] [-f] [cmd] ...");
+ pw.println(" -f: print details of intent filters");
+ pw.println(" -h: print this help");
pw.println(" cmd may be one of:");
- pw.println(" [package.name]: info about given package");
+ pw.println(" l[ibraries]: list known shared libraries");
+ pw.println(" f[ibraries]: list device features");
+ pw.println(" r[esolvers]: dump intent resolvers");
+ pw.println(" perm[issions]: dump permissions");
+ pw.println(" prov[iders]: dump content providers");
+ pw.println(" p[ackages]: dump installed packages");
+ pw.println(" s[hared-users]: dump shared user IDs");
+ pw.println(" m[essages]: print collected runtime messages");
+ pw.println(" <package.name>: info about given package");
return;
+ } else if ("-f".equals(opt)) {
+ showFilters = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
@@ -6895,32 +6905,87 @@
// Is this a package name?
if ("android".equals(cmd) || cmd.contains(".")) {
packageName = cmd;
+ } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
+ dumpStar = false;
+ dumpLibs = true;
+ } else if ("f".equals(cmd) || "features".equals(cmd)) {
+ dumpStar = false;
+ dumpFeatures = true;
+ } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
+ dumpStar = false;
+ dumpResolvers = true;
+ } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
+ dumpStar = false;
+ dumpPermissions = true;
+ } else if ("p".equals(cmd) || "packages".equals(cmd)) {
+ dumpStar = false;
+ dumpPackages = true;
+ } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
+ dumpStar = false;
+ dumpSharedUsers = true;
+ } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
+ dumpStar = false;
+ dumpProviders = true;
+ } else if ("m".equals(cmd) || "messages".equals(cmd)) {
+ dumpStar = false;
+ dumpMessages = true;
}
}
boolean printedTitle = false;
synchronized (mPackages) {
- if (mActivities.dump(pw, "Activity Resolver Table:", " ", packageName)) {
+ if ((dumpStar || dumpLibs) && packageName == null) {
+ if (printedTitle) pw.println(" ");
printedTitle = true;
+ pw.println("Libraries:");
+ Iterator<String> it = mSharedLibraries.keySet().iterator();
+ while (it.hasNext()) {
+ String name = it.next();
+ pw.print(" ");
+ pw.print(name);
+ pw.print(" -> ");
+ pw.println(mSharedLibraries.get(name));
+ }
}
- if (mReceivers.dump(pw, printedTitle
- ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:",
- " ", packageName)) {
+
+ if ((dumpStar || dumpFeatures) && packageName == null) {
+ if (printedTitle) pw.println(" ");
printedTitle = true;
+ pw.println("Features:");
+ Iterator<String> it = mAvailableFeatures.keySet().iterator();
+ while (it.hasNext()) {
+ String name = it.next();
+ pw.print(" ");
+ pw.println(name);
+ }
}
- if (mServices.dump(pw, printedTitle
- ? "\nService Resolver Table:" : "Service Resolver Table:",
- " ", packageName)) {
- printedTitle = true;
+
+ if (dumpStar || dumpResolvers) {
+ if (mActivities.dump(pw, printedTitle
+ ? "\nActivity Resolver Table:" : "Activity Resolver Table:",
+ " ", packageName, showFilters)) {
+ printedTitle = true;
+ }
+ if (mReceivers.dump(pw, printedTitle
+ ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:",
+ " ", packageName, showFilters)) {
+ printedTitle = true;
+ }
+ if (mServices.dump(pw, printedTitle
+ ? "\nService Resolver Table:" : "Service Resolver Table:",
+ " ", packageName, showFilters)) {
+ printedTitle = true;
+ }
+ if (mSettings.mPreferredActivities.dump(pw, printedTitle
+ ? "\nPreferred Activities:" : "Preferred Activities:",
+ " ", packageName, showFilters)) {
+ printedTitle = true;
+ }
}
- if (mSettings.mPreferredActivities.dump(pw, printedTitle
- ? "\nPreferred Activities:" : "Preferred Activities:",
- " ", packageName)) {
- printedTitle = true;
- }
+
boolean printedSomething = false;
- {
+ if (dumpStar || dumpPermissions) {
for (BasePermission p : mSettings.mPermissions.values()) {
if (packageName != null && !packageName.equals(p.sourcePackage)) {
continue;
@@ -6947,9 +7012,27 @@
}
}
}
+
+ if (dumpStar || dumpProviders) {
+ printedSomething = false;
+ for (PackageParser.Provider p : mProviders.values()) {
+ if (packageName != null && !packageName.equals(p.info.packageName)) {
+ continue;
+ }
+ if (!printedSomething) {
+ if (printedTitle) pw.println(" ");
+ pw.println("Registered ContentProviders:");
+ printedSomething = true;
+ printedTitle = true;
+ }
+ pw.print(" ["); pw.print(p.info.authority); pw.print("]: ");
+ pw.println(p.toString());
+ }
+ }
+
printedSomething = false;
SharedUserSetting packageSharedUser = null;
- {
+ if (dumpStar || dumpPackages) {
for (PackageSetting ps : mSettings.mPackages.values()) {
if (packageName != null && !packageName.equals(ps.realName)
&& !packageName.equals(ps.name)) {
@@ -7052,52 +7135,54 @@
}
}
printedSomething = false;
- if (mSettings.mRenamedPackages.size() > 0) {
- for (HashMap.Entry<String, String> e
- : mSettings.mRenamedPackages.entrySet()) {
- if (packageName != null && !packageName.equals(e.getKey())
- && !packageName.equals(e.getValue())) {
- continue;
+ if (dumpStar || dumpPackages) {
+ if (mSettings.mRenamedPackages.size() > 0) {
+ for (HashMap.Entry<String, String> e
+ : mSettings.mRenamedPackages.entrySet()) {
+ if (packageName != null && !packageName.equals(e.getKey())
+ && !packageName.equals(e.getValue())) {
+ continue;
+ }
+ if (!printedSomething) {
+ if (printedTitle) pw.println(" ");
+ pw.println("Renamed packages:");
+ printedSomething = true;
+ printedTitle = true;
+ }
+ pw.print(" "); pw.print(e.getKey()); pw.print(" -> ");
+ pw.println(e.getValue());
}
- if (!printedSomething) {
- if (printedTitle) pw.println(" ");
- pw.println("Renamed packages:");
- printedSomething = true;
- printedTitle = true;
+ }
+ printedSomething = false;
+ if (mSettings.mDisabledSysPackages.size() > 0) {
+ for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) {
+ if (packageName != null && !packageName.equals(ps.realName)
+ && !packageName.equals(ps.name)) {
+ continue;
+ }
+ if (!printedSomething) {
+ if (printedTitle) pw.println(" ");
+ pw.println("Hidden system packages:");
+ printedSomething = true;
+ printedTitle = true;
+ }
+ pw.print(" Package [");
+ pw.print(ps.realName != null ? ps.realName : ps.name);
+ pw.print("] (");
+ pw.print(Integer.toHexString(System.identityHashCode(ps)));
+ pw.println("):");
+ if (ps.realName != null) {
+ pw.print(" compat name="); pw.println(ps.name);
+ }
+ pw.print(" userId="); pw.println(ps.userId);
+ pw.print(" sharedUser="); pw.println(ps.sharedUser);
+ pw.print(" codePath="); pw.println(ps.codePathString);
+ pw.print(" resourcePath="); pw.println(ps.resourcePathString);
}
- pw.print(" "); pw.print(e.getKey()); pw.print(" -> ");
- pw.println(e.getValue());
}
}
printedSomething = false;
- if (mSettings.mDisabledSysPackages.size() > 0) {
- for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) {
- if (packageName != null && !packageName.equals(ps.realName)
- && !packageName.equals(ps.name)) {
- continue;
- }
- if (!printedSomething) {
- if (printedTitle) pw.println(" ");
- pw.println("Hidden system packages:");
- printedSomething = true;
- printedTitle = true;
- }
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
- if (ps.realName != null) {
- pw.print(" compat name="); pw.println(ps.name);
- }
- pw.print(" userId="); pw.println(ps.userId);
- pw.print(" sharedUser="); pw.println(ps.sharedUser);
- pw.print(" codePath="); pw.println(ps.codePathString);
- pw.print(" resourcePath="); pw.println(ps.resourcePathString);
- }
- }
- printedSomething = false;
- {
+ if (dumpStar || dumpSharedUsers) {
for (SharedUserSetting su : mSettings.mSharedUsers.values()) {
if (packageName != null && su != packageSharedUser) {
continue;
@@ -7120,11 +7205,11 @@
}
}
- if (packageName == null) {
+ if ((dumpStar || dumpMessages) && packageName == null) {
if (printedTitle) pw.println(" ");
printedTitle = true;
pw.println("Settings parse messages:");
- pw.println(mSettings.mReadMessages.toString());
+ pw.print(mSettings.mReadMessages.toString());
pw.println(" ");
pw.println("Package warning messages:");
@@ -7135,29 +7220,12 @@
int avail = in.available();
byte[] data = new byte[avail];
in.read(data);
- pw.println(new String(data));
+ pw.print(new String(data));
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
}
-
- synchronized (mProviders) {
- boolean printedSomething = false;
- for (PackageParser.Provider p : mProviders.values()) {
- if (packageName != null && !packageName.equals(p.info.packageName)) {
- continue;
- }
- if (!printedSomething) {
- if (printedTitle) pw.println(" ");
- pw.println("Registered ContentProviders:");
- printedSomething = true;
- printedTitle = true;
- }
- pw.print(" ["); pw.print(p.info.authority); pw.print("]: ");
- pw.println(p.toString());
- }
- }
}
static final class BasePermission {
@@ -9679,7 +9747,7 @@
PackageRemovedInfo outInfo = new PackageRemovedInfo();
synchronized (mInstallLock) {
boolean res = deletePackageLI(pkgName, false,
- PackageManager.DONT_DELETE_DATA, outInfo);
+ PackageManager.DONT_DELETE_DATA, outInfo, false);
if (res) {
pkgList.add(pkgName);
} else {
@@ -9688,6 +9756,13 @@
}
}
}
+
+ synchronized (mPackages) {
+ // We didn't update the settings after removing each package;
+ // write them now for all packages.
+ mSettings.writeLP();
+ }
+
// We have to absolutely send UPDATED_MEDIA_STATUS only
// after confirming that all the receivers processed the ordered
// broadcast when packages get disabled, force a gc to clean things up.
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 9401ff8..29a9a7e 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -871,7 +871,7 @@
mWakeLockState = mLocks.gatherState();
// goes in the middle to reduce flicker
if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) {
- userActivity(SystemClock.uptimeMillis(), false);
+ userActivity(SystemClock.uptimeMillis(), -1, false, OTHER_EVENT, false);
}
setPowerState(mWakeLockState | mUserState);
}
@@ -2105,6 +2105,7 @@
}
public void userActivity(long time, boolean noChangeLights) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
userActivity(time, -1, noChangeLights, OTHER_EVENT, false);
}
@@ -2128,7 +2129,6 @@
private void userActivity(long time, long timeoutOverride, boolean noChangeLights,
int eventType, boolean force) {
- //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)
&& (eventType == CHEEK_EVENT || eventType == TOUCH_EVENT)) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 717f63c..80dcc98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -31,6 +31,7 @@
import android.content.ContentService;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioService;
import android.os.Build;
@@ -44,6 +45,9 @@
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
+import android.view.Display;
+import android.view.WindowManager;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -496,6 +500,16 @@
statusBar.systemReady();
}
wm.systemReady();
+
+ // Update the configuration for this context by hand, because we're going
+ // to start using it before the config change done in wm.systemReady() will
+ // propagate to it.
+ Configuration config = wm.computeNewConfiguration();
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ w.getDefaultDisplay().getMetrics(metrics);
+ context.getResources().updateConfiguration(config, metrics);
+
power.systemReady();
try {
pm.systemReady();
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 7100cc5..ba3897d 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -40,6 +40,7 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -51,6 +52,8 @@
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -93,6 +96,7 @@
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Display;
+import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
@@ -104,6 +108,8 @@
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.InputHandler;
+import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -159,6 +165,7 @@
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
+ static final boolean DEBUG_DRAG = true;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
@@ -486,6 +493,330 @@
boolean mTurnOnScreen;
/**
+ * Drag/drop state
+ */
+ class DragState {
+ IBinder mToken;
+ Surface mSurface;
+ boolean mLocalOnly;
+ ClipData mData;
+ ClipDescription mDataDescription;
+ float mThumbOffsetX, mThumbOffsetY;
+ InputChannel mServerChannel, mClientChannel;
+ WindowState mTargetWindow;
+ ArrayList<WindowState> mNotifiedWindows;
+ boolean mDragEnded;
+
+ private final Rect tmpRect = new Rect();
+
+ DragState(IBinder token, Surface surface, boolean localOnly) {
+ mToken = token;
+ mSurface = surface;
+ mLocalOnly = localOnly;
+ mNotifiedWindows = new ArrayList<WindowState>();
+ }
+
+ void reset() {
+ if (mSurface != null) {
+ mSurface.destroy();
+ }
+ mSurface = null;
+ mLocalOnly = false;
+ mToken = null;
+ mData = null;
+ mThumbOffsetX = mThumbOffsetY = 0;
+ mNotifiedWindows = null;
+ }
+
+ void register() {
+ if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel");
+ if (mClientChannel != null) {
+ Slog.e(TAG, "Duplicate register of drag input channel");
+ } else {
+ InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+ mServerChannel = channels[0];
+ mClientChannel = channels[1];
+ mInputManager.registerInputChannel(mServerChannel);
+ InputQueue.registerInputChannel(mClientChannel, mDragInputHandler,
+ mH.getLooper().getQueue());
+ }
+ }
+
+ void unregister() {
+ if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel");
+ if (mClientChannel == null) {
+ Slog.e(TAG, "Unregister of nonexistent drag input channel");
+ } else {
+ mInputManager.unregisterInputChannel(mServerChannel);
+ InputQueue.unregisterInputChannel(mClientChannel);
+ mClientChannel.dispose();
+ mClientChannel = null;
+ mServerChannel = null;
+ }
+ }
+
+ /* call out to each visible window/session informing it about the drag
+ */
+ void broadcastDragStartedLw() {
+ // Cache a base-class instance of the clip metadata so that parceling
+ // works correctly in calling out to the apps.
+ mDataDescription = new ClipDescription(mData);
+ mNotifiedWindows.clear();
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_STARTED of " + mDataDescription);
+ }
+
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+ mDataDescription, null);
+ final int N = mWindows.size();
+ for (int i = 0; i < N; i++) {
+ // sendDragStartedLw() clones evt for local-process dispatch
+ sendDragStartedLw(mWindows.get(i), evt);
+ }
+ evt.recycle();
+ }
+
+ /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+ * designated window is potentially a drop recipient. There are race situations
+ * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+ * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+ *
+ * This method clones the 'event' parameter if it's being delivered to the same
+ * process, so it's safe for the caller to call recycle() on the event afterwards.
+ */
+ private void sendDragStartedLw(WindowState newWin, DragEvent event) {
+ if (!mDragEnded && newWin.isPotentialDragTarget()) {
+ try {
+ // clone for local callees since dispatch will recycle the event
+ if (Process.myPid() == newWin.mSession.mPid) {
+ event = DragEvent.obtain(event);
+ }
+ newWin.mClient.dispatchDragEvent(event);
+ // track each window that we've notified that the drag is starting
+ mNotifiedWindows.add(newWin);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-start window " + newWin);
+ }
+ }
+ }
+
+ /* helper - construct and send a DRAG_STARTED event only if the window has not
+ * previously been notified, i.e. it became visible after the drag operation
+ * was begun. This is a rare case.
+ */
+ private void sendDragStartedIfNeededLw(WindowState newWin) {
+ // If we have sent the drag-started, we needn't do so again
+ for (WindowState ws : mNotifiedWindows) {
+ if (ws == newWin) {
+ return;
+ }
+ }
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+ }
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+ mDataDescription, null);
+ // sendDragStartedLw() clones 'event' if the window is process-local
+ sendDragStartedLw(newWin, event);
+ event.recycle();
+ }
+
+ void broadcastDragEnded() {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_ENDED");
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null);
+ synchronized (mWindowMap) {
+ for (WindowState ws: mNotifiedWindows) {
+ try {
+ ws.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-end window " + ws);
+ }
+ }
+ mNotifiedWindows.clear();
+ mDragEnded = true;
+ }
+ evt.recycle();
+ }
+
+ void notifyMoveLw(float x, float y) {
+ final int myPid = Process.myPid();
+
+ // Move the surface to the given touch
+ mSurface.openTransaction();
+ mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY));
+ mSurface.closeTransaction();
+
+ // Tell the affected window
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ try {
+ // have we dragged over a new window?
+ if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow);
+ }
+ // force DRAG_EXITED_EVENT if appropriate
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
+ 0, 0, null, null);
+ mTargetWindow.mClient.dispatchDragEvent(evt);
+ if (myPid != mTargetWindow.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ if (touchedWin != null) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin);
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
+ x, y, null, null);
+ touchedWin.mClient.dispatchDragEvent(evt);
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drag notification to windows");
+ }
+ mTargetWindow = touchedWin;
+ }
+
+ // Tell the drop target about the data, and then broadcast the drag-ended notification
+ void notifyDropLw(float x, float y) {
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ if (touchedWin != null) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DROP to " + touchedWin);
+ }
+ final int myPid = Process.myPid();
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, x, y, null, mData);
+ try {
+ touchedWin.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+ }
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ }
+
+ // Find the visible, touch-deliverable window under the given point
+ private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+ WindowState touchedWin = null;
+ final int x = (int) xf;
+ final int y = (int) yf;
+ final ArrayList<WindowState> windows = mWindows;
+ final int N = windows.size();
+ for (int i = N - 1; i >= 0; i--) {
+ WindowState child = windows.get(i);
+ final int flags = child.mAttrs.flags;
+ if (!child.isVisibleLw()) {
+ // not visible == don't tell about drags
+ continue;
+ }
+ if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+ // not touchable == don't tell about drags
+ continue;
+ }
+ // account for the window's decor etc
+ tmpRect.set(child.mFrame);
+ if (child.mTouchableInsets == ViewTreeObserver
+ .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+ // The point is inside of the window if it is
+ // inside the frame, AND the content part of that
+ // frame that was given by the application.
+ tmpRect.left += child.mGivenContentInsets.left;
+ tmpRect.top += child.mGivenContentInsets.top;
+ tmpRect.right -= child.mGivenContentInsets.right;
+ tmpRect.bottom -= child.mGivenContentInsets.bottom;
+ } else if (child.mTouchableInsets == ViewTreeObserver
+ .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+ // The point is inside of the window if it is
+ // inside the frame, AND the visible part of that
+ // frame that was given by the application.
+ tmpRect.left += child.mGivenVisibleInsets.left;
+ tmpRect.top += child.mGivenVisibleInsets.top;
+ tmpRect.right -= child.mGivenVisibleInsets.right;
+ tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+ }
+ final int touchFlags = flags &
+ (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ if (tmpRect.contains(x, y) || touchFlags == 0) {
+ // Found it
+ touchedWin = child;
+ break;
+ }
+ }
+
+ return touchedWin;
+ }
+ }
+
+ DragState mDragState = null;
+ private final InputHandler mDragInputHandler = new BaseInputHandler() {
+ @Override
+ public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ boolean endDrag = false;
+ final float newX = event.getRawX();
+ final float newY = event.getRawY();
+
+ try {
+ if (mDragState != null) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+ }
+ } break;
+
+ case MotionEvent.ACTION_MOVE: {
+ synchronized (mWindowMap) {
+ // move the surface and tell the involved window(s) where we are
+ mDragState.notifyMoveLw(newX, newY);
+ }
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+ + newX + "," + newY);
+ synchronized (mWindowMap) {
+ mDragState.notifyDropLw(newX, newY);
+ }
+ endDrag = true;
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+ endDrag = true;
+ } break;
+ }
+
+ if (endDrag) {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+ // tell all the windows that the drag has ended
+ mDragState.broadcastDragEnded();
+
+ // stop intercepting input
+ mDragState.unregister();
+ mInputMonitor.updateInputWindowsLw();
+
+ // free our resources and drop all the object references
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception caught by drag handleMotion", e);
+ } finally {
+ finishedCallback.run();
+ }
+ }
+ };
+
+ /**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
*/
@@ -5046,7 +5377,60 @@
mPolicy.adjustConfigurationLw(config);
return true;
}
-
+
+ // -------------------------------------------------------------
+ // Drag and drop
+ // -------------------------------------------------------------
+
+ IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+ boolean localOnly, int width, int height, Surface outSurface) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+ + " local=" + localOnly + " win=" + window
+ + " asbinder=" + window.asBinder());
+ }
+
+ final int callerPid = Binder.getCallingPid();
+ final long origId = Binder.clearCallingIdentity();
+ IBinder token = null;
+
+ try {
+ synchronized (mWindowMap) {
+ try {
+ // !!! TODO: fail if the given window does not currently have touch focus?
+
+ if (mDragState == null) {
+ Surface surface = new Surface(session, callerPid, "drag surface", 0,
+ width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ outSurface.copyFrom(surface);
+ token = new Binder();
+ mDragState = new DragState(token, surface, localOnly);
+ mDragState.mSurface = surface;
+ mDragState.mLocalOnly = localOnly;
+ token = mDragState.mToken = new Binder();
+
+ // 5 second timeout for this window to actually begin the drag
+ mH.removeMessages(H.DRAG_START_TIMEOUT, window);
+ Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, window.asBinder());
+ mH.sendMessageDelayed(msg, 5000);
+ } else {
+ Slog.w(TAG, "Drag already in progress");
+ }
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+ if (mDragState != null) {
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return token;
+ }
+
// -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
@@ -5145,7 +5529,42 @@
return null;
}
-
+
+ private void addDragInputWindow(InputWindowList windowList) {
+ final InputWindow inputWindow = windowList.add();
+ inputWindow.inputChannel = mDragState.mServerChannel;
+ inputWindow.name = "drag";
+ inputWindow.layoutParamsFlags = 0;
+ inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+ inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+ inputWindow.visible = true;
+ inputWindow.canReceiveKeys = false;
+ inputWindow.hasFocus = true;
+ inputWindow.hasWallpaper = false;
+ inputWindow.paused = false;
+ inputWindow.layer = mPolicy.windowTypeToLayerLw(inputWindow.layoutParamsType)
+ * TYPE_LAYER_MULTIPLIER
+ + TYPE_LAYER_OFFSET;
+ inputWindow.ownerPid = Process.myPid();
+ inputWindow.ownerUid = Process.myUid();
+
+ // The drag window covers the entire display
+ inputWindow.frameLeft = 0;
+ inputWindow.frameTop = 0;
+ inputWindow.frameRight = mDisplay.getWidth();
+ inputWindow.frameBottom = mDisplay.getHeight();
+
+ inputWindow.visibleFrameLeft = inputWindow.frameLeft;
+ inputWindow.visibleFrameTop = inputWindow.frameTop;
+ inputWindow.visibleFrameRight = inputWindow.frameRight;
+ inputWindow.visibleFrameBottom = inputWindow.frameBottom;
+
+ inputWindow.touchableAreaLeft = inputWindow.frameLeft;
+ inputWindow.touchableAreaTop = inputWindow.frameTop;
+ inputWindow.touchableAreaRight = inputWindow.frameRight;
+ inputWindow.touchableAreaBottom = inputWindow.frameBottom;
+ }
+
/* Updates the cached window information provided to the input dispatcher. */
public void updateInputWindowsLw() {
// Populate the input window list with information about all of the windows that
@@ -5154,6 +5573,16 @@
// out to be difficult because only the native code knows for sure which window
// currently has touch focus.
final ArrayList<WindowState> windows = mWindows;
+
+ // If there's a drag in flight, provide a pseudowindow to catch drag input
+ final boolean inDrag = (mDragState != null);
+ if (inDrag) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "Inserting drag window");
+ }
+ addDragInputWindow(mTempInputWindows);
+ }
+
final int N = windows.size();
for (int i = N - 1; i >= 0; i--) {
final WindowState child = windows.get(i);
@@ -5169,7 +5598,13 @@
final boolean isVisible = child.isVisibleLw();
final boolean hasWallpaper = (child == mWallpaperTarget)
&& (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
-
+
+ // If there's a drag in progress and 'child' is a potential drop target,
+ // make sure it's been told about the drag
+ if (inDrag && isVisible) {
+ mDragState.sendDragStartedIfNeededLw(child);
+ }
+
// Add a window to our list of input windows.
final InputWindow inputWindow = mTempInputWindows.add();
inputWindow.inputChannel = child.mInputChannel;
@@ -5741,6 +6176,86 @@
}
}
+ /* Drag/drop */
+ public IBinder prepareDrag(IWindow window, boolean localOnly,
+ int width, int height, Surface outSurface) {
+ return prepareDragSurface(window, mSurfaceSession, localOnly,
+ width, height, outSurface);
+ }
+
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "perform drag: win=" + window + " data=" + data);
+ }
+
+ synchronized (mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG, "No drag prepared");
+ throw new IllegalStateException("performDrag() without prepareDrag()");
+ }
+
+ if (dragToken != mDragState.mToken) {
+ Slog.w(TAG, "Performing mismatched drag");
+ throw new IllegalStateException("performDrag() does not match prepareDrag()");
+ }
+
+ WindowState callingWin = windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG, "Bad requesting window " + window);
+ return false; // !!! TODO: throw here?
+ }
+
+ // !!! TODO: if input is not still focused on the initiating window, fail
+ // the drag initiation (e.g. an alarm window popped up just as the application
+ // called performDrag()
+
+ mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+ // !!! TODO: extract the current touch (x, y) in screen coordinates. That
+ // will let us eliminate the (touchX,touchY) parameters from the API.
+
+ mDragState.register();
+ mInputMonitor.updateInputWindowsLw();
+ mInputManager.transferTouchFocus(callingWin.mInputChannel,
+ mDragState.mServerChannel);
+
+ mDragState.mData = data;
+ mDragState.broadcastDragStartedLw();
+
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ final Surface surface = mDragState.mSurface;
+ surface.openTransaction();
+ try {
+ surface.setPosition((int)(touchX - thumbCenterX),
+ (int)(touchY - thumbCenterY));
+ surface.setAlpha(.5f);
+ surface.show();
+ } finally {
+ surface.closeTransaction();
+ }
+ }
+
+ return true; // success!
+ }
+
+ public void dragRecipientEntered(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag into new candidate view @ " + window);
+ }
+ }
+
+ public void dragRecipientExited(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag from old candidate view @ " + window);
+ }
+ }
+
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized(mWindowMap) {
long ident = Binder.clearCallingIdentity();
@@ -6882,6 +7397,15 @@
}
/**
+ * Can this window possibly be a drag/drop target? The test here is
+ * a combination of the above "visible now" with the check that the
+ * Input Manager uses when discarding windows from input consideration.
+ */
+ boolean isPotentialDragTarget() {
+ return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+ }
+
+ /**
* Same as isVisible(), but we also count it as visible between the
* call to IWindowSession.add() and the first relayout().
*/
@@ -6968,34 +7492,6 @@
&& !mDrawPending && !mCommitDrawPending;
}
- public boolean fillsScreenLw(int screenWidth, int screenHeight,
- boolean shownFrame, boolean onlyOpaque) {
- if (mSurface == null) {
- return false;
- }
- if (mAppToken != null && !mAppToken.appFullscreen) {
- return false;
- }
- if (onlyOpaque && mAttrs.format != PixelFormat.OPAQUE) {
- return false;
- }
- final Rect frame = shownFrame ? mShownFrame : mFrame;
-
- if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) {
- return frame.left <= mCompatibleScreenFrame.left &&
- frame.top <= mCompatibleScreenFrame.top &&
- frame.right >= mCompatibleScreenFrame.right &&
- frame.bottom >= mCompatibleScreenFrame.bottom;
- } else {
- if ((mAttrs.flags & FLAG_FULLSCREEN) != 0) {
- return true;
- }
- return frame.left <= 0 && frame.top <= 0
- && frame.right >= screenWidth
- && frame.bottom >= screenHeight;
- }
- }
-
/**
* Return true if the window is opaque and fully drawn. This indicates
* it may obscure windows behind it.
@@ -7810,6 +8306,7 @@
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int SEND_NEW_CONFIGURATION = 18;
public static final int REPORT_WINDOWS_CHANGE = 19;
+ public static final int DRAG_START_TIMEOUT = 20;
private Session mLastReportedHold;
@@ -8152,6 +8649,20 @@
break;
}
+ case DRAG_START_TIMEOUT: {
+ IBinder win = (IBinder)msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Timeout starting drag by win " + win);
+ }
+ synchronized (mWindowMap) {
+ // !!! TODO: ANR the app that has failed to start the drag in time
+ if (mDragState != null) {
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ }
+
}
}
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5c4b919..1eab7fc7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -113,9 +113,15 @@
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+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.InputStreamReader;
import java.io.PrintWriter;
@@ -6123,6 +6129,76 @@
return mSystemReady;
}
+ private static File getCalledPreBootReceiversFile() {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File fname = new File(systemDir, "called_pre_boots.dat");
+ return fname;
+ }
+
+ private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
+ ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
+ File file = getCalledPreBootReceiversFile();
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
+ int vers = dis.readInt();
+ String codename = dis.readUTF();
+ if (vers == android.os.Build.VERSION.SDK_INT
+ && codename.equals(android.os.Build.VERSION.CODENAME)) {
+ int num = dis.readInt();
+ while (num > 0) {
+ num--;
+ String pkg = dis.readUTF();
+ String cls = dis.readUTF();
+ lastDoneReceivers.add(new ComponentName(pkg, cls));
+ }
+ }
+ } catch (FileNotFoundException e) {
+ } catch (IOException e) {
+ Slog.w(TAG, "Failure reading last done pre-boot receivers", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return lastDoneReceivers;
+ }
+
+ private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
+ File file = getCalledPreBootReceiversFile();
+ FileOutputStream fos = null;
+ DataOutputStream dos = null;
+ try {
+ Slog.i(TAG, "Writing new set of last done pre-boot receivers...");
+ fos = new FileOutputStream(file);
+ dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
+ dos.writeInt(android.os.Build.VERSION.SDK_INT);
+ dos.writeUTF(android.os.Build.VERSION.CODENAME);
+ dos.writeInt(list.size());
+ for (int i=0; i<list.size(); i++) {
+ dos.writeUTF(list.get(i).getPackageName());
+ dos.writeUTF(list.get(i).getClassName());
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failure writing last done pre-boot receivers", e);
+ file.delete();
+ } finally {
+ if (dos != null) {
+ try {
+ dos.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
public void systemReady(final Runnable goingCallback) {
// In the simulator, startRunning will never have been called, which
// normally sets a few crucial variables. Do it here instead.
@@ -6157,9 +6233,24 @@
}
}
intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+
+ ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+
+ final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
for (int i=0; i<ris.size(); i++) {
ActivityInfo ai = ris.get(i).activityInfo;
- intent.setComponent(new ComponentName(ai.packageName, ai.name));
+ ComponentName comp = new ComponentName(ai.packageName, ai.name);
+ if (lastDoneReceivers.contains(comp)) {
+ ris.remove(i);
+ i--;
+ }
+ }
+
+ for (int i=0; i<ris.size(); i++) {
+ ActivityInfo ai = ris.get(i).activityInfo;
+ ComponentName comp = new ComponentName(ai.packageName, ai.name);
+ doneReceivers.add(comp);
+ intent.setComponent(comp);
IIntentReceiver finisher = null;
if (i == ris.size()-1) {
finisher = new IIntentReceiver.Stub() {
@@ -6174,6 +6265,7 @@
synchronized (ActivityManagerService.this) {
mDidUpdate = true;
}
+ writeLastDonePreBootReceivers(doneReceivers);
systemReady(goingCallback);
}
});
@@ -6213,19 +6305,19 @@
}
}
- if (procsToKill != null) {
- synchronized(this) {
+ synchronized(this) {
+ if (procsToKill != null) {
for (int i=procsToKill.size()-1; i>=0; i--) {
ProcessRecord proc = procsToKill.get(i);
Slog.i(TAG, "Removing system update proc: " + proc);
removeProcessLocked(proc, true);
}
-
- // Now that we have cleaned up any update processes, we
- // are ready to start launching real processes and know that
- // we won't trample on them any more.
- mProcessesReady = true;
}
+
+ // Now that we have cleaned up any update processes, we
+ // are ready to start launching real processes and know that
+ // we won't trample on them any more.
+ mProcessesReady = true;
}
Slog.i(TAG, "System now ready");
@@ -7741,7 +7833,7 @@
pw.println(" ");
pw.println("Receiver Resolver Table:");
- mReceiverResolver.dump(pw, null, " ", null);
+ mReceiverResolver.dump(pw, null, " ", null, false);
needSep = true;
}
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index bf4db80..6bd89cc 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -444,10 +444,11 @@
sb.append(shortComponentName);
sb.append(": ");
TimeUtils.formatDuration(thisTime, sb);
- sb.append(" (total ");
- TimeUtils.formatDuration(totalTime, sb);
- sb.append(totalTime);
- sb.append(")");
+ if (thisTime != totalTime) {
+ sb.append(" (total ");
+ TimeUtils.formatDuration(totalTime, sb);
+ sb.append(")");
+ }
Log.i(ActivityManagerService.TAG, sb.toString());
}
stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a4497d6..30395c0 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1881,7 +1881,7 @@
String resultWho, int requestCode,
int callingPid, int callingUid, boolean onlyIfNeeded,
boolean componentSpecified) {
- Slog.i(TAG, "Starting activity: " + intent);
+ Slog.i(TAG, "Starting: " + intent);
ActivityRecord sourceRecord = null;
ActivityRecord resultRecord = null;
diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java
index 3f15d0a..8463b5a 100644
--- a/services/java/com/android/server/am/UsageStatsService.java
+++ b/services/java/com/android/server/am/UsageStatsService.java
@@ -57,6 +57,7 @@
public final class UsageStatsService extends IUsageStats.Stub {
public static final String SERVICE_NAME = "usagestats";
private static final boolean localLOGV = false;
+ private static final boolean REPORT_UNEXPECTED = false;
private static final String TAG = "UsageStats";
// Current on-disk Parcel version
@@ -404,11 +405,11 @@
new Thread("UsageStatsService_DiskWriter") {
public void run() {
try {
- Slog.d(TAG, "Disk writer thread starting.");
+ if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
writeStatsToFile(true);
} finally {
mUnforcedDiskWriteRunning.set(false);
- Slog.d(TAG, "Disk writer thread ending.");
+ if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
}
}
}.start();
@@ -458,7 +459,7 @@
}
}
}
- Slog.d(TAG, "Dumped usage stats.");
+ if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
}
private void writeStatsFLOCK(File file) throws IOException {
@@ -493,7 +494,7 @@
}
public void shutdown() {
- Slog.w(TAG, "Writing usage stats before shutdown...");
+ Slog.i(TAG, "Writing usage stats before shutdown...");
writeStatsToFile(true);
}
@@ -520,7 +521,7 @@
if (mLastResumedPkg != null) {
// We last resumed some other package... just pause it now
// to recover.
- Slog.i(TAG, "Unexpected resume of " + pkgName
+ if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
+ " while already resumed in " + mLastResumedPkg);
PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
if (pus != null) {
@@ -559,7 +560,7 @@
return;
}
if (!mIsResumed) {
- Slog.i(TAG, "Something wrong here, didn't expect "
+ if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
+ pkgName + " to be paused");
return;
}
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index 3c05da2..d9b49fd 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -50,17 +50,24 @@
mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
+ public void reconnect() {
+ synchronized (mServiceConnection) {
+ mContext.unbindService(mServiceConnection);
+ 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) {
+ synchronized (mServiceConnection) {
mProvider = IGeocodeProvider.Stub.asInterface(service);
}
}
public void onServiceDisconnected(ComponentName className) {
Log.d(TAG, "onServiceDisconnected " + className);
- synchronized (this) {
+ synchronized (mServiceConnection) {
mProvider = null;
}
}
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 7dc9920..ef2056b 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -45,6 +45,7 @@
private final Context mContext;
private final String mName;
+ private final String mServiceName;
private ILocationProvider mProvider;
private Handler mHandler;
private final Connection mServiceConnection = new Connection();
@@ -65,14 +66,24 @@
Handler handler) {
mContext = context;
mName = name;
+ mServiceName = serviceName;
mHandler = handler;
mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE);
}
+ public void reconnect() {
+ synchronized (mServiceConnection) {
+ // unbind first
+ mContext.unbindService(mServiceConnection);
+ mContext.bindService(new Intent(mServiceName), 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) {
+ synchronized (mServiceConnection) {
mProvider = ILocationProvider.Stub.asInterface(service);
if (mProvider != null) {
mHandler.post(mServiceConnectedTask);
@@ -82,7 +93,7 @@
public void onServiceDisconnected(ComponentName className) {
Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
- synchronized (this) {
+ synchronized (mServiceConnection) {
mProvider = null;
}
}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index e1e54fc..4f1fab7 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -182,7 +182,6 @@
virtual bool getDisplayInfo(int32_t displayId,
int32_t* width, int32_t* height, int32_t* orientation);
- virtual void virtualKeyDownFeedback();
virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags);
virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
@@ -284,11 +283,9 @@
}
void NativeInputManager::dump(String8& dump) {
- dump.append("Input Reader State:\n");
mInputManager->getReader()->dump(dump);
dump.append("\n");
- dump.append("Input Dispatcher State:\n");
mInputManager->getDispatcher()->dump(dump);
dump.append("\n");
}
@@ -464,17 +461,6 @@
return android_server_PowerManagerService_isScreenBright();
}
-void NativeInputManager::virtualKeyDownFeedback() {
-#if DEBUG_INPUT_READER_POLICY
- LOGD("virtualKeyDownFeedback");
-#endif
-
- JNIEnv* env = jniEnv();
-
- env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyDownFeedback);
- checkAndClearExceptionFromCallback(env, "virtualKeyDownFeedback");
-}
-
int32_t NativeInputManager::interceptKey(nsecs_t when,
int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
#if DEBUG_INPUT_READER_POLICY
@@ -483,6 +469,12 @@
when, deviceId, down, keyCode, scanCode, policyFlags);
#endif
+ if (down && (policyFlags & POLICY_FLAG_VIRTUAL)) {
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyDownFeedback);
+ checkAndClearExceptionFromCallback(env, "virtualKeyDownFeedback");
+ }
+
const int32_t WM_ACTION_PASS_TO_USER = 1;
const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2;
const int32_t WM_ACTION_GO_TO_SLEEP = 4;
@@ -1289,6 +1281,7 @@
static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env,
jclass clazz, jobject fromChannelObj, jobject toChannelObj) {
if (checkInputManagerUnitialized(env)) {
+ LOGD("input manager uninitialized; bailing");
return false;
}
@@ -1298,6 +1291,7 @@
android_view_InputChannel_getInputChannel(env, toChannelObj);
if (fromChannel == NULL || toChannel == NULL) {
+ LOGD("bailing because from=%p to=%p", fromChannel, toChannel);
return false;
}
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1b21a8d..fb76720 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -271,6 +271,17 @@
}
}
+void Layer::drawForSreenShot() const
+{
+ bool currentFixedSize = mFixedSize;
+ bool currentBlending = mNeedsBlending;
+ const_cast<Layer*>(this)->mFixedSize = false;
+ const_cast<Layer*>(this)->mFixedSize = true;
+ LayerBase::drawForSreenShot();
+ const_cast<Layer*>(this)->mFixedSize = currentFixedSize;
+ const_cast<Layer*>(this)->mNeedsBlending = currentBlending;
+}
+
void Layer::onDraw(const Region& clip) const
{
Texture tex(mBufferManager.getActiveTexture());
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 188da6a..caa6d17 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -70,6 +70,7 @@
// LayerBase interface
virtual void setGeometry(hwc_layer_t* hwcl);
virtual void setPerFrameData(hwc_layer_t* hwcl);
+ virtual void drawForSreenShot() const;
virtual void onDraw(const Region& clip) const;
virtual uint32_t doTransaction(uint32_t transactionFlags);
virtual void lockPageFlip(bool& recomputeVisibleRegions);
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 3d049a7..14191cb 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -326,6 +326,12 @@
onDraw(clip);
}
+void LayerBase::drawForSreenShot() const
+{
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ onDraw( Region(hw.bounds()) );
+}
+
void LayerBase::clearWithOpenGL(const Region& clip, GLclampf red,
GLclampf green, GLclampf blue,
GLclampf alpha) const
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index c66dc34..bdee05b 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -121,6 +121,7 @@
* to perform the actual drawing.
*/
virtual void draw(const Region& clip) const;
+ virtual void drawForSreenShot() const;
/**
* onDraw - draws the surface.
diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp
index fdf9abc8..c060895 100644
--- a/services/surfaceflinger/LayerBuffer.cpp
+++ b/services/surfaceflinger/LayerBuffer.cpp
@@ -132,6 +132,12 @@
LayerBase::unlockPageFlip(planeTransform, outDirtyRegion);
}
+void LayerBuffer::drawForSreenShot() const
+{
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ clearWithOpenGL( Region(hw.bounds()) );
+}
+
void LayerBuffer::onDraw(const Region& clip) const
{
sp<Source> source(getSource());
diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h
index 1c0bf83..fece858 100644
--- a/services/surfaceflinger/LayerBuffer.h
+++ b/services/surfaceflinger/LayerBuffer.h
@@ -64,6 +64,7 @@
virtual sp<LayerBaseClient::Surface> createSurface() const;
virtual status_t ditch();
virtual void onDraw(const Region& clip) const;
+ virtual void drawForSreenShot() const;
virtual uint32_t doTransaction(uint32_t flags);
virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 17b98a6..e6bdfd1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1653,9 +1653,117 @@
// ---------------------------------------------------------------------------
+status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* w, uint32_t* h, PixelFormat* f,
+ uint32_t sw, uint32_t sh)
+{
+ status_t result = PERMISSION_DENIED;
+
+ // only one display supported for now
+ if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
+ return BAD_VALUE;
+
+ if (!GLExtensions::getInstance().haveFramebufferObject())
+ return INVALID_OPERATION;
+
+ // get screen geometry
+ const DisplayHardware& hw(graphicPlane(dpy).displayHardware());
+ const uint32_t hw_w = hw.getWidth();
+ const uint32_t hw_h = hw.getHeight();
+
+ if ((sw > hw_w) || (sh > hw_h))
+ return BAD_VALUE;
+
+ sw = (!sw) ? hw_w : sw;
+ sh = (!sh) ? hw_h : sh;
+ const size_t size = sw * sh * 4;
+
+ // make sure to clear all GL error flags
+ while ( glGetError() != GL_NO_ERROR ) ;
+
+ // create a FBO
+ GLuint name, tname;
+ glGenRenderbuffersOES(1, &tname);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
+ glGenFramebuffersOES(1, &name);
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
+ GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
+
+ GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+ if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
+
+ // invert everything, b/c glReadPixel() below will invert the FB
+ glViewport(0, 0, sw, sh);
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrthof(0, hw_w, 0, hw_h, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ // redraw the screen entirely...
+ glClearColor(0,0,0,1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ);
+ const size_t count = layers.size();
+ for (size_t i=0 ; i<count ; ++i) {
+ const sp<LayerBase>& layer(layers[i]);
+ layer->drawForSreenShot();
+ }
+
+ // XXX: this is needed on tegra
+ glScissor(0, 0, sw, sh);
+
+ // check for errors and return screen capture
+ if (glGetError() != GL_NO_ERROR) {
+ // error while rendering
+ result = INVALID_OPERATION;
+ } else {
+ // allocate shared memory large enough to hold the
+ // screen capture
+ sp<MemoryHeapBase> base(
+ new MemoryHeapBase(size, 0, "screen-capture") );
+ void* const ptr = base->getBase();
+ if (ptr) {
+ // capture the screen with glReadPixels()
+ glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
+ if (glGetError() == GL_NO_ERROR) {
+ *heap = base;
+ *w = sw;
+ *h = sh;
+ *f = PIXEL_FORMAT_RGBA_8888;
+ result = NO_ERROR;
+ }
+ } else {
+ result = NO_MEMORY;
+ }
+ }
+
+ glEnable(GL_SCISSOR_TEST);
+ glViewport(0, 0, hw_w, hw_h);
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+
+
+ } else {
+ result = BAD_VALUE;
+ }
+
+ // release FBO resources
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+ glDeleteRenderbuffersOES(1, &tname);
+ glDeleteFramebuffersOES(1, &name);
+ return result;
+}
+
+
status_t SurfaceFlinger::captureScreen(DisplayID dpy,
sp<IMemoryHeap>* heap,
- uint32_t* width, uint32_t* height, PixelFormat* format)
+ uint32_t* width, uint32_t* height, PixelFormat* format,
+ uint32_t sw, uint32_t sh)
{
// only one display supported for now
if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
@@ -1671,12 +1779,15 @@
uint32_t* w;
uint32_t* h;
PixelFormat* f;
+ uint32_t sw;
+ uint32_t sh;
status_t result;
public:
MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy,
- sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f)
+ sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f,
+ uint32_t sw, uint32_t sh)
: flinger(flinger), dpy(dpy),
- heap(heap), w(w), h(h), f(f), result(PERMISSION_DENIED)
+ heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED)
{
}
status_t getResult() const {
@@ -1689,94 +1800,15 @@
if (flinger->mSecureFrameBuffer)
return true;
- // make sure to clear all GL error flags
- while ( glGetError() != GL_NO_ERROR ) ;
+ result = flinger->captureScreenImplLocked(dpy,
+ heap, w, h, f, sw, sh);
- // get screen geometry
- const DisplayHardware& hw(flinger->graphicPlane(dpy).displayHardware());
- const uint32_t sw = hw.getWidth();
- const uint32_t sh = hw.getHeight();
- const Region screenBounds(hw.bounds());
- const size_t size = sw * sh * 4;
-
- // create a FBO
- GLuint name, tname;
- glGenRenderbuffersOES(1, &tname);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
- glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
- glGenFramebuffersOES(1, &name);
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
- GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
-
- GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
- if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
-
- // invert everything, b/c glReadPixel() below will invert the FB
- glMatrixMode(GL_PROJECTION);
- glPushMatrix();
- glLoadIdentity();
- glOrthof(0, sw, 0, sh, 0, 1);
- glMatrixMode(GL_MODELVIEW);
-
- // redraw the screen entirely...
- glClearColor(0,0,0,1);
- glClear(GL_COLOR_BUFFER_BIT);
- const Vector< sp<LayerBase> >& layers(
- flinger->mVisibleLayersSortedByZ);
- const size_t count = layers.size();
- for (size_t i=0 ; i<count ; ++i) {
- const sp<LayerBase>& layer(layers[i]);
- if (!strcmp(layer->getTypeId(), "LayerBuffer")) {
- // we cannot render LayerBuffer because it doens't
- // use OpenGL, and won't show-up in the FBO.
- continue;
- }
- layer->draw(screenBounds);
- }
-
- glMatrixMode(GL_PROJECTION);
- glPopMatrix();
- glMatrixMode(GL_MODELVIEW);
-
- // check for errors and return screen capture
- if (glGetError() != GL_NO_ERROR) {
- // error while rendering
- result = INVALID_OPERATION;
- } else {
- // allocate shared memory large enough to hold the
- // screen capture
- sp<MemoryHeapBase> base(
- new MemoryHeapBase(size, 0, "screen-capture") );
- void* const ptr = base->getBase();
- if (ptr) {
- // capture the screen with glReadPixels()
- glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
- if (glGetError() == GL_NO_ERROR) {
- *heap = base;
- *w = sw;
- *h = sh;
- *f = PIXEL_FORMAT_RGBA_8888;
- result = NO_ERROR;
- }
- } else {
- result = NO_MEMORY;
- }
- }
- } else {
- result = BAD_VALUE;
- }
-
- // release FBO resources
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
- glDeleteRenderbuffersOES(1, &tname);
- glDeleteFramebuffersOES(1, &name);
return true;
}
};
sp<MessageBase> msg = new MessageCaptureScreen(this,
- dpy, heap, width, height, format);
+ dpy, heap, width, height, format, sw, sh);
status_t res = postMessageSync(msg);
if (res == NO_ERROR) {
res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6e9ecbd..732e57e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -197,7 +197,9 @@
sp<IMemoryHeap>* heap,
uint32_t* width,
uint32_t* height,
- PixelFormat* format);
+ PixelFormat* format,
+ uint32_t reqWidth,
+ uint32_t reqHeight);
void screenReleased(DisplayID dpy);
void screenAcquired(DisplayID dpy);
@@ -319,6 +321,11 @@
void commitTransaction();
+ status_t captureScreenImplLocked(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width, uint32_t* height, PixelFormat* format,
+ uint32_t reqWidth = 0, uint32_t reqHeight = 0);
+
friend class FreezeLock;
sp<FreezeLock> getFreezeLock() const;
inline void incFreezeCount() {
diff --git a/services/surfaceflinger/tests/screencap/screencap.cpp b/services/surfaceflinger/tests/screencap/screencap.cpp
index 9e893f4..6cf1504 100644
--- a/services/surfaceflinger/tests/screencap/screencap.cpp
+++ b/services/surfaceflinger/tests/screencap/screencap.cpp
@@ -42,7 +42,7 @@
sp<IMemoryHeap> heap;
uint32_t w, h;
PixelFormat f;
- status_t err = composer->captureScreen(0, &heap, &w, &h, &f);
+ status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0);
if (err != NO_ERROR) {
fprintf(stderr, "screen capture failed: %s\n", strerror(-err));
exit(0);
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 7c3508f..09b7d05 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -861,6 +861,25 @@
}
/**
+ * Enables or disables echo suppression.
+ */
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ if (VDBG) {
+ Log.d(LOG_TAG, " setEchoSuppression(" + enabled + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+
+ if (hasActiveFgCall()) {
+ getActiveFgCall().getPhone().setEchoSuppressionEnabled(enabled);
+ }
+
+ if (VDBG) {
+ Log.d(LOG_TAG, "End setEchoSuppression(" + enabled + ")");
+ Log.d(LOG_TAG, this.toString());
+ }
+ }
+
+ /**
* Play a DTMF tone on the active call.
*
* @param c should be one of 0-9, '*' or '#'. Other values will be
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index ae9750d..a967850 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -25,9 +25,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -37,7 +38,7 @@
public class CallerInfoAsyncQuery {
- private static final boolean DBG = false;
+ private static final boolean DBG = true; // STOPSHIP: disable debugging before ship
private static final String LOG_TAG = "CallerInfoAsyncQuery";
private static final int EVENT_NEW_QUERY = 1;
@@ -190,7 +191,7 @@
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (DBG) log("query complete for token: " + token);
+ if (DBG) log("##### onQueryComplete() ##### query complete for token: " + token);
//get the cookie and notify the listener.
CookieWrapper cw = (CookieWrapper) cookie;
@@ -228,6 +229,8 @@
mCallerInfo = new CallerInfo().markAsVoiceMail();
} else {
mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor);
+ if (DBG) log("==> Got mCallerInfo: " + mCallerInfo);
+
// Use the number entered by the user for display.
if (!TextUtils.isEmpty(cw.number)) {
CountryDetector detector = (CountryDetector) mQueryContext.getSystemService(
@@ -243,7 +246,7 @@
//notify that we can clean up the queue after this.
CookieWrapper endMarker = new CookieWrapper();
endMarker.event = EVENT_END_OF_QUEUE;
- startQuery (token, endMarker, null, null, null, null, null);
+ startQuery(token, endMarker, null, null, null, null, null);
}
//notify the listener that the query is complete.
@@ -279,24 +282,82 @@
cw.cookie = cookie;
cw.event = EVENT_NEW_QUERY;
- c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
+ c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);
return c;
}
/**
- * Factory method to start query with a number
+ * Factory method to start the query based on a number.
+ *
+ * Note: if the number contains an "@" character we treat it
+ * as a SIP address, and look it up directly in the Data table
+ * rather than using the PhoneLookup table.
+ * TODO: But eventually we should expose two separate methods, one for
+ * numbers and one for SIP addresses, and then have
+ * PhoneUtils.startGetCallerInfo() decide which one to call based on
+ * the phone type of the incoming connection.
*/
public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
OnQueryCompleteListener listener, Object cookie) {
- //construct the URI object and start Query.
- Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+ if (DBG) {
+ log("##### CallerInfoAsyncQuery startQuery()... #####");
+ log("- number: " + number);
+ log("- cookie: " + cookie);
+ }
+
+ // Construct the URI object and query params, and start the query.
+
+ Uri contactRef;
+ String selection;
+ String[] selectionArgs;
+
+ if (PhoneNumberUtils.isUriNumber(number)) {
+ // "number" is really a SIP address.
+ if (DBG) log(" - Treating number as a SIP address: " + number);
+
+ // We look up SIP addresses directly in the Data table:
+ contactRef = Data.CONTENT_URI;
+
+ // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+ //
+ // Also note we use "upper(data1)" in the WHERE clause, and
+ // uppercase the incoming SIP address, in order to do a
+ // case-insensitive match.
+ //
+ // TODO: need to confirm that the use of upper() doesn't
+ // prevent us from using the index! (Linear scan of the whole
+ // contacts DB can be very slow.)
+ //
+ // TODO: May also need to normalize by adding "sip:" as a
+ // prefix, if we start storing SIP addresses that way in the
+ // database.
+
+ selection = "upper(" + Data.DATA1 + ")=?"
+ + " AND "
+ + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+ selectionArgs = new String[] { number.toUpperCase() };
+
+ } else {
+ // "number" is a regular phone number. Use the PhoneLookup table:
+ contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+ selection = null;
+ selectionArgs = null;
+ }
+
+ if (DBG) {
+ log("==> contactRef: " + contactRef);
+ log("==> selection: " + selection);
+ if (selectionArgs != null) {
+ for (int i = 0; i < selectionArgs.length; i++) {
+ log("==> selectionArgs[" + i + "]: " + selectionArgs[i]);
+ }
+ }
+ }
CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
c.allocate(context, contactRef);
- if (DBG) log("starting query for number: " + number + " handler: " + c.toString());
-
//create cookieWrapper, start query
CookieWrapper cw = new CookieWrapper();
cw.listener = listener;
@@ -312,10 +373,15 @@
cw.event = EVENT_NEW_QUERY;
}
- c.mHandler.startQuery (token, cw, contactRef, null, null, null, null);
-
+ c.mHandler.startQuery(token,
+ cw, // cookie
+ contactRef, // uri
+ null, // projection
+ selection, // selection
+ selectionArgs, // selectionArgs
+ null); // orderBy
return c;
- }
+ }
/**
* Method to add listeners to a currently running query
@@ -331,7 +397,7 @@
cw.cookie = cookie;
cw.event = EVENT_ADD_LISTENER;
- mHandler.startQuery (token, cw, null, null, null, null, null);
+ mHandler.startQuery(token, cw, null, null, null, null, null);
}
/**
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index d753973..52839be 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -35,7 +35,7 @@
*
*/
public abstract class DataConnectionTracker extends Handler {
- protected static final boolean DBG = true;
+ protected static final boolean DBG = false;
protected final String LOG_TAG = "DataConnectionTracker";
/**
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index ec6c0232..2957c7e 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -1187,6 +1187,11 @@
boolean getMute();
/**
+ * Enables or disables echo suppression.
+ */
+ void setEchoSuppressionEnabled(boolean enabled);
+
+ /**
* Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
*
* @param data The data for the request.
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 5412768..74e8c1b 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -505,6 +505,10 @@
mCM.unregisterForResendIncallMute(h);
}
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ // no need for regular phone
+ }
+
/**
* Subclasses of Phone probably want to replace this with a
* version scoped to their packages
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index 9e0c087..02fdf28 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -578,6 +578,10 @@
return mActivePhone.getMute();
}
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ mActivePhone.setEchoSuppressionEnabled(enabled);
+ }
+
public void invokeOemRilRequestRaw(byte[] data, Message response) {
mActivePhone.invokeOemRilRequestRaw(data, response);
}
diff --git a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
deleted file mode 100644
index 4c4e718..0000000
--- a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
+++ /dev/null
@@ -1,258 +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 com.android.internal.telephony;
-
-import android.net.LinkCapabilities;
-import android.net.LinkProperties;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.internal.telephony.ITelephonyRegistry;
-
-/**
- * Temporary. Will be removed after integrating with CallManager.
- * 100% copy from DefaultPhoneNotifier. Cannot access its package level
- * constructor; thus the copy.
- * @hide
- */
-public class SipPhoneNotifier implements PhoneNotifier {
-
- static final String LOG_TAG = "GSM";
- private static final boolean DBG = true;
- private ITelephonyRegistry mRegistry;
-
- public SipPhoneNotifier() {
- mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- }
-
- public void notifyPhoneState(Phone sender) {
- Call ringingCall = sender.getRingingCall();
- String incomingNumber = "";
- if (ringingCall != null && ringingCall.getEarliestConnection() != null){
- incomingNumber = ringingCall.getEarliestConnection().getAddress();
- }
- try {
- mRegistry.notifyCallState(convertCallState(sender.getState()), incomingNumber);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyServiceState(Phone sender) {
- try {
- mRegistry.notifyServiceState(sender.getServiceState());
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifySignalStrength(Phone sender) {
- try {
- mRegistry.notifySignalStrength(sender.getSignalStrength());
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyMessageWaitingChanged(Phone sender) {
- try {
- mRegistry.notifyMessageWaitingChanged(sender.getMessageWaitingIndicator());
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyCallForwardingChanged(Phone sender) {
- try {
- mRegistry.notifyCallForwardingChanged(sender.getCallForwardingIndicator());
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyDataActivity(Phone sender) {
- try {
- mRegistry.notifyDataActivity(convertDataActivityState(sender.getDataActivityState()));
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyDataConnection(Phone sender, String reason, String apnType) {
- doNotifyDataConnection(sender, reason, apnType, sender.getDataConnectionState(apnType));
- }
-
- public void notifyDataConnection(Phone sender, String reason, String apnType,
- Phone.DataState state) {
- doNotifyDataConnection(sender, reason, apnType, state);
- }
-
- private void doNotifyDataConnection(Phone sender, String reason, String apnType,
- Phone.DataState state) {
- // TODO
- // use apnType as the key to which connection we're talking about.
- // pass apnType back up to fetch particular for this one.
- TelephonyManager telephony = TelephonyManager.getDefault();
- LinkProperties linkProperties = null;
- LinkCapabilities linkCapabilities = null;
- if (state == Phone.DataState.CONNECTED) {
- linkProperties = sender.getLinkProperties(apnType);
- linkCapabilities = sender.getLinkCapabilities(apnType);
- }
- try {
- mRegistry.notifyDataConnection(
- convertDataState(state),
- sender.isDataConnectivityPossible(), reason,
- sender.getActiveApn(),
- apnType,
- linkProperties,
- linkCapabilities,
- ((telephony!=null) ? telephony.getNetworkType() :
- TelephonyManager.NETWORK_TYPE_UNKNOWN));
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
- try {
- mRegistry.notifyDataConnectionFailed(reason, apnType);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- public void notifyCellLocation(Phone sender) {
- Bundle data = new Bundle();
- sender.getCellLocation().fillInNotifierBundle(data);
- try {
- mRegistry.notifyCellLocation(data);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- private void log(String s) {
- Log.d(LOG_TAG, "[PhoneNotifier] " + s);
- }
-
- /**
- * Convert the {@link State} enum into the TelephonyManager.CALL_STATE_* constants
- * for the public API.
- */
- public static int convertCallState(Phone.State state) {
- switch (state) {
- case RINGING:
- return TelephonyManager.CALL_STATE_RINGING;
- case OFFHOOK:
- return TelephonyManager.CALL_STATE_OFFHOOK;
- default:
- return TelephonyManager.CALL_STATE_IDLE;
- }
- }
-
- /**
- * Convert the TelephonyManager.CALL_STATE_* constants into the {@link State} enum
- * for the public API.
- */
- public static Phone.State convertCallState(int state) {
- switch (state) {
- case TelephonyManager.CALL_STATE_RINGING:
- return Phone.State.RINGING;
- case TelephonyManager.CALL_STATE_OFFHOOK:
- return Phone.State.OFFHOOK;
- default:
- return Phone.State.IDLE;
- }
- }
-
- /**
- * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
- * for the public API.
- */
- public static int convertDataState(Phone.DataState state) {
- switch (state) {
- case CONNECTING:
- return TelephonyManager.DATA_CONNECTING;
- case CONNECTED:
- return TelephonyManager.DATA_CONNECTED;
- case SUSPENDED:
- return TelephonyManager.DATA_SUSPENDED;
- default:
- return TelephonyManager.DATA_DISCONNECTED;
- }
- }
-
- /**
- * Convert the TelephonyManager.DATA_* constants into {@link DataState} enum
- * for the public API.
- */
- public static Phone.DataState convertDataState(int state) {
- switch (state) {
- case TelephonyManager.DATA_CONNECTING:
- return Phone.DataState.CONNECTING;
- case TelephonyManager.DATA_CONNECTED:
- return Phone.DataState.CONNECTED;
- case TelephonyManager.DATA_SUSPENDED:
- return Phone.DataState.SUSPENDED;
- default:
- return Phone.DataState.DISCONNECTED;
- }
- }
-
- /**
- * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
- * for the public API.
- */
- public static int convertDataActivityState(Phone.DataActivityState state) {
- switch (state) {
- case DATAIN:
- return TelephonyManager.DATA_ACTIVITY_IN;
- case DATAOUT:
- return TelephonyManager.DATA_ACTIVITY_OUT;
- case DATAINANDOUT:
- return TelephonyManager.DATA_ACTIVITY_INOUT;
- case DORMANT:
- return TelephonyManager.DATA_ACTIVITY_DORMANT;
- default:
- return TelephonyManager.DATA_ACTIVITY_NONE;
- }
- }
-
- /**
- * Convert the TelephonyManager.DATA_* constants into the {@link DataState} enum
- * for the public API.
- */
- public static Phone.DataActivityState convertDataActivityState(int state) {
- switch (state) {
- case TelephonyManager.DATA_ACTIVITY_IN:
- return Phone.DataActivityState.DATAIN;
- case TelephonyManager.DATA_ACTIVITY_OUT:
- return Phone.DataActivityState.DATAOUT;
- case TelephonyManager.DATA_ACTIVITY_INOUT:
- return Phone.DataActivityState.DATAINANDOUT;
- case TelephonyManager.DATA_ACTIVITY_DORMANT:
- return Phone.DataActivityState.DORMANT;
- default:
- return Phone.DataActivityState.NONE;
- }
- }
-}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
old mode 100644
new mode 100755
index b71cf13..7d2013b
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -551,32 +551,53 @@
}
@Override
- protected void powerOffRadioSafely(){
- // clean data connection
+ protected void powerOffRadioSafely() {
DataConnectionTracker dcTracker = phone.mDataConnection;
Message msg = dcTracker.obtainMessage(DataConnectionTracker.EVENT_CLEAN_UP_CONNECTION);
- msg.arg1 = 1; // tearDown is true
msg.obj = CDMAPhone.REASON_RADIO_TURNED_OFF;
- dcTracker.sendMessage(msg);
- synchronized(this) {
- if (!mPendingRadioPowerOffAfterDataOff) {
- DataConnectionTracker.State currentState = dcTracker.getState();
- if (currentState != DataConnectionTracker.State.CONNECTED
- && currentState != DataConnectionTracker.State.DISCONNECTING
- && currentState != DataConnectionTracker.State.INITING) {
- if (DBG) log("Data disconnected, turn off radio right away.");
- hangupAndPowerOff();
- }
- else if (sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF, 30000)) {
- if (DBG) {
- log("Wait up to 30 sec for data to disconnect, then turn off radio.");
+ synchronized (this) {
+ if (networkType == ServiceState.RADIO_TECHNOLOGY_1xRTT) {
+ /*
+ * In 1x CDMA , during radio power off modem will disconnect the
+ * data call and sends the power down registration message along
+ * with the data call release message to the network
+ */
+
+ msg.arg1 = 0; // tearDown is false since modem does it anyway for 1X
+ dcTracker.sendMessage(msg);
+
+ Log.w(LOG_TAG, "Turn off the radio right away");
+ hangupAndPowerOff();
+ } else {
+ if (!mPendingRadioPowerOffAfterDataOff) {
+ DataConnectionTracker.State currentState = dcTracker.getState();
+ if (currentState != DataConnectionTracker.State.CONNECTED
+ && currentState != DataConnectionTracker.State.DISCONNECTING
+ && currentState != DataConnectionTracker.State.INITING) {
+
+ msg.arg1 = 0; // tearDown is false as it is not needed.
+ dcTracker.sendMessage(msg);
+
+ if (DBG)
+ log("Data disconnected, turn off radio right away.");
+ hangupAndPowerOff();
+ } else {
+ // clean data connection
+ msg.arg1 = 1; // tearDown is true
+ dcTracker.sendMessage(msg);
+
+ if (sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF, 30000)) {
+ if (DBG) {
+ log("Wait upto 30s for data to disconnect, then turn off radio.");
+ }
+ mPendingRadioPowerOffAfterDataOff = true;
+ } else {
+ Log.w(LOG_TAG, "Cannot send delayed Msg, turn off radio right away.");
+ hangupAndPowerOff();
+ }
}
- mPendingRadioPowerOffAfterDataOff = true;
- } else {
- Log.w(LOG_TAG, "Cannot send delayed Msg, turn off radio right away.");
- hangupAndPowerOff();
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
old mode 100755
new mode 100644
index e95b2f9..8accfd1
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -26,6 +26,7 @@
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.util.HexDump;
@@ -134,6 +135,7 @@
SmsMessage msg = new SmsMessage();
SmsEnvelope env = new SmsEnvelope();
CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
byte[] data;
byte count;
int countInt;
@@ -176,15 +178,24 @@
addr.origBytes = data;
- // ignore subaddress
- p.readInt(); //p_cur->sSubAddress.subaddressType
- p.readInt(); //p_cur->sSubAddress.odd
- count = p.readByte(); //p_cur->sSubAddress.number_of_digits
- //p_cur->sSubAddress.digits[digitCount] :
- for (int index=0; index < count; index++) {
- p.readByte();
+ subaddr.type = p.readInt(); // p_cur->sSubAddress.subaddressType
+ subaddr.odd = p.readByte(); // p_cur->sSubAddress.odd
+ count = p.readByte(); // p_cur->sSubAddress.number_of_digits
+
+ if (count < 0) {
+ count = 0;
}
+ // p_cur->sSubAddress.digits[digitCount] :
+
+ data = new byte[count];
+
+ for (int index = 0; index < count; ++index) {
+ data[index] = p.readByte();
+ }
+
+ subaddr.origBytes = data;
+
/* currently not supported by the modem-lib:
env.bearerReply
env.replySeqNo
@@ -206,6 +217,7 @@
// link the the filled objects to the SMS
env.origAddress = addr;
+ env.origSubaddress = subaddr;
msg.originatingAddress = addr;
msg.mEnvelope = env;
@@ -814,6 +826,8 @@
output.write(mEnvelope.teleService);
output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+ output.write(mEnvelope.origSubaddress.origBytes, 0,
+ mEnvelope.origSubaddress.origBytes.length);
return output.toByteArray();
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
new file mode 100644
index 0000000..f9cebf5
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsSubaddress.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project. All rights reserved.
+ * Copyright (C) 2010 Code Aurora Forum. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.cdma.sms;
+
+public class CdmaSmsSubaddress {
+ public int type;
+
+ public byte odd;
+
+ public byte[] origBytes;
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index a67327a..4a60231 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -17,6 +17,8 @@
package com.android.internal.telephony.cdma.sms;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+
public final class SmsEnvelope {
/**
* Message Types
@@ -74,17 +76,23 @@
/**
* The origination address identifies the originator of the SMS message.
- * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
public CdmaSmsAddress origAddress;
/**
* The destination address identifies the target of the SMS message.
- * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
*/
public CdmaSmsAddress destAddress;
/**
+ * The origination subaddress identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ */
+ public CdmaSmsSubaddress origSubaddress;
+
+ /**
* The 6-bit bearer reply parameter is used to request the return of a
* SMS Acknowledge Message.
* (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
index d93852c..189d97d 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
@@ -56,7 +56,7 @@
* 0, with the resulting code of 0x20.
*
* Note this mapping is also equivalent to that used by both the
- * IS5 and the IS-91 encodings. For the former this is defined
+ * IA5 and the IS-91 encodings. For the former this is defined
* using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B,
* Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
* and hence only maps entries up to the '_' character.
diff --git a/telephony/java/com/android/internal/telephony/sip/CallFailCause.java b/telephony/java/com/android/internal/telephony/sip/CallFailCause.java
deleted file mode 100644
index 58fb408..0000000
--- a/telephony/java/com/android/internal/telephony/sip/CallFailCause.java
+++ /dev/null
@@ -1,50 +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 com.android.internal.telephony.sip;
-
-/**
- * Call fail causes from TS 24.008 .
- * These are mostly the cause codes we need to distinguish for the UI.
- * See 22.001 Annex F.4 for mapping of cause codes to local tones.
- *
- * {@hide}
- *
- */
-public interface CallFailCause {
- static final int NORMAL_CLEARING = 16;
- // Busy Tone
- static final int USER_BUSY = 17;
-
- // No Tone
- static final int NUMBER_CHANGED = 22;
- static final int STATUS_ENQUIRY = 30;
- static final int NORMAL_UNSPECIFIED = 31;
-
- // Congestion Tone
- static final int NO_CIRCUIT_AVAIL = 34;
- static final int TEMPORARY_FAILURE = 41;
- static final int SWITCHING_CONGESTION = 42;
- static final int CHANNEL_NOT_AVAIL = 44;
- static final int QOS_NOT_AVAIL = 49;
- static final int BEARER_NOT_AVAIL = 58;
-
- // others
- static final int ACM_LIMIT_EXCEEDED = 68;
- static final int CALL_BARRED = 240;
- static final int FDN_BLOCKED = 241;
- static final int ERROR_UNSPECIFIED = 0xffff;
-}
diff --git a/telephony/java/com/android/internal/telephony/sip/CallProxy.java b/telephony/java/com/android/internal/telephony/sip/CallProxy.java
deleted file mode 100644
index fad9663..0000000
--- a/telephony/java/com/android/internal/telephony/sip/CallProxy.java
+++ /dev/null
@@ -1,114 +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 com.android.internal.telephony.sip;
-
-import com.android.internal.telephony.*;
-import java.util.List;
-
-// TODO: remove this class after integrating with CallManager
-class CallProxy extends Call {
- private Call mTarget;
-
- void setTarget(Call target) {
- mTarget = target;
- }
-
- @Override
- public List<Connection> getConnections() {
- return mTarget.getConnections();
- }
-
- @Override
- public Phone getPhone() {
- return mTarget.getPhone();
- }
-
- @Override
- public boolean isMultiparty() {
- return mTarget.isMultiparty();
- }
-
- @Override
- public void hangup() throws CallStateException {
- mTarget.hangup();
- }
-
- @Override
- public boolean hasConnection(Connection c) {
- return mTarget.hasConnection(c);
- }
-
- @Override
- public boolean hasConnections() {
- return mTarget.hasConnections();
- }
-
- @Override
- public State getState() {
- return mTarget.getState();
- }
-
- @Override
- public boolean isIdle() {
- return mTarget.isIdle();
- }
-
- @Override
- public Connection getEarliestConnection() {
- return mTarget.getEarliestConnection();
- }
-
- @Override
- public long getEarliestCreateTime() {
- return mTarget.getEarliestCreateTime();
- }
-
- @Override
- public long getEarliestConnectTime() {
- return mTarget.getEarliestConnectTime();
- }
-
- @Override
- public boolean isDialingOrAlerting() {
- return mTarget.isDialingOrAlerting();
- }
-
- @Override
- public boolean isRinging() {
- return mTarget.isRinging();
- }
-
- @Override
- public Connection getLatestConnection() {
- return mTarget.getLatestConnection();
- }
-
- @Override
- public boolean isGeneric() {
- return mTarget.isGeneric();
- }
-
- @Override
- public void setGeneric(boolean generic) {
- mTarget.setGeneric(generic);
- }
-
- @Override
- public void hangupIfAlive() {
- mTarget.hangupIfAlive();
- }
-}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
index 7e407b2..9050a43 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
@@ -19,7 +19,6 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.DriverCall;
import com.android.internal.telephony.Phone;
import android.net.sip.SipManager;
@@ -28,15 +27,10 @@
import java.util.List;
abstract class SipCallBase extends Call {
- private static final int MAX_CONNECTIONS_PER_CALL = 5;
-
protected List<Connection> connections = new ArrayList<Connection>();
protected abstract void setState(State newState);
- public void dispose() {
- }
-
public List<Connection> getConnections() {
// FIXME should return Collections.unmodifiableList();
return connections;
@@ -47,48 +41,7 @@
}
public String toString() {
- return state.toString();
- }
-
- /**
- * Called by SipConnection when it has disconnected
- */
- void connectionDisconnected(Connection conn) {
- if (state != State.DISCONNECTED) {
- /* If only disconnected connections remain, we are disconnected*/
-
- boolean hasOnlyDisconnectedConnections = true;
-
- for (int i = 0, s = connections.size() ; i < s; i ++) {
- if (connections.get(i).getState()
- != State.DISCONNECTED
- ) {
- hasOnlyDisconnectedConnections = false;
- break;
- }
- }
-
- if (hasOnlyDisconnectedConnections) {
- state = State.DISCONNECTED;
- }
- }
- }
-
-
- /*package*/ void detach(Connection conn) {
- connections.remove(conn);
-
- if (connections.size() == 0) {
- state = State.IDLE;
- }
- }
-
- /**
- * @return true if there's no space in this call for additional
- * connections to be added via "conference"
- */
- /*package*/ boolean isFull() {
- return connections.size() == MAX_CONNECTIONS_PER_CALL;
+ return state.toString() + ":" + super.toString();
}
void clearDisconnected() {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 33c89f8..ed578c8 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -330,7 +330,6 @@
public void setGsmBroadcastActivation(boolean activate, Message response) {
}
-
// ***** Methods for CDMA support
public void getDeviceIdentity(Message response) {
}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
index f5d84eb..c139a64 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -16,29 +16,17 @@
package com.android.internal.telephony.sip;
-import android.content.Context;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.UUSInfo;
+
import android.net.sip.SipAudioCall;
-import android.os.Message;
-import android.os.Registrant;
import android.os.SystemClock;
import android.util.Log;
import android.telephony.PhoneNumberUtils;
-import android.telephony.ServiceState;
-
-import com.android.internal.telephony.*;
abstract class SipConnectionBase extends Connection {
- //***** Event Constants
- private static final int EVENT_DTMF_DONE = 1;
- private static final int EVENT_PAUSE_DONE = 2;
- private static final int EVENT_NEXT_POST_DIAL = 3;
- private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
-
- //***** Constants
- private static final int PAUSE_DELAY_FIRST_MILLIS = 100;
- private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
- private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
-
private static final String LOG_TAG = "SIP_CONN";
private SipAudioCall mSipAudioCall;
@@ -47,10 +35,6 @@
private String postDialString; // outgoing calls only
private int nextPostDialChar; // index into postDialString
private boolean isIncoming;
- private boolean disconnected;
-
- int index; // index in SipCallTracker.connections[], -1 if unassigned
- // The Sip index is 1 + this
/*
* These time/timespan values are based on System.currentTimeMillis(),
@@ -167,55 +151,6 @@
protected abstract Phone getPhone();
- DisconnectCause disconnectCauseFromCode(int causeCode) {
- /**
- * See 22.001 Annex F.4 for mapping of cause codes
- * to local tones
- */
-
- switch (causeCode) {
- case CallFailCause.USER_BUSY:
- return DisconnectCause.BUSY;
-
- case CallFailCause.NO_CIRCUIT_AVAIL:
- case CallFailCause.TEMPORARY_FAILURE:
- case CallFailCause.SWITCHING_CONGESTION:
- case CallFailCause.CHANNEL_NOT_AVAIL:
- case CallFailCause.QOS_NOT_AVAIL:
- case CallFailCause.BEARER_NOT_AVAIL:
- return DisconnectCause.CONGESTION;
-
- case CallFailCause.ACM_LIMIT_EXCEEDED:
- return DisconnectCause.LIMIT_EXCEEDED;
-
- case CallFailCause.CALL_BARRED:
- return DisconnectCause.CALL_BARRED;
-
- case CallFailCause.FDN_BLOCKED:
- return DisconnectCause.FDN_BLOCKED;
-
- case CallFailCause.ERROR_UNSPECIFIED:
- case CallFailCause.NORMAL_CLEARING:
- default:
- Phone phone = getPhone();
- int serviceState = phone.getServiceState().getState();
- if (serviceState == ServiceState.STATE_POWER_OFF) {
- return DisconnectCause.POWER_OFF;
- } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
- || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) {
- return DisconnectCause.OUT_OF_SERVICE;
- } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
- return DisconnectCause.ERROR_UNSPECIFIED;
- } else if (causeCode == CallFailCause.NORMAL_CLEARING) {
- return DisconnectCause.NORMAL;
- } else {
- // If nothing else matches, report unknown call drop reason
- // to app, not NORMAL call end.
- return DisconnectCause.ERROR_UNSPECIFIED;
- }
- }
- }
-
@Override
public String getRemainingPostDialString() {
if (postDialState == PostDialState.CANCELLED
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index e0eac74..55e3002 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -16,10 +16,7 @@
package com.android.internal.telephony.sip;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.net.Uri;
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.sip.SipAudioCall;
@@ -29,42 +26,20 @@
import android.net.sip.SipProfile;
import android.net.sip.SipSession;
import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.Registrant;
-import android.os.RegistrantList;
-import android.os.SystemProperties;
-import android.preference.PreferenceManager;
-import android.provider.Telephony;
-import android.telephony.CellLocation;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.DataConnection;
-import com.android.internal.telephony.IccCard;
-import com.android.internal.telephony.IccFileHandler;
-import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.IccSmsInterfaceManager;
-import com.android.internal.telephony.MmiCode;
import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneNotifier;
-import com.android.internal.telephony.PhoneProxy;
-import com.android.internal.telephony.PhoneSubInfo;
-import com.android.internal.telephony.TelephonyProperties;
-import java.io.IOException;
import java.text.ParseException;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -72,7 +47,7 @@
*/
public class SipPhone extends SipPhoneBase {
private static final String LOG_TAG = "SipPhone";
- private static final boolean LOCAL_DEBUG = true;
+ private static final boolean DEBUG = true;
private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
@@ -88,17 +63,12 @@
SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
super(context, notifier);
- Log.v(LOG_TAG, " +++++++++++++++++++++ new SipPhone: " + profile.getUriString());
+ if (DEBUG) Log.d(LOG_TAG, "new SipPhone: " + profile.getUriString());
ringingCall = new SipCall();
foregroundCall = new SipCall();
backgroundCall = new SipCall();
mProfile = profile;
mSipManager = SipManager.newInstance(context);
-
- // FIXME: what's this for SIP?
- //Change the system property
- //SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
- // new Integer(Phone.PHONE_TYPE_GSM).toString());
}
@Override
@@ -133,14 +103,27 @@
return false;
}
- SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
- Log.v(LOG_TAG, " ++++++ taking call from: "
- + sipAudioCall.getPeerProfile().getUriString());
- String localUri = sipAudioCall.getLocalProfile().getUriString();
- if (localUri.equals(mProfile.getUriString())) {
- boolean makeCallWait = foregroundCall.getState().isAlive();
- ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
- return true;
+ try {
+ SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+ if (DEBUG) Log.d(LOG_TAG, "+++ taking call from: "
+ + sipAudioCall.getPeerProfile().getUriString());
+ String localUri = sipAudioCall.getLocalProfile().getUriString();
+ if (localUri.equals(mProfile.getUriString())) {
+ boolean makeCallWait = foregroundCall.getState().isAlive();
+ ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+ if (sipAudioCall.getState()
+ != SipSession.State.INCOMING_CALL) {
+ // Peer cancelled the call!
+ if (DEBUG) Log.d(LOG_TAG, " call cancelled !!");
+ ringingCall.reset();
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ // Peer may cancel the call at any time during the time we hook
+ // up ringingCall with sipAudioCall. Clean up ringingCall when
+ // that happens.
+ ringingCall.reset();
}
return false;
}
@@ -148,13 +131,9 @@
public void acceptCall() throws CallStateException {
synchronized (SipPhone.class) {
- // FIXME if SWITCH fails, should retry with ANSWER
- // in case the active/holding call disappeared and this
- // is no longer call waiting
-
if ((ringingCall.getState() == Call.State.INCOMING) ||
(ringingCall.getState() == Call.State.WAITING)) {
- Log.v(LOG_TAG, "acceptCall");
+ if (DEBUG) Log.d(LOG_TAG, "acceptCall");
// Always unmute when answering a new call
setMute(false);
ringingCall.acceptCall();
@@ -167,7 +146,7 @@
public void rejectCall() throws CallStateException {
synchronized (SipPhone.class) {
if (ringingCall.getState().isRinging()) {
- Log.v(LOG_TAG, "rejectCall");
+ if (DEBUG) Log.d(LOG_TAG, "rejectCall");
ringingCall.rejectCall();
} else {
throw new CallStateException("phone not ringing");
@@ -183,10 +162,6 @@
private Connection dialInternal(String dialString)
throws CallStateException {
- // TODO: parse SIP URL?
- // Need to make sure dialString gets parsed properly
- //String newDialString = PhoneNumberUtils.stripSeparators(dialString);
- //return mCT.dial(newDialString);
clearDisconnected();
if (!canDial()) {
@@ -201,7 +176,6 @@
}
setMute(false);
- //cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
try {
Connection c = foregroundCall.dial(dialString);
return c;
@@ -212,7 +186,7 @@
}
public void switchHoldingAndActive() throws CallStateException {
- Log.v(LOG_TAG, " ~~~~~~ switch fg and bg");
+ if (DEBUG) Log.d(LOG_TAG, " ~~~~~~ switch fg and bg");
synchronized (SipPhone.class) {
foregroundCall.switchWith(backgroundCall);
if (backgroundCall.getState().isAlive()) backgroundCall.hold();
@@ -317,6 +291,21 @@
Log.e(LOG_TAG, "call waiting not supported");
}
+ @Override
+ public void setEchoSuppressionEnabled(boolean enabled) {
+ synchronized (SipPhone.class) {
+ AudioGroup audioGroup = foregroundCall.getAudioGroup();
+ if (audioGroup == null) return;
+ int mode = audioGroup.getMode();
+ audioGroup.setMode(enabled
+ ? AudioGroup.MODE_ECHO_SUPPRESSION
+ : AudioGroup.MODE_NORMAL);
+ if (DEBUG) Log.d(LOG_TAG, String.format(
+ "audioGroup mode change: %d --> %d", mode,
+ audioGroup.getMode()));
+ }
+ }
+
public void setMute(boolean muted) {
synchronized (SipPhone.class) {
foregroundCall.setMute(muted);
@@ -361,6 +350,11 @@
}
private class SipCall extends SipCallBase {
+ void reset() {
+ connections.clear();
+ setState(Call.State.IDLE);
+ }
+
void switchWith(SipCall that) {
synchronized (SipPhone.class) {
SipCall tmp = new SipCall();
@@ -398,9 +392,11 @@
CallerInfo info = new CallerInfo();
info.name = name;
info.phoneNumber = number;
- Log.v(LOG_TAG, "create caller info from scratch:");
- Log.v(LOG_TAG, " name: " + info.name);
- Log.v(LOG_TAG, " numb: " + info.phoneNumber);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "create caller info from scratch:");
+ Log.d(LOG_TAG, " name: " + info.name);
+ Log.d(LOG_TAG, " numb: " + info.phoneNumber);
+ }
return info;
}
@@ -408,10 +404,12 @@
private CallerInfo findCallerInfo(String number) {
CallerInfo info = CallerInfo.getCallerInfo(mContext, number);
if ((info == null) || (info.name == null)) return null;
- Log.v(LOG_TAG, "got caller info from contact:");
- Log.v(LOG_TAG, " name: " + info.name);
- Log.v(LOG_TAG, " numb: " + info.phoneNumber);
- Log.v(LOG_TAG, " pres: " + info.numberPresentation);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "got caller info from contact:");
+ Log.d(LOG_TAG, " name: " + info.name);
+ Log.d(LOG_TAG, " numb: " + info.phoneNumber);
+ Log.d(LOG_TAG, " pres: " + info.numberPresentation);
+ }
return info;
}
@@ -445,8 +443,9 @@
public void hangup() throws CallStateException {
synchronized (SipPhone.class) {
if (state.isAlive()) {
- Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this
- + " on phone " + getPhone());
+ if (DEBUG) Log.d(LOG_TAG, "hang up call: " + getState()
+ + ": " + this + " on phone " + getPhone());
+ setState(State.DISCONNECTING);
CallStateException excp = null;
for (Connection c : connections) {
try {
@@ -456,10 +455,9 @@
}
}
if (excp != null) throw excp;
- setState(State.DISCONNECTING);
} else {
- Log.d(LOG_TAG, "hang up dead call: " + getState() + ": "
- + this + " on phone " + getPhone());
+ if (DEBUG) Log.d(LOG_TAG, "hang up dead call: " + getState()
+ + ": " + this + " on phone " + getPhone());
}
}
}
@@ -571,7 +569,7 @@
@Override
protected void setState(State newState) {
if (state != newState) {
- Log.v(LOG_TAG, "++******++ call state changed: " + state
+ if (DEBUG) Log.v(LOG_TAG, "+***+ call state changed: " + state
+ " --> " + newState + ": " + this + ": on phone "
+ getPhone() + " " + connections.size());
@@ -598,10 +596,11 @@
// set state to DISCONNECTED only when all conns are disconnected
if (state != State.DISCONNECTED) {
boolean allConnectionsDisconnected = true;
- Log.v(LOG_TAG, "---check if all connections are disconnected: "
+ if (DEBUG) Log.d(LOG_TAG, "---check connections: "
+ connections.size());
for (Connection c : connections) {
- Log.v(LOG_TAG, " state=" + c.getState() + ": " + c);
+ if (DEBUG) Log.d(LOG_TAG, " state=" + c.getState() + ": "
+ + c);
if (c.getState() != State.DISCONNECTED) {
allConnectionsDisconnected = false;
break;
@@ -633,13 +632,20 @@
}
synchronized (SipPhone.class) {
setState(Call.State.DISCONNECTED);
- mSipAudioCall.close();
- mOwner.onConnectionEnded(SipConnection.this);
- Log.v(LOG_TAG, "-------- connection ended: "
- + mPeer.getUriString() + ": "
- + mSipAudioCall.getState() + ", cause: "
- + getDisconnectCause() + ", on phone "
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ mSipAudioCall = null;
+ String sessionState = (sipAudioCall == null)
+ ? ""
+ : (sipAudioCall.getState() + ", ");
+ if (DEBUG) Log.d(LOG_TAG, "--- connection ended: "
+ + mPeer.getUriString() + ": " + sessionState
+ + "cause: " + getDisconnectCause() + ", on phone "
+ getPhone());
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.close();
+ }
+ mOwner.onConnectionEnded(SipConnection.this);
}
}
@@ -678,7 +684,7 @@
setState(newState);
}
mOwner.onConnectionStateChanged(SipConnection.this);
- Log.v(LOG_TAG, "++******++ connection state changed: "
+ if (DEBUG) Log.v(LOG_TAG, "+***+ connection state changed: "
+ mPeer.getUriString() + ": " + mState
+ " on phone " + getPhone());
}
@@ -686,7 +692,7 @@
@Override
protected void onError(DisconnectCause cause) {
- Log.w(LOG_TAG, "SIP error: " + cause);
+ if (DEBUG) Log.d(LOG_TAG, "SIP error: " + cause);
if (mSipAudioCall.isInCall()
&& (cause != DisconnectCause.LOST_SIGNAL)) {
// Don't end the call when in a call.
@@ -791,16 +797,20 @@
@Override
public void hangup() throws CallStateException {
synchronized (SipPhone.class) {
- Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
- + mState + ": on phone " + getPhone().getPhoneName());
+ if (DEBUG) Log.d(LOG_TAG, "hangup conn: " + mPeer.getUriString()
+ + ": " + mState + ": on phone "
+ + getPhone().getPhoneName());
+ if (!mState.isAlive()) return;
try {
- if (mState.isAlive()) {
- if (mSipAudioCall != null) mSipAudioCall.endCall();
- setState(Call.State.DISCONNECTING);
- setDisconnectCause(DisconnectCause.LOCAL);
+ SipAudioCall sipAudioCall = mSipAudioCall;
+ if (sipAudioCall != null) {
+ sipAudioCall.setListener(null);
+ sipAudioCall.endCall();
}
} catch (SipException e) {
throw new CallStateException("hangup(): " + e);
+ } finally {
+ mAdapter.onCallEnded(DisconnectCause.LOCAL);
}
}
}
@@ -814,8 +824,9 @@
"cannot put conn back to a call in non-idle state: "
+ call.getState());
}
- Log.v(LOG_TAG, "separate conn: " + mPeer.getUriString()
- + " from " + mOwner + " back to " + call);
+ if (DEBUG) Log.d(LOG_TAG, "separate conn: "
+ + mPeer.getUriString() + " from " + mOwner + " back to "
+ + call);
AudioGroup audioGroup = call.getAudioGroup(); // may be null
call.add(this);
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index 5f26af4..afd4d0c 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -16,43 +16,21 @@
package com.android.internal.telephony.sip;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
import android.net.LinkProperties;
-import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemProperties;
-import android.preference.PreferenceManager;
-import android.provider.Telephony;
import android.telephony.CellLocation;
-import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
-import android.text.TextUtils;
import android.util.Log;
-import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
-import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
-import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
-import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
-import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
-import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION;
-
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.DataConnection;
import com.android.internal.telephony.IccCard;
@@ -63,38 +41,21 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneNotifier;
-import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.PhoneSubInfo;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.UUSInfo;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
abstract class SipPhoneBase extends PhoneBase {
- // NOTE that LOG_TAG here is "Sip", which means that log messages
- // from this file will go into the radio log rather than the main
- // log. (Use "adb logcat -b radio" to see them.)
- static final String LOG_TAG = "SipPhone";
- private static final boolean LOCAL_DEBUG = true;
+ private static final String LOG_TAG = "SipPhone";
- //SipCallTracker mCT;
- PhoneSubInfo mSubInfo;
-
- Registrant mPostDialHandler;
-
- final RegistrantList mRingbackRegistrants = new RegistrantList();
-
+ private RegistrantList mRingbackRegistrants = new RegistrantList();
private State state = State.IDLE;
public SipPhoneBase(Context context, PhoneNotifier notifier) {
super(notifier, context, new SipCommandInterface(context), false);
-
- // FIXME: what's this for SIP?
- //Change the system property
- //SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
- // new Integer(Phone.PHONE_TYPE_GSM).toString());
}
public abstract Call getForegroundCall();
@@ -149,15 +110,6 @@
mRingbackRegistrants.notifyRegistrants(result);
}
- public void dispose() {
- mIsTheCurrentActivePhone = false;
- mSubInfo.dispose();
- }
-
- public void removeReferences() {
- mSubInfo = null;
- }
-
public ServiceState getServiceState() {
// FIXME: we may need to provide this when data connectivity is lost
// or when server is down
@@ -167,7 +119,7 @@
}
public CellLocation getCellLocation() {
- return null; //mSST.cellLoc;
+ return null;
}
public State getState() {
@@ -223,7 +175,6 @@
}
void notifyNewRingingConnection(Connection c) {
- /* we'd love it if this was package-scoped*/
super.notifyNewRingingConnectionP(c);
}
@@ -351,53 +302,12 @@
onComplete.sendToTarget();
}
- private boolean isValidCommandInterfaceCFReason(int commandInterfaceCFReason) {
- switch (commandInterfaceCFReason) {
- case CF_REASON_UNCONDITIONAL:
- case CF_REASON_BUSY:
- case CF_REASON_NO_REPLY:
- case CF_REASON_NOT_REACHABLE:
- case CF_REASON_ALL:
- case CF_REASON_ALL_CONDITIONAL:
- return true;
- default:
- return false;
- }
- }
-
- private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
- switch (commandInterfaceCFAction) {
- case CF_ACTION_DISABLE:
- case CF_ACTION_ENABLE:
- case CF_ACTION_REGISTRATION:
- case CF_ACTION_ERASURE:
- return true;
- default:
- return false;
- }
- }
-
- protected boolean isCfEnable(int action) {
- return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
- }
-
public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
- if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
- // FIXME: what to reply?
- AsyncResult.forMessage(onComplete, null, null);
- onComplete.sendToTarget();
- }
}
public void setCallForwardingOption(int commandInterfaceCFAction,
int commandInterfaceCFReason, String dialingNumber,
int timerSeconds, Message onComplete) {
- if (isValidCommandInterfaceCFAction(commandInterfaceCFAction)
- && isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
- // FIXME: what to reply?
- AsyncResult.forMessage(onComplete, null, null);
- onComplete.sendToTarget();
- }
}
public void getOutgoingCallerIdDisplay(Message onComplete) {
@@ -414,13 +324,11 @@
}
public void getCallWaiting(Message onComplete) {
- // FIXME: what to reply?
AsyncResult.forMessage(onComplete, null, null);
onComplete.sendToTarget();
}
public void setCallWaiting(boolean enable, Message onComplete) {
- // FIXME: what to reply?
Log.e(LOG_TAG, "call waiting not supported");
}
@@ -433,29 +341,23 @@
}
public void getAvailableNetworks(Message response) {
- // FIXME: what to reply?
}
public void setNetworkSelectionModeAutomatic(Message response) {
- // FIXME: what to reply?
}
public void selectNetworkManually(
com.android.internal.telephony.gsm.NetworkInfo network,
Message response) {
- // FIXME: what to reply?
}
public void getNeighboringCids(Message response) {
- // FIXME: what to reply?
}
public void setOnPostDialCharacter(Handler h, int what, Object obj) {
- mPostDialHandler = new Registrant(h, what, obj);
}
public void getDataCallList(Message response) {
- // FIXME: what to reply?
}
public List<DataConnection> getCurrentDataConnectionList () {
@@ -495,27 +397,20 @@
}
public void saveClirSetting(int commandInterfaceCLIRMode) {
- // FIXME: what's this for SIP?
}
- /**
- * Retrieves the PhoneSubInfo of the SipPhone
- */
public PhoneSubInfo getPhoneSubInfo(){
- return mSubInfo;
+ return null;
}
- /** {@inheritDoc} */
public IccSmsInterfaceManager getIccSmsInterfaceManager(){
return null;
}
- /** {@inheritDoc} */
public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
return null;
}
- /** {@inheritDoc} */
public IccFileHandler getIccFileHandler(){
return null;
}
@@ -555,9 +450,9 @@
} else {
state = State.OFFHOOK;
}
- Log.e(LOG_TAG, " ^^^^^^ new phone state: " + state);
if (state != oldState) {
+ Log.d(LOG_TAG, " ^^^ new phone state: " + state);
notifyPhoneStateChanged();
}
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index af71a0f..df3c3ed 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -40,6 +40,15 @@
</activity>
<activity
+ android:name="LabelsActivity"
+ android:label="_Labels">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="ResizeActivity"
android:label="_Resize"
android:windowSoftInputMode="adjustResize">
@@ -164,6 +173,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="GradientsActivity"
+ android:label="_Gradients">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
<activity
android:name="ShadersActivity"
diff --git a/tests/HwAccelerationTest/res/layout/labels.xml b/tests/HwAccelerationTest/res/layout/labels.xml
new file mode 100644
index 0000000..695a2cc
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/labels.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="200dip"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:text="This is a long text view for ellipsizing" />
+
+ <TextView
+ android:layout_width="200dip"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="center_horizontal"
+ android:text="This is a very long text view for ellipsizing" />
+
+ <TextView
+ android:layout_width="200dip"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="right"
+ android:text="This is a very long text view for ellipsizing" />
+
+</LinearLayout>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
new file mode 100644
index 0000000..b70f3a9
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -0,0 +1,108 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class GradientsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(new ShadersView(this));
+ }
+
+ static class ShadersView extends View {
+ private final Paint mPaint;
+ private final float mDrawWidth;
+ private final float mDrawHeight;
+ private final LinearGradient mGradient;
+ private final Matrix mMatrix;
+
+ ShadersView(Context c) {
+ super(c);
+
+ mDrawWidth = 200;
+ mDrawHeight = 200;
+
+ mGradient = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+ mMatrix = new Matrix();
+
+ mPaint = new Paint();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRGB(255, 255, 255);
+
+ // Gradients
+ canvas.save();
+ float top = 40.0f;
+ float right = 40.0f + mDrawWidth;
+ float left = 40.0f;
+ float bottom = 40.0f + mDrawHeight;
+
+ mPaint.setShader(mGradient);
+
+ mMatrix.setScale(1, mDrawWidth);
+ mMatrix.postRotate(90);
+ mMatrix.postTranslate(right, top);
+ mGradient.setLocalMatrix(mMatrix);
+ canvas.drawRect(right - mDrawWidth, top, right, top + mDrawHeight, mPaint);
+
+ top += 40.0f + mDrawHeight;
+ bottom += 40.0f + mDrawHeight;
+
+ mMatrix.setScale(1, mDrawHeight);
+ mMatrix.postTranslate(left, top);
+ mGradient.setLocalMatrix(mMatrix);
+ canvas.drawRect(left, top, right, top + mDrawHeight, mPaint);
+
+ left += 40.0f + mDrawWidth;
+ right += 40.0f + mDrawWidth;
+ top -= 40.0f + mDrawHeight;
+ bottom -= 40.0f + mDrawHeight;
+
+ mMatrix.setScale(1, mDrawHeight);
+ mMatrix.postRotate(180);
+ mMatrix.postTranslate(left, bottom);
+ mGradient.setLocalMatrix(mMatrix);
+ canvas.drawRect(left, bottom - mDrawHeight, right, bottom, mPaint);
+
+ top += 40.0f + mDrawHeight;
+ bottom += 40.0f + mDrawHeight;
+
+ mMatrix.setScale(1, mDrawWidth);
+ mMatrix.postRotate(-90);
+ mMatrix.postTranslate(left, top);
+ mGradient.setLocalMatrix(mMatrix);
+ canvas.drawRect(left, top, left + mDrawWidth, bottom, mPaint);
+
+ canvas.restore();
+ }
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.java
new file mode 100644
index 0000000..bae0500
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LabelsActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LabelsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.labels);
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
index 9c8e7ec..2db1071 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ShadersActivity.java
@@ -77,8 +77,13 @@
m2.setScale(0.5f, 0.5f);
mScaledShader.setLocalMatrix(m2);
- mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f,
+ mHorGradient = new LinearGradient(0.0f, 0.0f, 1.0f, 0.0f,
Color.RED, Color.GREEN, Shader.TileMode.CLAMP);
+ Matrix m3 = new Matrix();
+ m3.setScale(mDrawHeight, 1.0f);
+ m3.postRotate(-90.0f);
+ m3.postTranslate(0.0f, mDrawHeight);
+ mHorGradient.setLocalMatrix(m3);
mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 1.5f, mDrawHeight,
Color.BLUE, Color.MAGENTA, Shader.TileMode.CLAMP);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
index f47b00f..763169a 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
@@ -87,6 +87,7 @@
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);
list.setCacheColorHint(0);
+ list.setVerticalFadingEdgeEnabled(true);
registerForContextMenu(list);
}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 2d7ee13..e30cf4a 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -727,7 +727,7 @@
}
},
- new Test("Persistent with numbers 222") {
+ new Test("Persistent with numbers 22") {
public void run() {
mNM.notify(1, notificationWithNumbers(22));
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 90a6256..1d6b18d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -782,7 +782,6 @@
const String16 translatable16("translatable");
const String16 formatted16("formatted");
const String16 false16("false");
- const String16 product16("product");
const String16 myPackage(assets->getPackage());
@@ -830,7 +829,6 @@
bool curIsStyled = false;
bool curIsPseudolocalizable = false;
bool curIsFormatted = fileIsTranslatable;
- String16 curProduct;
bool localHasErrors = false;
if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
@@ -1228,8 +1226,6 @@
translatable.setTo(block.getAttributeStringValue(i, &length));
} else if (strcmp16(attr, formatted16.string()) == 0) {
formatted.setTo(block.getAttributeStringValue(i, &length));
- } else if (strcmp16(attr, product16.string()) == 0) {
- curProduct.setTo(block.getAttributeStringValue(i, &length));
}
}
@@ -1356,6 +1352,12 @@
hasErrors = localHasErrors = true;
}
+ String16 product;
+ identIdx = block.indexOfAttribute(NULL, "product");
+ if (identIdx >= 0) {
+ product = String16(block.getAttributeStringValue(identIdx, &len));
+ }
+
String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
if (curIsBag) {
@@ -1447,7 +1449,7 @@
err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
ident, parentIdent, itemIdent, curFormat, curIsFormatted,
- curProduct, false, overwrite, outTable);
+ product, false, overwrite, outTable);
if (err == NO_ERROR) {
if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize()) {
@@ -1456,7 +1458,7 @@
block.setPosition(parserPosition);
err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
curType, ident, parentIdent, itemIdent, curFormat,
- curIsFormatted, curProduct, true, overwrite, outTable);
+ curIsFormatted, product, true, overwrite, outTable);
#endif
}
}
@@ -1480,7 +1482,7 @@
err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
*curTag, curIsStyled, curFormat, curIsFormatted,
- curProduct, false, overwrite, outTable);
+ product, false, overwrite, outTable);
if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
hasErrors = localHasErrors = true;
@@ -1492,7 +1494,7 @@
block.setPosition(parserPosition);
err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
ident, *curTag, curIsStyled, curFormat,
- curIsFormatted, curProduct,
+ curIsFormatted, product,
true, overwrite, outTable);
if (err != NO_ERROR) {
hasErrors = localHasErrors = true;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index f91f601..0553f5e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -30,6 +30,8 @@
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -46,6 +48,7 @@
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.BridgeInflater;
+import android.view.DragEvent;
import android.view.InputChannel;
import android.view.IWindow;
import android.view.IWindowSession;
@@ -1073,6 +1076,33 @@
}
@SuppressWarnings("unused")
+ public IBinder prepareDrag(IWindow window, boolean localOnly,
+ int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+ throws RemoteException {
+ // pass for now
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientEntered(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
+ public void dragRecipientExited(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @SuppressWarnings("unused")
public void setWallpaperPosition(IBinder window, float x, float y,
float xStep, float yStep) {
// pass for now.
@@ -1170,6 +1200,11 @@
// pass for now.
}
+ @SuppressWarnings("unused")
+ public void dispatchDragEvent(DragEvent event) {
+ // pass for now.
+ }
+
public IBinder asBinder() {
// pass for now.
return null;
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@
bridge will provide its own implementation.
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+ http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+ http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+ http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+ http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
--
end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..590923f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
-import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -60,38 +60,55 @@
* old-FQCN to rename and they get erased as they get renamed. At the end, classes still
* left here are not in the code base anymore and thus were not renamed. */
private HashSet<String> mClassesNotRenamed;
- /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+ /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
private HashMap<String, Set<String>> mDeleteReturns;
+ /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+ private final HashMap<String, Set<String>> mDelegateMethods;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
- *
+ *
* @param log Output logger.
* @param osDestJar The path of the destination JAR to create.
- * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
- * @param stubMethods The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- * @param deleteReturns List of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
+ * @param createInfo Creation parameters. Must not be null.
*/
- public AsmGenerator(Log log, String osDestJar,
- Class<?>[] injectClasses,
- String[] stubMethods,
- String[] renameClasses, String[] deleteReturns) {
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
- mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
- mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
- new HashSet<String>();
+ mInjectClasses = createInfo.getInjectedClasses();
+ mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+ // Create the map/set of methods to change to delegates
+ mDelegateMethods = new HashMap<String, Set<String>>();
+ for (String signature : createInfo.getDelegateMethods()) {
+ int pos = signature.indexOf('#');
+ if (pos <= 0 || pos >= signature.length() - 1) {
+ continue;
+ }
+ String className = binaryToInternalClassName(signature.substring(0, pos));
+ String methodName = signature.substring(pos + 1);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(methodName);
+ }
+ for (String className : createInfo.getDelegateClassNatives()) {
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(DelegateClassAdapter.ALL_NATIVES);
+ }
// Create the map of classes to rename.
mRenameClasses = new HashMap<String, String>();
mClassesNotRenamed = new HashSet<String>();
- int n = renameClasses == null ? 0 : renameClasses.length;
+ String[] renameClasses = createInfo.getRenamedClasses();
+ int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
assert i + 1 < n;
// The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +117,37 @@
mRenameClasses.put(oldFqcn, newFqcn);
mClassesNotRenamed.add(oldFqcn);
}
-
+
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
- if (deleteReturns != null) {
- Set<String> returnTypes = null;
- String renamedClass = null;
- for (String className : deleteReturns) {
- // if we reach the end of a section, add it to the main map
- if (className == null) {
- if (returnTypes != null) {
- mDeleteReturns.put(renamedClass, returnTypes);
- }
-
- renamedClass = null;
- continue;
+ String[] deleteReturns = createInfo.getDeleteReturns();
+ Set<String> returnTypes = null;
+ String renamedClass = null;
+ for (String className : deleteReturns) {
+ // if we reach the end of a section, add it to the main map
+ if (className == null) {
+ if (returnTypes != null) {
+ mDeleteReturns.put(renamedClass, returnTypes);
}
-
- // if the renamed class is null, this is the beginning of a section
- if (renamedClass == null) {
- renamedClass = binaryToInternalClassName(className);
- continue;
- }
-
- // just a standard return type, we add it to the list.
- if (returnTypes == null) {
- returnTypes = new HashSet<String>();
- }
- returnTypes.add(binaryToInternalClassName(className));
+
+ renamedClass = null;
+ continue;
}
+
+ // if the renamed class is null, this is the beginning of a section
+ if (renamedClass == null) {
+ renamedClass = binaryToInternalClassName(className);
+ continue;
+ }
+
+ // just a standard return type, we add it to the list.
+ if (returnTypes == null) {
+ returnTypes = new HashSet<String>();
+ }
+ returnTypes.add(binaryToInternalClassName(className));
}
}
-
+
/**
* Returns the list of classes that have not been renamed yet.
* <p/>
@@ -163,12 +179,12 @@
public void setDeps(Map<String, ClassReader> deps) {
mDeps = deps;
}
-
+
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
}
-
+
/** Gets the map of dependencies that must be completely stubbed */
public Map<String, ClassReader> getDeps() {
return mDeps;
@@ -177,7 +193,7 @@
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-
+
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +202,7 @@
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
-
+
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +227,8 @@
/**
* Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
+ *
+ * @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
@@ -236,7 +252,7 @@
String classNameToEntryPath(String className) {
return className.replaceAll("\\.", "/").concat(".class");
}
-
+
/**
* Utility method to get the JAR entry path from a Class name.
* e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +264,32 @@
name = "$" + clazz.getSimpleName() + name;
clazz = parent;
}
- return classNameToEntryPath(clazz.getCanonicalName() + name);
+ return classNameToEntryPath(clazz.getCanonicalName() + name);
}
/**
* Transforms a class.
* <p/>
* There are 3 kind of transformations:
- *
+ *
* 1- For "mock" dependencies classes, we want to remove all code from methods and replace
* by a stub. Native methods must be implemented with this stub too. Abstract methods are
* left intact. Modified classes must be overridable (non-private, non-final).
* Native methods must be made non-final, non-private.
- *
+ *
* 2- For "keep" classes, we want to rewrite all native methods as indicated above.
* If a class has native methods, it must also be made non-private, non-final.
- *
+ *
* Note that unfortunately static methods cannot be changed to non-static (since static and
* non-static are invoked differently.)
*/
byte[] transform(ClassReader cr, boolean stubNativesOnly) {
boolean hasNativeMethods = hasNativeMethods(cr);
+
+ // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
String className = cr.getClassName();
-
+
String newName = transformName(className);
// transformName returns its input argument if there's no need to rename the class
if (newName != className) {
@@ -288,13 +306,24 @@
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
+
ClassVisitor rv = cw;
if (newName != className) {
rv = new RenameClassAdapter(cw, className, newName);
}
-
- TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
+
+ Set<String> delegateMethods = mDelegateMethods.get(className);
+ if (delegateMethods != null && !delegateMethods.isEmpty()) {
+ // If delegateMethods only contains one entry ALL_NATIVES and the class is
+ // known to have no native methods, just skip this step.
+ if (hasNativeMethods ||
+ !(delegateMethods.size() == 1 &&
+ delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+ rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+ }
+ }
+
+ TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
newName, rv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
@@ -323,7 +352,7 @@
return newName + className.substring(pos);
}
}
-
+
return className;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..92892784 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,11 +16,70 @@
package com.android.tools.layoutlib.create;
-public class CreateInfo {
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public Class<?>[] getInjectedClasses() {
+ return INJECTED_CLASSES;
+ }
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateMethods() {
+ return DELEGATE_METHODS;
+ }
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateClassNatives() {
+ return DELEGATE_CLASS_NATIVES;
+ }
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public String[] getOverriddenMethods() {
+ return OVERRIDDEN_METHODS;
+ }
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public String[] getRenamedClasses() {
+ return RENAMED_CLASSES;
+ }
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ //-----
+
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
- public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
@@ -28,19 +87,37 @@
};
/**
+ * The list of methods to rewrite as delegates.
+ */
+ private final static String[] DELEGATE_METHODS = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.view.View#isInEditMode",
+ // "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.graphics.Paint"
+ };
+
+ /**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
*/
- public final static String[] OVERRIDDEN_METHODS = new String[] {
- "android.view.View#isInEditMode",
- "android.content.res.Resources$Theme#obtainStyledAttributes",
- };
+ private final static String[] OVERRIDDEN_METHODS = new String[] {
+ // TODO: remove once DelegateClass is working
+ "android.view.View#isInEditMode",
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
- public final static String[] RENAMED_CLASSES =
+ private final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
"android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
- public final static String[] REMOVED_METHODS =
+ private final static String[] DELETE_RETURNS =
new String[] {
"android.graphics.Paint", // class to delete methods from
"android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+ public final static String ALL_NATIVES = "<<all_natives>>";
+
+ private final String mClassName;
+ private final Set<String> mDelegateMethods;
+ private final Log mLog;
+
+ /**
+ * Creates a new {@link DelegateClassAdapter} that can transform some methods
+ * from a class into delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ *
+ * @param log The logger object. Must not be null.
+ * @param cv the class visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param delegateMethods The set of method names to modify and/or the
+ * special constant {@link #ALL_NATIVES} to convert all native methods.
+ */
+ public DelegateClassAdapter(Log log,
+ ClassVisitor cv,
+ String className,
+ Set<String> delegateMethods) {
+ super(cv);
+ mLog = log;
+ mClassName = className;
+ mDelegateMethods = delegateMethods;
+ }
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+ mDelegateMethods.contains(name);
+
+ if (useDelegate) {
+ // remove native
+ access = access & ~Opcodes.ACC_NATIVE;
+ }
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ if (useDelegate) {
+ DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+ name, desc, isStatic);
+ if (isNative) {
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateCode();
+ } else {
+ return a;
+ }
+ }
+ return mw;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * 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 com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code><className>_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+ /**
+ * Suffix added to delegate classes.
+ */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+ /** True if {@link #visitCode()} has been invoked. */
+ private boolean mVisitCodeCalled;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mv the method visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter(Log log,
+ MethodVisitor mv,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ mLog = log;
+ mParentVisitor = mv;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ // We're going to simplify by not supporting constructors.
+ // The only trick with a constructor is to find the proper super constructor
+ // and call it (and deciding if we should mirror the original method call to
+ // a custom constructor or call a default one.)
+ throw new UnsupportedOperationException(
+ String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+ className, methodName, desc));
+ }
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is not-static, the first parameter will be this.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void method_1(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.method_1(this, a, b, c);
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at runtime, they have no influence on the method being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ aw.visitEnd();
+
+ if (!mVisitCodeCalled) {
+ // If this is a direct call to generateCode() as done by DelegateClassAdapter
+ // for natives, visitCode() hasn't been called yet.
+ mParentVisitor.visitCode();
+ mVisitCodeCalled = true;
+ }
+
+ int numVars = 0;
+
+ // Push "this" for an instance method, which is always ALOAD 0
+ if (!mIsStatic) {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+ }
+
+ // Push all other arguments
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
+ numVars += size;
+ }
+
+ // Construct the descriptor of the delegate. For a static method, it's the same
+ // however for an instance method we need to pass the 'this' reference first
+ String desc = mDesc;
+ if (!mIsStatic && argTypes.length > 0) {
+ Type[] argTypes2 = new Type[argTypes.length + 1];
+
+ argTypes2[0] = Type.getObjectType(mClassName);
+ System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
+
+ desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+ }
+
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+ // Invoke the static delegate
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mParentVisitor.visitMaxs(numVars, numVars);
+ mParentVisitor.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ public void visitCode() {
+ mVisitCodeCalled = true;
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * Skip the original.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ /**
+ * End of visiting. Generate the messaging code.
+ */
+ public void visitEnd() {
+ generateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ public void visitAttribute(Attribute attr) {
+ mParentVisitor.visitAttribute(attr);
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ // Skip original code.
+ }
+
+ public void visitLabel(Label label) {
+ // Skip original code.
+ }
+
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // Skip original code.
+ }
+
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ // Skip original code.
+ }
+
+ public void visitIincInsn(int var, int increment) {
+ // Skip original code.
+ }
+
+ public void visitIntInsn(int opcode, int operand) {
+ // Skip original code.
+ }
+
+ public void visitJumpInsn(int opcode, Label label) {
+ // Skip original code.
+ }
+
+ public void visitLdcInsn(Object cst) {
+ // Skip original code.
+ }
+
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ // Skip original code.
+ }
+
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ // Skip original code.
+ }
+
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitTypeInsn(int opcode, String type) {
+ // Skip original code.
+ }
+
+ public void visitVarInsn(int opcode, int var) {
+ // Skip original code.
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public abstract Class<?>[] getInjectedClasses();
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateMethods();
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateClassNatives();
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getOverriddenMethods();
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getRenamedClasses();
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 303f097..4adaff9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@
import java.util.Set;
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create <bunch of framework jars>
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
public class Main {
public static void main(String[] args) {
@@ -42,12 +63,7 @@
}
try {
- AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
- CreateInfo.INJECTED_CLASSES,
- CreateInfo.OVERRIDDEN_METHODS,
- CreateInfo.RENAMED_CLASSES,
- CreateInfo.REMOVED_METHODS
- );
+ AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { "android.view.View" }, // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@
import java.util.Set;
/**
- * Class adapter that can stub some or all of the methods of the class.
+ * Class adapter that can stub some or all of the methods of the class.
*/
class TransformClassAdapter extends ClassAdapter {
@@ -41,12 +41,12 @@
/**
* Creates a new class adapter that will stub some or all methods.
- * @param logger
- * @param stubMethods
+ * @param logger
+ * @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
* @param hasNative True if the method has natives, in which case its access should be
* changed.
@@ -67,10 +67,10 @@
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
-
+
// This class might be being renamed.
name = mClassName;
-
+
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@
mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
super.visit(version, access, name, signature, superName, interfaces);
}
-
+
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
-
+
if (mDeleteReturns != null) {
Type t = Type.getReturnType(desc);
if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@
(mStubAll ||
(access & Opcodes.ACC_NATIVE) != 0) ||
mStubMethods.contains(methodSignature)) {
-
+
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
// remove abstract, final and native
access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
+
String invokeSignature = methodSignature + desc;
mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
+
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
isStatic, isNative);
@@ -149,7 +149,7 @@
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
-
+
/* Visits a field. Makes it public. */
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
-
+
return super.visitField(access, name, desc, signature, value);
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertNotNull;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
import org.junit.After;
import org.junit.Before;
@@ -46,9 +45,9 @@
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -69,9 +68,9 @@
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
- "mock_android.dummy.InnerTest$MyStaticInnerClass",
- "mock_android.dummy.InnerTest$NotStaticInner1",
- "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@
},
map.keySet().toArray());
}
-
+
@Test
public void testFindClass() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
-
+
assertNotNull(cr);
assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@
"mock_android.widget.TableLayout",
},
found.keySet().toArray());
-
+
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
-
+
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@
ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-
+
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@
import static org.junit.Assert.assertArrayEquals;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,9 +42,9 @@
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -65,16 +63,41 @@
@Test
public void testClassRenaming() throws IOException, LogAbortException {
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
- null, // classes to inject in the final JAR
- null, // methods to force override
- new String[] { // classes to rename (so that we can replace them)
- "mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
- },
- null // methods deleted from their return type.
- );
+
+ ICreateInfo ci = new ICreateInfo() {
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[] {
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ };
+ }
+
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
null, // derived from
@@ -83,7 +106,7 @@
});
aa.analyze();
agen.generate();
-
+
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@
@Test
public void testHasNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@
@Test
public void testHasNoNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[0], cv.getMethodsFound());
assertFalse(cv.hasNativeMethods());
}
+ //-------
+
/**
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..9ad2e6e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+ private MockLog mLog;
+
+ private static final String CLASS_NAME =
+ DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+ ClassWithNative.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ mLog.setVerbose(true); // capture debug error too
+ }
+
+ /**
+ * Tests that a class not being modified still works.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNoOp() throws Exception {
+ // create an instance of the class that will be modified
+ // (load the class in a distinct class loader so that we can trash its definition later)
+ ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+ ClassWithNative instance1 = clazz1.newInstance();
+ assertEquals(42, instance1.add(20, 22));
+ try {
+ instance1.callNativeInstance(10, 3.1415, new Object[0] );
+ fail("Test should have failed to invoke callTheNativeMethod [1]");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected to fail since the native method is not implemented.
+ }
+
+ // Now process it but tell the delegate to not modify any method
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ String internalClassName = CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it again
+ final byte[] bytes = cw.toByteArray();
+
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
+ }
+ };
+ cl2.testModifiedInstance();
+ }
+
+ /**
+ * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+ * so this should fail with an {@link UnsupportedOperationException}.
+ *
+ * Although not tested here, the message of the exception should contain the
+ * constructor signature.
+ */
+ @Test(expected=UnsupportedOperationException.class)
+ public void testConstructorsNotSupported() throws IOException {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("<init>");
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+ }
+
+ @Test
+ public void testDelegateNative() throws Exception {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it
+ final byte[] bytes = cw.toByteArray();
+
+ try {
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+
+ // Use reflection to access inner methods
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ Object[] objResult = new Object[] { null };
+ int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+ assertEquals((int)(10 + 3.1415), result);
+ assertSame(i2, objResult[0]);
+
+ // Check that the native method now has the new annotation and is not native
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertFalse(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+ }
+ };
+ cl2.testModifiedInstance();
+
+ } catch (Throwable t) {
+ // For debugging, dump the bytecode of the class in case of unexpected error.
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ TraceClassVisitor tcv = new TraceClassVisitor(pw);
+
+ ClassReader cr2 = new ClassReader(bytes);
+ cr2.accept(tcv, 0 /* flags */);
+
+ String msg = "\n" + t.getClass().getCanonicalName();
+ if (t.getMessage() != null) {
+ msg += ": " + t.getMessage();
+ }
+ msg = msg + "\nBytecode dump:\n" + sw.toString();
+
+ // Re-throw exception with new message
+ RuntimeException ex = new RuntimeException(msg, t);
+ throw ex;
+ }
+ }
+
+ //-------
+
+ /**
+ * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+ * <p/>
+ * The trick here is that this class loader will test our modified version of ClassWithNative.
+ * Trying to do so in the original class loader generates all sort of link issues because
+ * there are 2 different definitions of the same class name. This class loader will
+ * define and load the class when requested by name and provide helpers to access the
+ * instance methods via reflection.
+ */
+ private abstract class ClassLoader2 extends ClassLoader {
+ private final byte[] mClassWithNative;
+
+ public ClassLoader2(byte[] classWithNative) {
+ super(null);
+ mClassWithNative = classWithNative;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+
+ if (CLASS_NAME.equals(name)) {
+ // Load the modified ClassWithNative from its bytes representation.
+ return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+ }
+
+ try {
+ // Load everything else from the original definition into the new class loader.
+ ClassReader cr = new ClassReader(name);
+ ClassWriter cw = new ClassWriter(0);
+ cr.accept(cw, 0);
+ byte[] bytes = cw.toByteArray();
+ return defineClass(name, bytes, 0, bytes.length);
+
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+ */
+ public int callAdd(Object instance, int a, int b) throws Exception {
+ Method m = instance.getClass().getMethod("add",
+ new Class<?>[] { int.class, int.class });
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+ * via reflection.
+ */
+ public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+ throws Exception {
+ Method m = instance.getClass().getMethod("callNativeInstance",
+ new Class<?>[] { int.class, double.class, Object[].class });
+
+ Object result = m.invoke(instance, new Object[] { a, d, o });
+ return ((Integer) result).intValue();
+ }
+
+ public abstract void testModifiedInstance() throws Exception;
+ }
+
+ /**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+ }
+
+ /**
+ * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
+ */
+ public static class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@
public class LogTest {
- public static class MockLog extends Log {
- StringBuilder mOut = new StringBuilder();
- StringBuilder mErr = new StringBuilder();
-
- public String getOut() {
- return mOut.toString();
- }
-
- public String getErr() {
- return mErr.toString();
- }
-
- @Override
- protected void outPrintln(String msg) {
- mOut.append(msg);
- mOut.append('\n');
- }
-
- @Override
- protected void errPrintln(String msg) {
- mErr.append(msg);
- mErr.append('\n');
- }
- }
-
private MockLog mLog;
-
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * 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.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+ StringBuilder mOut = new StringBuilder();
+ StringBuilder mErr = new StringBuilder();
+
+ public String getOut() {
+ return mOut.toString();
+ }
+
+ public String getErr() {
+ return mErr.toString();
+ }
+
+ @Override
+ protected void outPrintln(String msg) {
+ mOut.append(msg);
+ mOut.append('\n');
+ }
+
+ @Override
+ protected void errPrintln(String msg) {
+ mErr.append(msg);
+ mErr.append('\n');
+ }
+}
diff --git a/tools/obbtool/mkobb.sh b/tools/obbtool/mkobb.sh
index f4cae9a1..ba5256f 100755
--- a/tools/obbtool/mkobb.sh
+++ b/tools/obbtool/mkobb.sh
@@ -21,7 +21,7 @@
MOUNTDIR=/tmp
# Presets. Changing these will probably break your OBB on the device
-CRYPTO=blowfish
+CRYPTO=twofish
FS=vfat
MKFS=mkfs.vfat
LOSETUP=losetup
@@ -122,7 +122,12 @@
rmdir ${temp_mount}
fi
if [ "x${loop_dev}" != "x" ]; then \
- ${LOSETUPBIN} -d ${loop_dev}
+ if [ ${use_crypto} -eq 1 ]; then \
+ dmsetup remove -f ${loop_dev}
+ ${LOSETUPBIN} -d ${old_loop_dev}
+ else \
+ ${LOSETUPBIN} -d ${loop_dev}
+ fi
fi
if [ "x${tempfile}" != "x" -a -f "${tempfile}" ]; then \
rm -f ${tempfile}
@@ -156,11 +161,10 @@
while true; do \
case "$1" in
- -c) use_crypto=1; shift;;
-d) directory=$2; shift 2;;
-h) usage; exit 1;;
- -k) key=$2; shift 2;;
- -K) prompt_key=1; shift;;
+ -k) key=$2; use_crypto=1; shift 2;;
+ -K) prompt_key=1; use_crypto=1; shift;;
-v) verbose=1; shift;;
-o) filename=$2; shift 2;;
--) shift; break;;
@@ -202,7 +206,7 @@
tempfile=$(tempfile -d ${outdir}) || ( echo "ERROR: couldn't create temporary file in ${outdir}"; exit 1 )
-block_count=`du --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
+block_count=`du -s --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
if [ $? -ne 0 ]; then \
echo "ERROR: Couldn't read size of input directory ${directory}"
exit 1
@@ -216,12 +220,14 @@
loop_dev=$(${LOSETUPBIN} -f) || ( echo "ERROR: losetup wouldn't tell us the next unused device"; exit 1 )
+${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
+
if [ ${use_crypto} -eq 1 ]; then \
- keyfile=$(tempfile -d ${outdir}) || ( echo "ERROR: could not create temporary key file"; exit 1 )
- ${LOSETUPBIN} -p 5 -e ${CRYPTO} ${loop_dev} ${tempfile} 5< ${keyfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
- rm -f ${keyfile}
-else \
- ${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
+ hashed_key=`echo -n "${key}" | md5sum | awk '{ print $1 }'`
+ unique_dm_name=`basename ${tempfile}`
+ echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${hashed_key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
+ old_loop_dev=${loop_dev}
+ loop_dev=/dev/mapper/${unique_dm_name}
fi
#
@@ -252,7 +258,12 @@
#
umount ${temp_mount}
rmdir ${temp_mount}
-${LOSETUPBIN} -d ${loop_dev}
+if [ ${use_crypto} -eq 1 ]; then \
+ dmsetup remove -f ${loop_dev}
+ ${LOSETUPBIN} -d ${old_loop_dev}
+else \
+ ${LOSETUPBIN} -d ${loop_dev}
+fi
mv ${tempfile} ${filename}
trap - ERR
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
index dfa6841..3877aeb 100644
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ b/voip/java/android/net/rtp/AudioCodec.java
@@ -80,8 +80,7 @@
*/
public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
- // TODO: add rest of the codecs when the native part is done.
- private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA};
+ private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
private AudioCodec(int type, String rtpmap, String fmtp) {
this.type = type;
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 59631c1..bd859e8 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -23,6 +23,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Log;
import java.text.ParseException;
@@ -83,6 +84,8 @@
/** Part of the incoming call intent. */
public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
+ private static final String TAG = "SipManager";
+
private ISipService mSipService;
private Context mContext;
@@ -173,7 +176,7 @@
SipRegistrationListener listener) throws SipException {
try {
mSipService.open3(localProfile, incomingCallBroadcastAction,
- createRelay(listener));
+ createRelay(listener, localProfile.getUriString()));
} catch (RemoteException e) {
throw new SipException("open()", e);
}
@@ -191,7 +194,7 @@
SipRegistrationListener listener) throws SipException {
try {
mSipService.setRegistrationListener(
- localProfileUri, createRelay(listener));
+ localProfileUri, createRelay(listener, localProfileUri));
} catch (RemoteException e) {
throw new SipException("setRegistrationListener()", e);
}
@@ -425,8 +428,8 @@
public void register(SipProfile localProfile, int expiryTime,
SipRegistrationListener listener) throws SipException {
try {
- ISipSession session = mSipService.createSession(
- localProfile, createRelay(listener));
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()));
session.register(expiryTime);
} catch (RemoteException e) {
throw new SipException("register()", e);
@@ -446,8 +449,8 @@
public void unregister(SipProfile localProfile,
SipRegistrationListener listener) throws SipException {
try {
- ISipSession session = mSipService.createSession(
- localProfile, createRelay(listener));
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()));
session.unregister();
} catch (RemoteException e) {
throw new SipException("unregister()", e);
@@ -475,8 +478,8 @@
}
private static ISipSessionListener createRelay(
- SipRegistrationListener listener) {
- return ((listener == null) ? null : new ListenerRelay(listener));
+ SipRegistrationListener listener, String uri) {
+ return ((listener == null) ? null : new ListenerRelay(listener, uri));
}
/**
@@ -512,17 +515,23 @@
private static class ListenerRelay extends SipSessionAdapter {
private SipRegistrationListener mListener;
+ private String mUri;
// listener must not be null
- public ListenerRelay(SipRegistrationListener listener) {
+ public ListenerRelay(SipRegistrationListener listener, String uri) {
mListener = listener;
+ mUri = uri;
}
private String getUri(ISipSession session) {
try {
- return session.getLocalProfile().getUriString();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ return ((session == null)
+ ? mUri
+ : session.getLocalProfile().getUriString());
+ } catch (Throwable e) {
+ // SipService died? SIP stack died?
+ Log.w(TAG, "getUri(): " + e);
+ return null;
}
}
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 0ff5586..405dff8a 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -39,6 +39,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -49,6 +50,7 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -64,6 +66,7 @@
*/
public final class SipService extends ISipService.Stub {
private static final String TAG = "SipService";
+ private static final boolean DEBUGV = false;
private static final boolean DEBUG = true;
private static final boolean DEBUG_TIMER = DEBUG && false;
private static final int EXPIRY_TIME = 3600;
@@ -119,17 +122,19 @@
}
public synchronized SipProfile[] getListOfProfiles() {
- SipProfile[] profiles = new SipProfile[mSipGroups.size()];
- int i = 0;
+ boolean isCallerRadio = isCallerRadio();
+ ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
for (SipSessionGroupExt group : mSipGroups.values()) {
- profiles[i++] = group.getLocalProfile();
+ if (isCallerRadio || isCallerCreator(group)) {
+ profiles.add(group.getLocalProfile());
+ }
}
- return profiles;
+ return profiles.toArray(new SipProfile[profiles.size()]);
}
public void open(SipProfile localProfile) {
localProfile.setCallingUid(Binder.getCallingUid());
- if (localProfile.getAutoRegistration()) {
+ if (localProfile.getAutoRegistration() && isCallerRadio()) {
openToReceiveCalls(localProfile);
} else {
openToMakeCalls(localProfile);
@@ -153,8 +158,14 @@
String incomingCallBroadcastAction, ISipSessionListener listener) {
localProfile.setCallingUid(Binder.getCallingUid());
if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
- throw new RuntimeException(
- "empty broadcast action for incoming call");
+ Log.w(TAG, "empty broadcast action for incoming call");
+ return;
+ }
+ if (incomingCallBroadcastAction.equals(
+ SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) {
+ Log.w(TAG, "failed to open the profile; "
+ + "the action string is reserved");
+ return;
}
if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
+ incomingCallBroadcastAction + ": " + listener);
@@ -171,29 +182,64 @@
}
}
+ private boolean isCallerCreator(SipSessionGroupExt group) {
+ SipProfile profile = group.getLocalProfile();
+ return (profile.getCallingUid() == Binder.getCallingUid());
+ }
+
+ private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
+ return (isCallerRadio() || isCallerCreator(group));
+ }
+
+ private boolean isCallerRadio() {
+ return (Binder.getCallingUid() == Process.PHONE_UID);
+ }
+
public synchronized void close(String localProfileUri) {
- SipSessionGroupExt group = mSipGroups.remove(localProfileUri);
- if (group != null) {
- notifyProfileRemoved(group.getLocalProfile());
- group.close();
- if (isWifiOn() && !anyOpened()) releaseWifiLock();
+ SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+ if (group == null) return;
+ if (!isCallerCreatorOrRadio(group)) {
+ Log.d(TAG, "only creator or radio can close this profile");
+ return;
}
+
+ group = mSipGroups.remove(localProfileUri);
+ notifyProfileRemoved(group.getLocalProfile());
+ group.close();
+ if (isWifiOn() && !anyOpened()) releaseWifiLock();
}
public synchronized boolean isOpened(String localProfileUri) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- return ((group != null) ? group.isOpened() : false);
+ if (group == null) return false;
+ if (isCallerCreatorOrRadio(group)) {
+ return group.isOpened();
+ } else {
+ Log.i(TAG, "only creator or radio can query on the profile");
+ return false;
+ }
}
public synchronized boolean isRegistered(String localProfileUri) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- return ((group != null) ? group.isRegistered() : false);
+ if (group == null) return false;
+ if (isCallerCreatorOrRadio(group)) {
+ return group.isRegistered();
+ } else {
+ Log.i(TAG, "only creator or radio can query on the profile");
+ return false;
+ }
}
public synchronized void setRegistrationListener(String localProfileUri,
ISipSessionListener listener) {
SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group != null) group.setListener(listener);
+ if (group == null) return;
+ if (isCallerCreator(group)) {
+ group.setListener(listener);
+ } else {
+ Log.i(TAG, "only creator can set listener on the profile");
+ }
}
public synchronized ISipSession createSession(SipProfile localProfile,
@@ -234,6 +280,8 @@
group = new SipSessionGroupExt(localProfile, null, null);
mSipGroups.put(key, group);
notifyProfileAdded(localProfile);
+ } else if (!isCallerCreator(group)) {
+ throw new SipException("only creator can access the profile");
}
return group;
}
@@ -244,6 +292,9 @@
String key = localProfile.getUriString();
SipSessionGroupExt group = mSipGroups.get(key);
if (group != null) {
+ if (!isCallerCreator(group)) {
+ throw new SipException("only creator can access the profile");
+ }
group.setIncomingCallBroadcastAction(
incomingCallBroadcastAction);
group.setListener(listener);
@@ -510,32 +561,44 @@
}
}
+ // KeepAliveProcess is controlled by AutoRegistrationProcess.
+ // All methods will be invoked in sync with SipService.this except realRun()
private class KeepAliveProcess implements Runnable {
private static final String TAG = "\\KEEPALIVE/";
private static final int INTERVAL = 10;
private SipSessionGroup.SipSessionImpl mSession;
+ private boolean mRunning = false;
public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
mSession = session;
}
public void start() {
+ if (mRunning) return;
+ mRunning = true;
mTimer.set(INTERVAL * 1000, this);
}
+ // timeout handler
public void run() {
+ if (!mRunning) return;
+ final SipSessionGroup.SipSessionImpl session = mSession;
+
// delegate to mExecutor
getExecutor().addTask(new Runnable() {
public void run() {
- realRun();
+ realRun(session);
}
});
}
- private void realRun() {
+ // real timeout handler
+ private void realRun(SipSessionGroup.SipSessionImpl session) {
synchronized (SipService.this) {
- SipSessionGroup.SipSessionImpl session = mSession.duplicate();
- if (DEBUG) Log.d(TAG, "~~~ keepalive");
+ if (notCurrentSession(session)) return;
+
+ session = session.duplicate();
+ if (DEBUGV) Log.v(TAG, "~~~ keepalive");
mTimer.cancel(this);
session.sendKeepAlive();
if (session.isReRegisterRequired()) {
@@ -547,8 +610,14 @@
}
public void stop() {
+ mRunning = false;
+ mSession = null;
mTimer.cancel(this);
}
+
+ private boolean notCurrentSession(ISipSession session) {
+ return (session != mSession) || !mRunning;
+ }
}
private class AutoRegistrationProcess extends SipSessionAdapter
@@ -561,13 +630,15 @@
private long mExpiryTime;
private int mErrorCode;
private String mErrorMessage;
+ private boolean mRunning = false;
private String getAction() {
return toString();
}
public void start(SipSessionGroup group) {
- if (mSession == null) {
+ if (!mRunning) {
+ mRunning = true;
mBackoff = 1;
mSession = (SipSessionGroup.SipSessionImpl)
group.createSession(this);
@@ -584,35 +655,24 @@
}
public void stop() {
- stop(false);
- }
-
- private void stopButKeepStates() {
- stop(true);
- }
-
- private void stop(boolean keepStates) {
- if (mSession == null) return;
+ if (!mRunning) return;
+ mRunning = false;
+ mSession.setListener(null);
if (mConnected && mRegistered) mSession.unregister();
+
mTimer.cancel(this);
if (mKeepAliveProcess != null) {
mKeepAliveProcess.stop();
mKeepAliveProcess = null;
}
- if (!keepStates) {
- mSession = null;
- mRegistered = false;
- }
- }
- private boolean isStopped() {
- return (mSession == null);
+ mRegistered = false;
+ setListener(mProxy.getListener());
}
public void setListener(ISipSessionListener listener) {
synchronized (SipService.this) {
mProxy.setListener(listener);
- if (mSession == null) return;
try {
int state = (mSession == null)
@@ -632,6 +692,18 @@
mProxy.onRegistrationFailed(mSession, mErrorCode,
mErrorMessage);
}
+ } else if (!mConnected) {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.DATA_CONNECTION_LOST,
+ "no data connection");
+ } else if (!mRunning) {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.CLIENT_ERROR,
+ "registration not running");
+ } else {
+ mProxy.onRegistrationFailed(mSession,
+ SipErrorCode.IN_PROGRESS,
+ String.valueOf(state));
}
} catch (Throwable t) {
Log.w(TAG, "setListener(): " + t);
@@ -643,21 +715,29 @@
return mRegistered;
}
+ // timeout handler
public void run() {
- // delegate to mExecutor
- getExecutor().addTask(new Runnable() {
- public void run() {
- realRun();
- }
- });
+ synchronized (SipService.this) {
+ if (!mRunning) return;
+ final SipSessionGroup.SipSessionImpl session = mSession;
+
+ // delegate to mExecutor
+ getExecutor().addTask(new Runnable() {
+ public void run() {
+ realRun(session);
+ }
+ });
+ }
}
- private void realRun() {
- mErrorCode = SipErrorCode.NO_ERROR;
- mErrorMessage = null;
- if (DEBUG) Log.d(TAG, "~~~ registering");
+ // real timeout handler
+ private void realRun(SipSessionGroup.SipSessionImpl session) {
synchronized (SipService.this) {
- if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
+ if (notCurrentSession(session)) return;
+ mErrorCode = SipErrorCode.NO_ERROR;
+ mErrorMessage = null;
+ if (DEBUG) Log.d(TAG, "~~~ registering");
+ if (mConnected) session.register(EXPIRY_TIME);
}
}
@@ -697,22 +777,29 @@
public void onRegistering(ISipSession session) {
if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
+
mRegistered = false;
mProxy.onRegistering(session);
}
}
+ private boolean notCurrentSession(ISipSession session) {
+ if (session != mSession) {
+ ((SipSessionGroup.SipSessionImpl) session).setListener(null);
+ return true;
+ }
+ return !mRunning;
+ }
+
@Override
public void onRegistrationDone(ISipSession session, int duration) {
if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
mProxy.onRegistrationDone(session, duration);
- if (isStopped()) return;
-
if (duration > 0) {
mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime()
@@ -751,17 +838,18 @@
if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
+ SipErrorCode.toString(errorCode) + ": " + message);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
- mErrorCode = errorCode;
- mErrorMessage = message;
- mProxy.onRegistrationFailed(session, errorCode, message);
+ if (notCurrentSession(session)) return;
if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
if (DEBUG) Log.d(TAG, " pause auto-registration");
- stopButKeepStates();
- } else if (!isStopped()) {
+ stop();
+ } else {
onError();
}
+
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ mProxy.onRegistrationFailed(session, errorCode, message);
}
}
@@ -769,14 +857,11 @@
public void onRegistrationTimeout(ISipSession session) {
if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
synchronized (SipService.this) {
- if (!isStopped() && (session != mSession)) return;
+ if (notCurrentSession(session)) return;
+
mErrorCode = SipErrorCode.TIME_OUT;
mProxy.onRegistrationTimeout(session);
-
- if (!isStopped()) {
- mRegistered = false;
- onError();
- }
+ onError();
}
}
@@ -883,6 +968,7 @@
mConnected = connected;
}
+ // timeout handler
@Override
public void run() {
// delegate to mExecutor
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 4321d7b..bc377cf 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -49,6 +49,7 @@
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
+import javax.sip.ObjectInUseException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
@@ -334,12 +335,12 @@
if (isRequestEvent(Request.INVITE, evt)) {
RequestEvent event = (RequestEvent) evt;
SipSessionImpl newSession = new SipSessionImpl(mProxy);
+ newSession.mState = SipSession.State.INCOMING_CALL;
newSession.mServerTransaction = mSipHelper.sendRinging(event,
generateTag());
newSession.mDialog = newSession.mServerTransaction.getDialog();
newSession.mInviteReceived = event;
newSession.mPeerProfile = createPeerProfile(event.getRequest());
- newSession.mState = SipSession.State.INCOMING_CALL;
newSession.mPeerSessionDescription =
extractContent(event.getRequest());
addSipSession(newSession);
@@ -415,11 +416,25 @@
mPeerProfile = null;
mState = SipSession.State.READY_TO_CALL;
mInviteReceived = null;
- mDialog = null;
- mServerTransaction = null;
- mClientTransaction = null;
mPeerSessionDescription = null;
+ if (mDialog != null) mDialog.delete();
+ mDialog = null;
+
+ try {
+ if (mServerTransaction != null) mServerTransaction.terminate();
+ } catch (ObjectInUseException e) {
+ // ignored
+ }
+ mServerTransaction = null;
+
+ try {
+ if (mClientTransaction != null) mClientTransaction.terminate();
+ } catch (ObjectInUseException e) {
+ // ignored
+ }
+ mClientTransaction = null;
+
cancelSessionTimer();
}
@@ -708,7 +723,6 @@
case SipSession.State.PINGING:
reset();
mReRegisterFlag = true;
- mState = SipSession.State.READY_TO_CALL;
break;
default:
@@ -877,6 +891,7 @@
private boolean readyForCall(EventObject evt) throws SipException {
// expect MakeCallCommand, RegisterCommand, DEREGISTER
if (evt instanceof MakeCallCommand) {
+ mState = SipSession.State.OUTGOING_CALL;
MakeCallCommand cmd = (MakeCallCommand) evt;
mPeerProfile = cmd.getPeerProfile();
mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
@@ -884,25 +899,24 @@
generateTag());
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.OUTGOING_CALL;
- mProxy.onCalling(this);
startSessionTimer(cmd.getTimeout());
+ mProxy.onCalling(this);
return true;
} else if (evt instanceof RegisterCommand) {
+ mState = SipSession.State.REGISTERING;
int duration = ((RegisterCommand) evt).getDuration();
mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
generateTag(), duration);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.REGISTERING;
mProxy.onRegistering(this);
return true;
} else if (DEREGISTER == evt) {
+ mState = SipSession.State.DEREGISTERING;
mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
generateTag(), 0);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSession.State.DEREGISTERING;
mProxy.onRegistering(this);
return true;
}
@@ -913,11 +927,11 @@
// expect MakeCallCommand(answering) , END_CALL cmd , Cancel
if (evt instanceof MakeCallCommand) {
// answer call
+ mState = SipSession.State.INCOMING_CALL_ANSWERING;
mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
mLocalProfile,
((MakeCallCommand) evt).getSessionDescription(),
mServerTransaction);
- mState = SipSession.State.INCOMING_CALL_ANSWERING;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
} else if (END_CALL == evt) {
@@ -965,8 +979,8 @@
// ring back for better UX
if (mState == SipSession.State.OUTGOING_CALL) {
mState = SipSession.State.OUTGOING_CALL_RING_BACK;
- mProxy.onRingingBack(this);
cancelSessionTimer();
+ mProxy.onRingingBack(this);
}
return true;
case Response.OK:
@@ -1009,8 +1023,8 @@
// RFC says that UA should not send out cancel when no
// response comes back yet. We are cheating for not checking
// response.
- mSipHelper.sendCancel(mClientTransaction);
mState = SipSession.State.OUTGOING_CALL_CANCELING;
+ mSipHelper.sendCancel(mClientTransaction);
startSessionTimer(CANCEL_CALL_TIMER);
return true;
}
@@ -1065,8 +1079,8 @@
return true;
} else if (isRequestEvent(Request.INVITE, evt)) {
// got Re-INVITE
- RequestEvent event = mInviteReceived = (RequestEvent) evt;
mState = SipSession.State.INCOMING_CALL;
+ RequestEvent event = mInviteReceived = (RequestEvent) evt;
mPeerSessionDescription = extractContent(event.getRequest());
mServerTransaction = null;
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1077,9 +1091,9 @@
return true;
} else if (evt instanceof MakeCallCommand) {
// to change call
+ mState = SipSession.State.OUTGOING_CALL;
mClientTransaction = mSipHelper.sendReinvite(mDialog,
((MakeCallCommand) evt).getSessionDescription());
- mState = SipSession.State.OUTGOING_CALL;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
}
@@ -1223,14 +1237,12 @@
}
private void onRegistrationFailed(Throwable exception) {
- reset();
exception = getRootCause(exception);
onRegistrationFailed(getErrorCode(exception),
exception.toString());
}
private void onRegistrationFailed(Response response) {
- reset();
int statusCode = response.getStatusCode();
onRegistrationFailed(getErrorCode(statusCode),
createErrorMessage(response));
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
new file mode 100644
index 0000000..f3ecac2
--- /dev/null
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#include "AudioCodec.h"
+
+#include "gsmamr_dec.h"
+#include "gsmamr_enc.h"
+
+namespace {
+
+const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244};
+
+//------------------------------------------------------------------------------
+
+// See RFC 4867 for the encoding details.
+
+class AmrCodec : public AudioCodec
+{
+public:
+ AmrCodec() {
+ if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
+ mEncoder = NULL;
+ }
+ if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
+ mDecoder = NULL;
+ }
+ }
+
+ ~AmrCodec() {
+ if (mEncoder) {
+ AMREncodeExit(&mEncoder, &mSidSync);
+ }
+ if (mDecoder) {
+ GSMDecodeFrameExit(&mDecoder);
+ }
+ }
+
+ int set(int sampleRate, const char *fmtp);
+ int encode(void *payload, int16_t *samples);
+ int decode(int16_t *samples, void *payload, int length);
+
+private:
+ void *mEncoder;
+ void *mSidSync;
+ void *mDecoder;
+
+ int mMode;
+ int mModeSet;
+ bool mOctetAligned;
+};
+
+int AmrCodec::set(int sampleRate, const char *fmtp)
+{
+ // These parameters are not supported.
+ if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") ||
+ strcasestr(fmtp, "interleaving=")) {
+ return -1;
+ }
+
+ // Handle mode-set and octet-align.
+ char *modes = strcasestr(fmtp, "mode-set=");
+ if (modes) {
+ mMode = 0;
+ mModeSet = 0;
+ for (char c = *modes; c && c != ' '; c = *++modes) {
+ if (c >= '0' && c <= '7') {
+ int mode = c - '0';
+ if (mode > mMode) {
+ mMode = mode;
+ }
+ mModeSet |= 1 << mode;
+ }
+ }
+ } else {
+ mMode = 7;
+ mModeSet = 0xFF;
+ }
+ mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL);
+
+ // TODO: handle mode-change-*.
+
+ return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
+}
+
+int AmrCodec::encode(void *payload, int16_t *samples)
+{
+ unsigned char *bytes = (unsigned char *)payload;
+ Frame_Type_3GPP type;
+
+ int length = AMREncode(mEncoder, mSidSync, (Mode)mMode,
+ samples, bytes + 1, &type, AMR_TX_WMF);
+
+ if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) {
+ return -1;
+ }
+
+ if (mOctetAligned) {
+ bytes[0] = 0xF0;
+ bytes[1] = (mMode << 3) | 0x04;
+ ++length;
+ } else {
+ // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit).
+ bytes[0] = 0xFF;
+ bytes[1] = 0xC0 | (mMode << 1) | 1;
+
+ // Shift left 6 bits and update the length.
+ bytes[length + 1] = 0;
+ for (int i = 0; i <= length; ++i) {
+ bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2);
+ }
+ length = (10 + gFrameBits[mMode] + 7) >> 3;
+ }
+ return length;
+}
+
+int AmrCodec::decode(int16_t *samples, void *payload, int length)
+{
+ unsigned char *bytes = (unsigned char *)payload;
+ Frame_Type_3GPP type;
+ if (length < 2) {
+ return -1;
+ }
+ int request = bytes[0] >> 4;
+
+ if (mOctetAligned) {
+ if ((bytes[1] & 0xC4) != 0x04) {
+ return -1;
+ }
+ type = (Frame_Type_3GPP)(bytes[1] >> 3);
+ if (length != (16 + gFrameBits[type] + 7) >> 3) {
+ return -1;
+ }
+ length -= 2;
+ bytes += 2;
+ } else {
+ if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) {
+ return -1;
+ }
+ type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07);
+ if (length != (10 + gFrameBits[type] + 7) >> 3) {
+ return -1;
+ }
+
+ // Shift left 2 bits and update the length.
+ --length;
+ for (int i = 1; i < length; ++i) {
+ bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6);
+ }
+ bytes[length] <<= 2;
+ length = (gFrameBits[type] + 7) >> 3;
+ ++bytes;
+ }
+
+ if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) {
+ return -1;
+ }
+
+ // Handle CMR
+ if (request < 8 && request != mMode) {
+ for (int i = request; i >= 0; --i) {
+ if (mModeSet & (1 << i)) {
+ mMode = request;
+ break;
+ }
+ }
+ }
+
+ return 160;
+}
+
+//------------------------------------------------------------------------------
+
+// See RFC 3551 for the encoding details.
+
+class GsmEfrCodec : public AudioCodec
+{
+public:
+ GsmEfrCodec() {
+ if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
+ mEncoder = NULL;
+ }
+ if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
+ mDecoder = NULL;
+ }
+ }
+
+ ~GsmEfrCodec() {
+ if (mEncoder) {
+ AMREncodeExit(&mEncoder, &mSidSync);
+ }
+ if (mDecoder) {
+ GSMDecodeFrameExit(&mDecoder);
+ }
+ }
+
+ int set(int sampleRate, const char *fmtp) {
+ return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
+ }
+
+ int encode(void *payload, int16_t *samples);
+ int decode(int16_t *samples, void *payload, int length);
+
+private:
+ void *mEncoder;
+ void *mSidSync;
+ void *mDecoder;
+};
+
+int GsmEfrCodec::encode(void *payload, int16_t *samples)
+{
+ unsigned char *bytes = (unsigned char *)payload;
+ Frame_Type_3GPP type;
+
+ int length = AMREncode(mEncoder, mSidSync, MR122,
+ samples, bytes, &type, AMR_TX_WMF);
+
+ if (type == AMR_122 && length == 32) {
+ bytes[0] = 0xC0 | (bytes[1] >> 4);
+ for (int i = 1; i < 31; ++i) {
+ bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
+ }
+ return 31;
+ }
+ return -1;
+}
+
+int GsmEfrCodec::decode(int16_t *samples, void *payload, int length)
+{
+ unsigned char *bytes = (unsigned char *)payload;
+ if (length == 31 && (bytes[0] >> 4) == 0x0C) {
+ for (int i = 0; i < 30; ++i) {
+ bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
+ }
+ bytes[30] <<= 4;
+
+ if (AMRDecode(mDecoder, AMR_122, bytes, samples, MIME_IETF) == 31) {
+ return 160;
+ }
+ }
+ return -1;
+}
+
+} // namespace
+
+AudioCodec *newAmrCodec()
+{
+ return new AmrCodec;
+}
+
+AudioCodec *newGsmEfrCodec()
+{
+ return new GsmEfrCodec;
+}
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
index 29683bd..76c43ba 100644
--- a/voip/jni/rtp/Android.mk
+++ b/voip/jni/rtp/Android.mk
@@ -22,11 +22,13 @@
LOCAL_SRC_FILES := \
AudioCodec.cpp \
AudioGroup.cpp \
+ EchoSuppressor.cpp \
RtpStream.cpp \
util.cpp \
rtp_jni.cpp
LOCAL_SRC_FILES += \
+ AmrCodec.cpp \
G711Codec.cpp \
GsmCodec.cpp
@@ -34,13 +36,20 @@
libnativehelper \
libcutils \
libutils \
- libmedia
+ libmedia \
+ libstagefright
LOCAL_STATIC_LIBRARIES := libgsm
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
- external/libgsm/inc
+ external/libgsm/inc \
+ frameworks/base/media/libstagefright/codecs/amrnb/common/include \
+ frameworks/base/media/libstagefright/codecs/amrnb/common/ \
+ frameworks/base/media/libstagefright/codecs/amrnb/enc/include \
+ frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
+ frameworks/base/media/libstagefright/codecs/amrnb/dec/include \
+ frameworks/base/media/libstagefright/codecs/amrnb/dec/src
LOCAL_CFLAGS += -fvisibility=hidden
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
index fc33ef2..2267ea0 100644
--- a/voip/jni/rtp/AudioCodec.cpp
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -21,6 +21,8 @@
extern AudioCodec *newAlawCodec();
extern AudioCodec *newUlawCodec();
extern AudioCodec *newGsmCodec();
+extern AudioCodec *newAmrCodec();
+extern AudioCodec *newGsmEfrCodec();
struct AudioCodecType {
const char *name;
@@ -29,6 +31,8 @@
{"PCMA", newAlawCodec},
{"PCMU", newUlawCodec},
{"GSM", newGsmCodec},
+ {"AMR", newAmrCodec},
+ {"GSM-EFR", newGsmEfrCodec},
{NULL, NULL},
};
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 72c882b..9da560a 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -44,6 +44,7 @@
#include "JNIHelp.h"
#include "AudioCodec.h"
+#include "EchoSuppressor.h"
extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
@@ -57,9 +58,9 @@
// a modulo operation on the index while accessing the array. However modulo can
// be expensive on some platforms, such as ARM. Thus we round up the size of the
// array to the nearest power of 2 and then use bitwise-and instead of modulo.
-// Currently we make it 256ms long and assume packet interval is 32ms or less.
-// The first 64ms is the place where samples get mixed. The rest 192ms is the
-// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers
+// Currently we make it 512ms long and assume packet interval is 40ms or less.
+// The first 80ms is the place where samples get mixed. The rest 432ms is the
+// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers
// are chosen by experiments and each of them can be adjusted as needed.
// Other notes:
@@ -69,7 +70,11 @@
// milliseconds. No floating points.
// + If we cannot get enough CPU, we drop samples and simulate packet loss.
// + Resampling is not done yet, so streams in one group must use the same rate.
-// For the first release we might only support 8kHz and 16kHz.
+// For the first release only 8000Hz is supported.
+
+#define BUFFER_SIZE 512
+#define HISTORY_SIZE 80
+#define MEASURE_PERIOD 2000
class AudioStream
{
@@ -85,9 +90,6 @@
void encode(int tick, AudioStream *chain);
void decode(int tick);
-private:
- bool isNatAddress(struct sockaddr_storage *addr);
-
enum {
NORMAL = 0,
SEND_ONLY = 1,
@@ -95,12 +97,14 @@
LAST_MODE = 2,
};
+private:
int mMode;
int mSocket;
sockaddr_storage mRemote;
AudioCodec *mCodec;
uint32_t mCodecMagic;
uint32_t mDtmfMagic;
+ bool mFixRemote;
int mTick;
int mSampleRate;
@@ -112,6 +116,7 @@
int mBufferMask;
int mBufferHead;
int mBufferTail;
+ int mLatencyTimer;
int mLatencyScore;
uint16_t mSequence;
@@ -160,12 +165,13 @@
mInterval = mSampleCount / mSampleRate;
// Allocate jitter buffer.
- for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1);
- mBufferMask >>= 2;
+ for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1);
+ mBufferMask *= BUFFER_SIZE;
mBuffer = new int16_t[mBufferMask];
--mBufferMask;
mBufferHead = 0;
mBufferTail = 0;
+ mLatencyTimer = 0;
mLatencyScore = 0;
// Initialize random bits.
@@ -181,10 +187,24 @@
if (codec) {
mRemote = *remote;
mCodec = codec;
+
+ // Here we should never get an private address, but some buggy proxy
+ // servers do give us one. To solve this, we replace the address when
+ // the first time we successfully decode an incoming packet.
+ mFixRemote = false;
+ if (remote->ss_family == AF_INET) {
+ unsigned char *address =
+ (unsigned char *)&((sockaddr_in *)remote)->sin_addr;
+ if (address[0] == 10 ||
+ (address[0] == 172 && (address[1] >> 4) == 1) ||
+ (address[0] == 192 && address[1] == 168)) {
+ mFixRemote = true;
+ }
+ }
}
- LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket,
- (codec ? codec->name : "RAW"), mSampleRate, mInterval);
+ LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket,
+ (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode);
return true;
}
@@ -234,7 +254,7 @@
mTick += skipped * mInterval;
mSequence += skipped;
mTimestamp += skipped * mSampleCount;
- LOGD("stream[%d] skips %d packets", mSocket, skipped);
+ LOGV("stream[%d] skips %d packets", mSocket, skipped);
}
tick = mTick;
@@ -283,7 +303,7 @@
if (!mixed) {
if ((mTick ^ mLogThrottle) >> 10) {
mLogThrottle = mTick;
- LOGD("stream[%d] no data", mSocket);
+ LOGV("stream[%d] no data", mSocket);
}
return;
}
@@ -311,23 +331,13 @@
buffer[2] = mSsrc;
int length = mCodec->encode(&buffer[3], samples);
if (length <= 0) {
- LOGD("stream[%d] encoder error", mSocket);
+ LOGV("stream[%d] encoder error", mSocket);
return;
}
sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
sizeof(mRemote));
}
-bool AudioStream::isNatAddress(struct sockaddr_storage *addr) {
- if (addr->ss_family != AF_INET) return false;
- struct sockaddr_in *s4 = (struct sockaddr_in *)addr;
- unsigned char *d = (unsigned char *) &s4->sin_addr;
- if ((d[0] == 10)
- || ((d[0] == 172) && (d[1] & 0x10))
- || ((d[0] == 192) && (d[1] == 168))) return true;
- return false;
-}
-
void AudioStream::decode(int tick)
{
char c;
@@ -337,31 +347,37 @@
}
// Make sure mBufferHead and mBufferTail are reasonable.
- if ((unsigned int)(tick + 256 - mBufferHead) > 1024) {
- mBufferHead = tick - 64;
+ if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) {
+ mBufferHead = tick - HISTORY_SIZE;
mBufferTail = mBufferHead;
}
- if (tick - mBufferHead > 64) {
+ if (tick - mBufferHead > HISTORY_SIZE) {
// Throw away outdated samples.
- mBufferHead = tick - 64;
+ mBufferHead = tick - HISTORY_SIZE;
if (mBufferTail - mBufferHead < 0) {
mBufferTail = mBufferHead;
}
}
- if (mBufferTail - tick <= 80) {
- mLatencyScore = tick;
- } else if (tick - mLatencyScore >= 5000) {
- // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms
- // in the past 5s. This rarely happens, so let us just keep it simple.
- LOGD("stream[%d] latency control", mSocket);
- mBufferTail = tick + 40;
+ // Adjust the jitter buffer if the latency keeps larger than two times of the
+ // packet interval in the past two seconds.
+ int score = mBufferTail - tick - mInterval * 2;
+ if (mLatencyScore > score) {
+ mLatencyScore = score;
+ }
+ if (mLatencyScore <= 0) {
+ mLatencyTimer = tick;
+ mLatencyScore = score;
+ } else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
+ LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
+ mBufferTail -= mLatencyScore;
+ mLatencyTimer = tick;
}
- if (mBufferTail - mBufferHead > 256 - mInterval) {
+ if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) {
// Buffer overflow. Drop the packet.
- LOGD("stream[%d] buffer overflow", mSocket);
+ LOGV("stream[%d] buffer overflow", mSocket);
recv(mSocket, &c, 1, MSG_DONTWAIT);
return;
}
@@ -375,27 +391,17 @@
MSG_TRUNC | MSG_DONTWAIT) >> 1;
} else {
__attribute__((aligned(4))) uint8_t buffer[2048];
- struct sockaddr_storage src_addr;
- socklen_t addrlen;
- length = recvfrom(mSocket, buffer, sizeof(buffer),
- MSG_TRUNC|MSG_DONTWAIT, (sockaddr*)&src_addr, &addrlen);
+ sockaddr_storage remote;
+ socklen_t len = sizeof(remote);
- // The following if clause is for fixing the target address if
- // proxy server did not replace the NAT address with its media
- // port in SDP. Although it is proxy server's responsibility for
- // replacing the connection address with correct one, we will change
- // the target address as we detect the difference for now until we
- // know the best way to get rid of this issue.
- if ((memcmp((void*)&src_addr, (void*)&mRemote, addrlen) != 0) &&
- isNatAddress(&mRemote)) {
- memcpy((void*)&mRemote, (void*)&src_addr, addrlen);
- }
+ length = recvfrom(mSocket, buffer, sizeof(buffer),
+ MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &len);
// Do we need to check SSRC, sequence, and timestamp? They are not
// reliable but at least they can be used to identify duplicates?
if (length < 12 || length > (int)sizeof(buffer) ||
(ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
- LOGD("stream[%d] malformed packet", mSocket);
+ LOGV("stream[%d] malformed packet", mSocket);
return;
}
int offset = 12 + ((buffer[0] & 0x0F) << 2);
@@ -409,24 +415,28 @@
if (length >= 0) {
length = mCodec->decode(samples, &buffer[offset], length);
}
+ if (length > 0 && mFixRemote) {
+ mRemote = remote;
+ mFixRemote = false;
+ }
}
- if (length != mSampleCount) {
- LOGD("stream[%d] decoder error", mSocket);
+ if (length <= 0) {
+ LOGV("stream[%d] decoder error", mSocket);
return;
}
if (tick - mBufferTail > 0) {
- // Buffer underrun. Reset the jitter buffer to 40ms.
- LOGD("stream[%d] buffer underrun", mSocket);
+ // Buffer underrun. Reset the jitter buffer.
+ LOGV("stream[%d] buffer underrun", mSocket);
if (mBufferTail - mBufferHead <= 0) {
- mBufferHead = tick + 40;
+ mBufferHead = tick + mInterval;
mBufferTail = mBufferHead;
} else {
- int tail = (tick + 40) * mSampleRate;
+ int tail = (tick + mInterval) * mSampleRate;
for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
mBuffer[i & mBufferMask] = 0;
}
- mBufferTail = tick + 40;
+ mBufferTail = tick + mInterval;
}
}
@@ -453,7 +463,6 @@
bool add(AudioStream *stream);
bool remove(int socket);
-private:
enum {
ON_HOLD = 0,
MUTED = 1,
@@ -462,6 +471,7 @@
LAST_MODE = 3,
};
+private:
AudioStream *mChain;
int mEventQueue;
volatile int mDtmfEvent;
@@ -683,7 +693,7 @@
int count = 0;
for (AudioStream *stream = chain; stream; stream = stream->mNext) {
- if (!stream->mTick || tick - stream->mTick >= 0) {
+ if (tick - stream->mTick >= 0) {
stream->encode(tick, chain);
}
if (deadline - stream->mTick > 0) {
@@ -757,7 +767,9 @@
}
LOGD("latency: output %d, input %d", track.latency(), record.latency());
- // TODO: initialize echo canceler here.
+ // Initialize echo canceler.
+ EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 +
+ (track.latency() + record.latency()) * sampleRate / 1000);
// Give device socket a reasonable buffer size.
setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
@@ -767,11 +779,14 @@
char c;
while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
- // Start your engine!
- track.start();
+ // Start AudioRecord before AudioTrack. This prevents AudioTrack from being
+ // disabled due to buffer underrun while waiting for AudioRecord.
if (mode != MUTED) {
record.start();
+ int16_t one;
+ record.read(&one, sizeof(one));
}
+ track.start();
while (!exitPending()) {
int16_t output[sampleCount];
@@ -797,41 +812,37 @@
track.releaseBuffer(&buffer);
} else if (status != TIMED_OUT && status != WOULD_BLOCK) {
LOGE("cannot write to AudioTrack");
- break;
+ return true;
}
}
if (toRead > 0) {
AudioRecord::Buffer buffer;
- buffer.frameCount = record.frameCount();
+ buffer.frameCount = toRead;
status_t status = record.obtainBuffer(&buffer, 1);
if (status == NO_ERROR) {
- int count = ((int)buffer.frameCount < toRead) ?
- buffer.frameCount : toRead;
- memcpy(&input[sampleCount - toRead], buffer.i8, count * 2);
- toRead -= count;
- if (buffer.frameCount < record.frameCount()) {
- buffer.frameCount = count;
- }
+ int offset = sampleCount - toRead;
+ memcpy(&input[offset], buffer.i8, buffer.size);
+ toRead -= buffer.frameCount;
record.releaseBuffer(&buffer);
} else if (status != TIMED_OUT && status != WOULD_BLOCK) {
LOGE("cannot read from AudioRecord");
- break;
+ return true;
}
}
}
if (chances <= 0) {
- LOGE("device loop timeout");
- break;
+ LOGW("device loop timeout");
+ while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
}
if (mode != MUTED) {
if (mode == NORMAL) {
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
} else {
- // TODO: Echo canceller runs here.
+ echo.run(output, input);
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
}
}
@@ -937,6 +948,10 @@
void setMode(JNIEnv *env, jobject thiz, jint mode)
{
+ if (mode < 0 || mode > AudioGroup::LAST_MODE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
if (group && !group->setMode(mode)) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
new file mode 100644
index 0000000..92015a9
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <math.h>
+
+#define LOG_TAG "Echo"
+#include <utils/Log.h>
+
+#include "EchoSuppressor.h"
+
+EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength)
+{
+ int scale = 1;
+ while (tailLength > 200 * scale) {
+ scale <<= 1;
+ }
+ if (scale > sampleCount) {
+ scale = sampleCount;
+ }
+
+ mScale = scale;
+ mSampleCount = sampleCount;
+ mWindowSize = sampleCount / scale;
+ mTailLength = (tailLength + scale - 1) / scale;
+ mRecordLength = (sampleRate + sampleCount - 1) / sampleCount;
+ mRecordOffset = 0;
+
+ mXs = new float[mTailLength + mWindowSize];
+ memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize));
+ mXYs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mXXs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mYY = 0;
+
+ mXYRecords = new float[mRecordLength * mTailLength];
+ memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength);
+ mXXRecords = new float[mRecordLength * mWindowSize];
+ memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize);
+ mYYRecords = new float[mRecordLength];
+ memset(mYYRecords, 0, sizeof(float) * mRecordLength);
+
+ mLastX = 0;
+ mLastY = 0;
+}
+
+EchoSuppressor::~EchoSuppressor()
+{
+ delete [] mXs;
+ delete [] mXYs;
+ delete [] mXXs;
+ delete [] mXYRecords;
+ delete [] mXXRecords;
+ delete [] mYYRecords;
+}
+
+void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
+{
+ float *records;
+
+ // Update Xs.
+ for (int i = 0; i < mTailLength; ++i) {
+ mXs[i] = mXs[mWindowSize + i];
+ }
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float x = playbacked[j + k] >> 8;
+ mLastX += x;
+ sum += (mLastX >= 0) ? mLastX : -mLastX;
+ mLastX = 0.005f * mLastX - x;
+ }
+ mXs[mTailLength - 1 + i] = sum;
+ }
+
+ // Update XXs and XXRecords.
+ for (int i = 0; i < mTailLength - mWindowSize; ++i) {
+ mXXs[i] = mXXs[mWindowSize + i];
+ }
+ records = &mXXRecords[mRecordOffset * mWindowSize];
+ for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) {
+ float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i];
+ mXXs[j] = mXXs[j - 1] + xx - records[i];
+ records[i] = xx;
+ if (mXXs[j] < 0) {
+ mXXs[j] = 0;
+ }
+ }
+
+ // Compute Ys.
+ float ys[mWindowSize];
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float y = recorded[j + k] >> 8;
+ mLastY += y;
+ sum += (mLastY >= 0) ? mLastY : -mLastY;
+ mLastY = 0.005f * mLastY - y;
+ }
+ ys[i] = sum;
+ }
+
+ // Update YY and YYRecords.
+ float yy = 0;
+ for (int i = 0; i < mWindowSize; ++i) {
+ yy += ys[i] * ys[i];
+ }
+ mYY += yy - mYYRecords[mRecordOffset];
+ mYYRecords[mRecordOffset] = yy;
+ if (mYY < 0) {
+ mYY = 0;
+ }
+
+ // Update XYs and XYRecords.
+ records = &mXYRecords[mRecordOffset * mTailLength];
+ for (int i = 0; i < mTailLength; ++i) {
+ float xy = 0;
+ for (int j = 0;j < mWindowSize; ++j) {
+ xy += mXs[i + j] * ys[j];
+ }
+ mXYs[i] += xy - records[i];
+ records[i] = xy;
+ if (mXYs[i] < 0) {
+ mXYs[i] = 0;
+ }
+ }
+
+ // Computes correlations from XYs, XXs, and YY.
+ float weight = 1.0f / (mYY + 1);
+ float correlation = 0;
+ int latency = 0;
+ for (int i = 0; i < mTailLength; ++i) {
+ float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1);
+ if (c > correlation) {
+ correlation = c;
+ latency = i;
+ }
+ }
+
+ correlation = sqrtf(correlation);
+ if (correlation > 0.3f) {
+ float factor = 1.0f - correlation;
+ factor *= factor;
+ for (int i = 0; i < mSampleCount; ++i) {
+ recorded[i] *= factor;
+ }
+ }
+// LOGI("latency %5d, correlation %.10f", latency, correlation);
+
+
+ // Increase RecordOffset.
+ ++mRecordOffset;
+ if (mRecordOffset == mRecordLength) {
+ mRecordOffset = 0;
+ }
+}
diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h
new file mode 100644
index 0000000..85decf5
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ECHO_SUPPRESSOR_H__
+#define __ECHO_SUPPRESSOR_H__
+
+#include <stdint.h>
+
+class EchoSuppressor
+{
+public:
+ // The sampleCount must be power of 2.
+ EchoSuppressor(int sampleRate, int sampleCount, int tailLength);
+ ~EchoSuppressor();
+ void run(int16_t *playbacked, int16_t *recorded);
+
+private:
+ int mScale;
+ int mSampleCount;
+ int mWindowSize;
+ int mTailLength;
+ int mRecordLength;
+ int mRecordOffset;
+
+ float *mXs;
+ float *mXYs;
+ float *mXXs;
+ float mYY;
+
+ float *mXYRecords;
+ float *mXXRecords;
+ float *mYYRecords;
+
+ float mLastX;
+ float mLastY;
+};
+
+#endif
diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp
index 091afa9..a467acf 100644
--- a/voip/jni/rtp/G711Codec.cpp
+++ b/voip/jni/rtp/G711Codec.cpp
@@ -18,7 +18,7 @@
namespace {
-int8_t gExponents[128] = {
+const int8_t gExponents[128] = {
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp
index 33b88e43..f5efc17 100644
--- a/voip/jni/rtp/RtpStream.cpp
+++ b/voip/jni/rtp/RtpStream.cpp
@@ -88,13 +88,11 @@
jint dup(JNIEnv *env, jobject thiz)
{
- int socket1 = env->GetIntField(thiz, gNative);
- int socket2 = ::dup(socket1);
- if (socket2 == -1) {
+ int socket = ::dup(env->GetIntField(thiz, gNative));
+ if (socket == -1) {
jniThrowException(env, "java/lang/IllegalStateException", strerror(errno));
}
- LOGD("dup %d to %d", socket1, socket2);
- return socket2;
+ return socket;
}
void close(JNIEnv *env, jobject thiz)
@@ -102,7 +100,6 @@
int socket = env->GetIntField(thiz, gNative);
::close(socket);
env->SetIntField(thiz, gNative, -1);
- LOGD("close %d", socket);
}
JNINativeMethod gMethods[] = {