diff options
410 files changed, 19509 insertions, 7553 deletions
diff --git a/Android.mk b/Android.mk index 7c29c73721c3..65f7a35550be 100644 --- a/Android.mk +++ b/Android.mk @@ -123,6 +123,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/storage/IMountService.aidl \ core/java/android/os/storage/IMountServiceListener.aidl \ core/java/android/os/storage/IMountShutdownObserver.aidl \ + core/java/android/os/storage/IObbActionListener.aidl \ core/java/android/os/INetworkManagementService.aidl \ core/java/android/os/INetStatService.aidl \ core/java/android/os/IPermissionController.aidl \ @@ -368,7 +369,7 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -since ./frameworks/base/api/7.xml 7 \ -since ./frameworks/base/api/8.xml 8 \ -since ./frameworks/base/api/current.xml HC \ - -error 1 -error 2 -warning 3 -error 4 -error 6 -error 8 \ + -error 101 -error 102 -warning 103 -error 104 -error 106 -error 108 \ -overview $(LOCAL_PATH)/core/java/overview.html framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:=$(call intermediates-dir-for,JAVA_LIBRARIES,framework) diff --git a/CleanSpec.mk b/CleanSpec.mk index 1b7daa68e3f3..f73e4d5d555c 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -68,6 +68,14 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverb_inte $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverbtest_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/soundfx/) $(call add-clean-step, find . -type f -name "*.rs" -print0 | xargs -0 touch) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libandroid_runtime_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libandroid_runtime.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libandroid_runtime.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libandroid_runtime.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libhwui_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libhwui.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/api/9.xml b/api/9.xml index abb67f9a6359..f151a1695ba6 100644 --- a/api/9.xml +++ b/api/9.xml @@ -58593,16 +58593,6 @@ <parameter name="bitmap" type="android.graphics.Bitmap"> </parameter> </constructor> -<constructor name="Canvas" - type="android.graphics.Canvas" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="gl" type="javax.microedition.khronos.opengles.GL"> -</parameter> -</constructor> <method name="clipPath" return="boolean" abstract="false" @@ -59519,17 +59509,6 @@ <parameter name="paint" type="android.graphics.Paint"> </parameter> </method> -<method name="freeGlCaches" - return="void" - abstract="false" - native="false" - synchronized="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="getClipBounds" return="boolean" abstract="false" @@ -59576,17 +59555,6 @@ visibility="public" > </method> -<method name="getGL" - return="javax.microedition.khronos.opengles.GL" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="getHeight" return="int" abstract="false" @@ -59950,21 +59918,6 @@ <parameter name="matrix" type="android.graphics.Matrix"> </parameter> </method> -<method name="setViewport" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="width" type="int"> -</parameter> -<parameter name="height" type="int"> -</parameter> -</method> <method name="skew" return="void" abstract="false" diff --git a/api/current.xml b/api/current.xml index cc2bb3c12984..2c88f11e9877 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1482,6 +1482,28 @@ visibility="public" > </field> +<field name="animator_fade_in" + type="int" + transient="false" + volatile="false" + value="17432609" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="animator_fade_out" + type="int" + transient="false" + volatile="false" + value="17432610" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="anticipate_interpolator" type="int" transient="false" @@ -4310,6 +4332,50 @@ visibility="public" > </field> +<field name="fragmentCloseEnterAnimation" + type="int" + transient="false" + volatile="false" + value="16843561" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="fragmentCloseExitAnimation" + type="int" + transient="false" + volatile="false" + value="16843562" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="fragmentOpenEnterAnimation" + type="int" + transient="false" + volatile="false" + value="16843559" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="fragmentOpenExitAnimation" + type="int" + transient="false" + volatile="false" + value="16843560" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="freezesText" type="int" transient="false" @@ -10580,6 +10646,17 @@ visibility="public" > </field> +<field name="windowActionBarOverlay" + type="int" + transient="false" + volatile="false" + value="16843558" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="windowActionBarStyle" type="int" transient="false" @@ -20795,6 +20872,19 @@ <parameter name="sequenceItems" type="android.animation.Animatable..."> </parameter> </method> +<method name="setTarget" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="target" type="java.lang.Object"> +</parameter> +</method> </class> <class name="Sequencer.Builder" extends="java.lang.Object" @@ -20936,6 +21026,17 @@ visibility="public" > </method> +<method name="getHeight" + return="int" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getNavigationMode" return="int" abstract="true" @@ -20980,6 +21081,17 @@ visibility="public" > </method> +<method name="hide" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="insertTab" return="void" abstract="true" @@ -20995,6 +21107,17 @@ <parameter name="position" type="int"> </parameter> </method> +<method name="isShowing" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="newTab" return="android.app.ActionBar.Tab" abstract="true" @@ -21287,6 +21410,17 @@ <parameter name="resId" type="int"> </parameter> </method> +<method name="show" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <field name="DISPLAY_HIDE_HOME" type="int" transient="false" @@ -26353,6 +26487,17 @@ <parameter name="id" type="int"> </parameter> </method> +<method name="getActionBar" + return="android.app.ActionBar" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getContext" return="android.content.Context" abstract="false" @@ -27839,8 +27984,8 @@ <parameter name="savedInstanceState" type="android.os.Bundle"> </parameter> </method> -<method name="onCreateAnimation" - return="android.view.animation.Animation" +<method name="onCreateAnimatable" + return="android.animation.Animatable" abstract="false" native="false" synchronized="false" @@ -28346,39 +28491,6 @@ <parameter name="fragment" type="android.app.Fragment"> </parameter> </method> -<field name="TRANSIT_ACTIVITY_CLOSE" - type="int" - transient="false" - volatile="false" - value="8199" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_ACTIVITY_OPEN" - type="int" - transient="false" - volatile="false" - value="4102" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_ENTER" - type="int" - transient="false" - volatile="false" - value="4097" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TRANSIT_ENTER_MASK" type="int" transient="false" @@ -28390,17 +28502,6 @@ visibility="public" > </field> -<field name="TRANSIT_EXIT" - type="int" - transient="false" - volatile="false" - value="8194" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TRANSIT_EXIT_MASK" type="int" transient="false" @@ -28412,88 +28513,33 @@ visibility="public" > </field> -<field name="TRANSIT_HIDE" - type="int" - transient="false" - volatile="false" - value="8196" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_NONE" - type="int" - transient="false" - volatile="false" - value="0" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_PREVIEW_DONE" - type="int" - transient="false" - volatile="false" - value="5" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_SHOW" - type="int" - transient="false" - volatile="false" - value="4099" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_TASK_CLOSE" - type="int" - transient="false" - volatile="false" - value="8201" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_TASK_OPEN" +<field name="TRANSIT_FRAGMENT_CLOSE" type="int" transient="false" volatile="false" - value="4104" + value="8194" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="TRANSIT_TASK_TO_BACK" +<field name="TRANSIT_FRAGMENT_OPEN" type="int" transient="false" volatile="false" - value="8203" + value="4097" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="TRANSIT_TASK_TO_FRONT" +<field name="TRANSIT_NONE" type="int" transient="false" volatile="false" - value="4106" + value="0" static="true" final="true" deprecated="not deprecated" @@ -28511,50 +28557,6 @@ visibility="public" > </field> -<field name="TRANSIT_WALLPAPER_CLOSE" - type="int" - transient="false" - volatile="false" - value="8204" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_WALLPAPER_INTRA_CLOSE" - type="int" - transient="false" - volatile="false" - value="8207" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_WALLPAPER_INTRA_OPEN" - type="int" - transient="false" - volatile="false" - value="4110" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -<field name="TRANSIT_WALLPAPER_OPEN" - type="int" - transient="false" - volatile="false" - value="4109" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> </interface> <class name="Instrumentation" extends="java.lang.Object" @@ -30658,6 +30660,17 @@ <parameter name="holder" type="android.view.SurfaceHolder"> </parameter> </method> +<field name="KEY_NATIVE_SAVED_STATE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android:native_state"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="META_DATA_LIB_NAME" type="java.lang.String" transient="false" @@ -38871,7 +38884,7 @@ synchronized="false" static="false" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > </method> @@ -39106,6 +39119,19 @@ <parameter name="uri" type="android.net.Uri"> </parameter> </constructor> +<method name="coerceToText" + return="java.lang.CharSequence" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</method> <method name="getIntent" return="android.content.Intent" abstract="false" @@ -39463,6 +39489,21 @@ <parameter name="values" type="android.content.ContentValues[]"> </parameter> </method> +<method name="compareMimeTypes" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="concreteType" type="java.lang.String"> +</parameter> +<parameter name="desiredType" type="java.lang.String"> +</parameter> +</method> <method name="delete" return="int" abstract="true" @@ -39513,6 +39554,21 @@ visibility="public" > </method> +<method name="getStreamTypes" + return="java.lang.String[]" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeTypeFilter" type="java.lang.String"> +</parameter> +</method> <method name="getType" return="java.lang.String" abstract="true" @@ -39649,6 +39705,48 @@ <exception name="FileNotFoundException" type="java.io.FileNotFoundException"> </exception> </method> +<method name="openPipeHelper" + return="android.os.ParcelFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeType" type="java.lang.String"> +</parameter> +<parameter name="opts" type="android.os.Bundle"> +</parameter> +<parameter name="args" type="T"> +</parameter> +<parameter name="func" type="android.content.ContentProvider.PipeDataWriter<T>"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +</method> +<method name="openTypedAssetFile" + return="android.content.res.AssetFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeTypeFilter" type="java.lang.String"> +</parameter> +<parameter name="opts" type="android.os.Bundle"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +</method> <method name="query" return="android.database.Cursor" abstract="true" @@ -39740,6 +39838,35 @@ </parameter> </method> </class> +<interface name="ContentProvider.PipeDataWriter" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="writeDataToPipe" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="output" type="android.os.ParcelFileDescriptor"> +</parameter> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeType" type="java.lang.String"> +</parameter> +<parameter name="opts" type="android.os.Bundle"> +</parameter> +<parameter name="args" type="T"> +</parameter> +</method> +</interface> <class name="ContentProviderClient" extends="java.lang.Object" abstract="false" @@ -39812,6 +39939,23 @@ visibility="public" > </method> +<method name="getStreamTypes" + return="java.lang.String[]" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="url" type="android.net.Uri"> +</parameter> +<parameter name="mimeTypeFilter" type="java.lang.String"> +</parameter> +<exception name="RemoteException" type="android.os.RemoteException"> +</exception> +</method> <method name="getType" return="java.lang.String" abstract="false" @@ -39882,6 +40026,27 @@ <exception name="RemoteException" type="android.os.RemoteException"> </exception> </method> +<method name="openTypedAssetFileDescriptor" + return="android.content.res.AssetFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeType" type="java.lang.String"> +</parameter> +<parameter name="opts" type="android.os.Bundle"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +<exception name="RemoteException" type="android.os.RemoteException"> +</exception> +</method> <method name="query" return="android.database.Cursor" abstract="false" @@ -40652,6 +40817,21 @@ <parameter name="authority" type="java.lang.String"> </parameter> </method> +<method name="getStreamTypes" + return="java.lang.String[]" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="url" type="android.net.Uri"> +</parameter> +<parameter name="mimeTypeFilter" type="java.lang.String"> +</parameter> +</method> <method name="getSyncAdapterTypes" return="android.content.SyncAdapterType[]" abstract="false" @@ -40849,6 +41029,25 @@ <exception name="FileNotFoundException" type="java.io.FileNotFoundException"> </exception> </method> +<method name="openTypedAssetFileDescriptor" + return="android.content.res.AssetFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="mimeType" type="java.lang.String"> +</parameter> +<parameter name="opts" type="android.os.Bundle"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +</method> <method name="query" return="android.database.Cursor" abstract="false" @@ -47262,6 +47461,17 @@ visibility="public" > </field> +<field name="ACTION_PASTE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.PASTE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_PICK" type="java.lang.String" transient="false" @@ -61715,6 +61925,21 @@ <parameter name="sqlString" type="java.lang.String"> </parameter> </method> +<method name="appendSelectionArgs" + return="java.lang.String[]" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="originalValues" type="java.lang.String[]"> +</parameter> +<parameter name="newValues" type="java.lang.String[]"> +</parameter> +</method> <method name="appendValueToSql" return="void" abstract="false" @@ -68809,16 +69034,6 @@ <parameter name="bitmap" type="android.graphics.Bitmap"> </parameter> </constructor> -<constructor name="Canvas" - type="android.graphics.Canvas" - static="false" - final="false" - deprecated="deprecated" - visibility="public" -> -<parameter name="gl" type="javax.microedition.khronos.opengles.GL"> -</parameter> -</constructor> <method name="clipPath" return="boolean" abstract="false" @@ -69735,17 +69950,6 @@ <parameter name="paint" type="android.graphics.Paint"> </parameter> </method> -<method name="freeGlCaches" - return="void" - abstract="false" - native="false" - synchronized="false" - static="true" - final="false" - deprecated="deprecated" - visibility="public" -> -</method> <method name="getClipBounds" return="boolean" abstract="false" @@ -69800,7 +70004,7 @@ static="false" final="false" deprecated="deprecated" - visibility="public" + visibility="protected" > </method> <method name="getHeight" @@ -70177,21 +70381,6 @@ <parameter name="matrix" type="android.graphics.Matrix"> </parameter> </method> -<method name="setViewport" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="deprecated" - visibility="public" -> -<parameter name="width" type="int"> -</parameter> -<parameter name="height" type="int"> -</parameter> -</method> <method name="skew" return="void" abstract="false" @@ -91630,7 +91819,7 @@ type="int" transient="false" volatile="false" - value="1" + value="0" static="true" final="true" deprecated="not deprecated" @@ -91641,7 +91830,7 @@ type="int" transient="false" volatile="false" - value="0" + value="1" static="true" final="true" deprecated="not deprecated" @@ -102917,19 +103106,6 @@ <parameter name="value" type="java.lang.String"> </parameter> </method> -<method name="setShowNotification" - return="android.net.DownloadManager.Request" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="flags" type="int"> -</parameter> -</method> <method name="setTitle" return="android.net.DownloadManager.Request" abstract="false" @@ -102976,17 +103152,6 @@ visibility="public" > </field> -<field name="NOTIFICATION_WHEN_RUNNING" - type="int" - transient="false" - volatile="false" - value="1" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> </class> <class name="LocalServerSocket" extends="java.lang.Object" @@ -104426,6 +104591,21 @@ visibility="public" > </method> +<method name="getBooleanQueryParameter" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="key" type="java.lang.String"> +</parameter> +<parameter name="defaultValue" type="boolean"> +</parameter> +</method> <method name="getEncodedAuthority" return="java.lang.String" abstract="true" @@ -133993,6 +134173,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="createPipe" + return="android.os.ParcelFileDescriptor[]" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> <method name="describeContents" return="int" abstract="false" @@ -135983,6 +136176,8 @@ > <parameter name="filename" type="java.lang.String"> </parameter> +<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException"> +</exception> </method> <method name="isUsbMassStorageConnected" return="boolean" @@ -136048,6 +136243,8 @@ </parameter> <parameter name="force" type="boolean"> </parameter> +<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException"> +</exception> </method> <method name="unregisterListener" return="void" @@ -180378,6 +180575,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="setLenient" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="lenient" type="boolean"> +</parameter> +</method> <method name="skipValue" return="void" abstract="false" @@ -180448,6 +180658,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="java.io.Closeable"> +</implements> <constructor name="JsonWriter" type="android.util.JsonWriter" static="false" @@ -180564,7 +180776,7 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> -<method name="setIndentSpaces" +<method name="setIndent" return="void" abstract="false" native="false" @@ -180574,7 +180786,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="indent" type="int"> +<parameter name="indent" type="java.lang.String"> </parameter> </method> <method name="value" @@ -193505,6 +193717,28 @@ visibility="public" > </method> +<method name="getRotationX" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getRotationY" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getScaleX" return="float" abstract="false" @@ -195675,6 +195909,32 @@ <parameter name="rotation" type="float"> </parameter> </method> +<method name="setRotationX" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="rotationX" type="float"> +</parameter> +</method> +<method name="setRotationY" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="rotationY" type="float"> +</parameter> +</method> <method name="setSaveEnabled" return="void" abstract="false" @@ -200879,7 +201139,7 @@ visibility="public" > </field> -<field name="FEATURE_ACTION_MODE_OVERLAY" +<field name="FEATURE_ACTION_BAR_OVERLAY" type="int" transient="false" volatile="false" @@ -200890,6 +201150,17 @@ visibility="public" > </field> +<field name="FEATURE_ACTION_MODE_OVERLAY" + type="int" + transient="false" + volatile="false" + value="10" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FEATURE_CONTEXT_MENU" type="int" transient="false" @@ -200916,7 +201187,7 @@ type="int" transient="false" volatile="false" - value="10" + value="11" static="true" final="true" deprecated="not deprecated" @@ -213060,6 +213331,17 @@ visibility="public" > </method> +<method name="getVisibleTitleHeight" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getZoomControls" return="android.view.View" abstract="false" @@ -216383,7 +216665,7 @@ </interface> <class name="AdapterViewAnimator" extends="android.widget.AdapterView" - abstract="false" + abstract="true" static="false" final="false" deprecated="not deprecated" @@ -216433,28 +216715,6 @@ visibility="public" > </method> -<method name="getDefaultInAnimation" - return="android.view.animation.Animation" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> -<method name="getDefaultOutAnimation" - return="android.view.animation.Animation" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="getDisplayedChild" return="int" abstract="false" @@ -216653,23 +216913,6 @@ visibility="public" > </method> -<method name="showOnly" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -<parameter name="childIndex" type="int"> -</parameter> -<parameter name="animate" type="boolean"> -</parameter> -<parameter name="onLayout" type="boolean"> -</parameter> -</method> <method name="showPrevious" return="void" abstract="false" @@ -227845,6 +228088,22 @@ <parameter name="autoRequery" type="boolean"> </parameter> </constructor> +<constructor name="ResourceCursorAdapter" + type="android.widget.ResourceCursorAdapter" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="layout" type="int"> +</parameter> +<parameter name="c" type="android.database.Cursor"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</constructor> <method name="newView" return="android.view.View" abstract="false" @@ -229975,6 +230234,37 @@ </parameter> </method> </interface> +<class name="StackView" + extends="android.widget.AdapterViewAnimator" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="StackView" + type="android.widget.StackView" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</constructor> +<constructor name="StackView" + type="android.widget.StackView" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="attrs" type="android.util.AttributeSet"> +</parameter> +</constructor> +</class> <class name="TabHost" extends="android.widget.FrameLayout" abstract="false" @@ -272652,7 +272942,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="loop" type="boolean"> +<parameter name="disable" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception> @@ -274211,7 +274501,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="value" type="boolean"> +<parameter name="keepAlive" type="boolean"> </parameter> <exception name="SocketException" type="java.net.SocketException"> </exception> diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index b0adaec3cadd..fd3a0d046486 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -45,26 +45,26 @@ import com.google.android.collect.Maps; /** * This class provides access to a centralized registry of the user's - * online accounts. With this service, users only need to enter their - * credentials (username and password) once for any account, granting - * applications access to online resources with "one-click" approval. + * online accounts. The user enters credentials (username and password) once + * per account, granting applications access to online resources with + * "one-click" approval. * * <p>Different online services have different ways of handling accounts and * authentication, so the account manager uses pluggable <em>authenticator</em> - * modules for different <em>account types</em>. The authenticators (which - * may be written by third parties) handle the actual details of validating - * account credentials and storing account information. For example, Google, - * Facebook, and Microsoft Exchange each have their own authenticator. + * modules for different <em>account types</em>. Authenticators (which may be + * written by third parties) handle the actual details of validating account + * credentials and storing account information. For example, Google, Facebook, + * and Microsoft Exchange each have their own authenticator. * * <p>Many servers support some notion of an <em>authentication token</em>, * which can be used to authenticate a request to the server without sending * the user's actual password. (Auth tokens are normally created with a * separate request which does include the user's credentials.) AccountManager - * can generate these auth tokens for applications, so the application doesn't - * need to handle passwords directly. Auth tokens are normally reusable, and - * cached by AccountManager, but must be refreshed periodically. It's the - * responsibility of applications to <em>invalidate</em> auth tokens when they - * stop working so the AccountManager knows it needs to regenerate them. + * can generate auth tokens for applications, so the application doesn't need to + * handle passwords directly. Auth tokens are normally reusable and cached by + * AccountManager, but must be refreshed periodically. It's the responsibility + * of applications to <em>invalidate</em> auth tokens when they stop working so + * the AccountManager knows it needs to regenerate them. * * <p>Applications accessing a server normally go through these steps: * @@ -84,14 +84,19 @@ import com.google.android.collect.Maps; * {@link #addAccount} may be called to prompt the user to create an * account of the appropriate type. * + * <li><b>Important:</b> If the application is using a previously remembered + * account selection, it must make sure the account is still in the list + * of accounts returned by {@link #getAccountsByType}. Requesting an auth token + * for an account no longer on the device results in an undefined failure. + * * <li>Request an auth token for the selected account(s) using one of the * {@link #getAuthToken} methods or related helpers. Refer to the description * of each method for exact usage and error handling details. * * <li>Make the request using the auth token. The form of the auth token, * the format of the request, and the protocol used are all specific to the - * service you are accessing. The application makes the request itself, using - * whatever network and protocol libraries are useful. + * service you are accessing. The application may use whatever network and + * protocol libraries are useful. * * <li><b>Important:</b> If the request fails with an authentication error, * it could be that a cached auth token is stale and no longer honored by @@ -103,7 +108,7 @@ import com.google.android.collect.Maps; * appropriate actions taken. * </ul> * - * <p>Some AccountManager methods may require interaction with the user to + * <p>Some AccountManager methods may need to interact with the user to * prompt for credentials, present options, or ask the user to add an account. * The caller may choose whether to allow AccountManager to directly launch the * necessary user interface and wait for the user, or to return an Intent which @@ -113,18 +118,17 @@ import com.google.android.collect.Maps; * the current foreground {@link Activity} context. * * <p>Many AccountManager methods take {@link AccountManagerCallback} and - * {@link Handler} as parameters. These methods return immediately but + * {@link Handler} as parameters. These methods return immediately and * run asynchronously. If a callback is provided then * {@link AccountManagerCallback#run} will be invoked on the Handler's * thread when the request completes, successfully or not. - * An {@link AccountManagerFuture} is returned by these requests and also - * supplied to the callback (if any). The result is retrieved by calling - * {@link AccountManagerFuture#getResult()} which waits for the operation - * to complete (if necessary) and either returns the result or throws an - * exception if an error occurred during the operation. - * To make the request synchronously, call + * The result is retrieved by calling {@link AccountManagerFuture#getResult()} + * on the {@link AccountManagerFuture} returned by the method (and also passed + * to the callback). This method waits for the operation to complete (if + * necessary) and either returns the result or throws an exception if an error + * occurred during the operation. To make the request synchronously, call * {@link AccountManagerFuture#getResult()} immediately on receiving the - * future from the method. No callback need be supplied. + * future from the method; no callback need be supplied. * * <p>Requests which may block, including * {@link AccountManagerFuture#getResult()}, must never be called on @@ -143,32 +147,32 @@ public class AccountManager { public static final int ERROR_CODE_BAD_REQUEST = 8; /** - * The Bundle key used for the {@link String} account name in results + * Bundle key used for the {@link String} account name in results * from methods which return information about a particular account. */ public static final String KEY_ACCOUNT_NAME = "authAccount"; /** - * The Bundle key used for the {@link String} account type in results + * Bundle key used for the {@link String} account type in results * from methods which return information about a particular account. */ public static final String KEY_ACCOUNT_TYPE = "accountType"; /** - * The Bundle key used for the auth token value in results + * Bundle key used for the auth token value in results * from {@link #getAuthToken} and friends. */ public static final String KEY_AUTHTOKEN = "authtoken"; /** - * The Bundle key used for an {@link Intent} in results from methods that + * Bundle key used for an {@link Intent} in results from methods that * may require the caller to interact with the user. The Intent can * be used to start the corresponding user interface activity. */ public static final String KEY_INTENT = "intent"; /** - * The Bundle key used to supply the password directly in options to + * Bundle key used to supply the password directly in options to * {@link #confirmCredentials}, rather than prompting the user with * the standard password prompt. */ @@ -476,7 +480,7 @@ public class AccountManager { * @param account The {@link Account} to add * @param password The password to associate with the account, null for none * @param userdata String values to use for the account's userdata, null for none - * @return Whether the account was successfully added. False if the account + * @return True if the account was successfully added, false if the account * already exists, the account is null, or another error occurs. */ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) { @@ -733,15 +737,14 @@ public class AccountManager { * sense to ask the user directly for a password. * * <p>If a previously generated auth token is cached for this account and - * type, then it will be returned. Otherwise, if we have a saved password - * the server accepts, it will be used to generate a new auth token. - * Otherwise, the user will be asked for a password, which will be sent to - * the server to generate a new auth token. + * type, then it is returned. Otherwise, if a saved password is + * available, it is sent to the server to generate a new auth token. + * Otherwise, the user is prompted to enter a password. * - * <p>The value of the auth token type depends on the authenticator. - * Some services use different tokens to access different functionality -- - * for example, Google uses different auth tokens to access Gmail and - * Google Calendar for the same account. + * <p>Some authenticators have auth token <em>types</em>, whose value + * is authenticator-dependent. Some services use different token types to + * access different functionality -- for example, Google uses different auth + * tokens to access Gmail and Google Calendar for the same account. * * <p>This method may be called from any thread, but the returned * {@link AccountManagerFuture} must not be used on the main thread. @@ -778,6 +781,9 @@ public class AccountManager { * <li> {@link IOException} if the authenticator experienced an I/O problem * creating a new auth token, usually because of network trouble * </ul> + * If the account is no longer present on the device, the return value is + * authenticator-dependent. The caller should verify the validity of the + * account before requesting an auth token. */ public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final Bundle options, @@ -800,29 +806,27 @@ public class AccountManager { * user should not be immediately interrupted with a password prompt. * * <p>If a previously generated auth token is cached for this account and - * type, then it will be returned. Otherwise, if we have saved credentials - * the server accepts, it will be used to generate a new auth token. - * Otherwise, an Intent will be returned which, when started, will prompt - * the user for a password. If the notifyAuthFailure parameter is set, - * the same Intent will be associated with a status bar notification, + * type, then it is returned. Otherwise, if a saved password is + * available, it is sent to the server to generate a new auth token. + * Otherwise, an {@link Intent} is returned which, when started, will + * prompt the user for a password. If the notifyAuthFailure parameter is + * set, a status bar notification is also created with the same Intent, * alerting the user that they need to enter a password at some point. * - * <p>If the intent is left in a notification, you will need to wait until - * the user gets around to entering a password before trying again, - * which could be hours or days or never. When it does happen, the - * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} - * {@link Intent}, which applications can use to trigger another attempt - * to fetch an auth token. + * <p>In that case, you may need to wait until the user responds, which + * could take hours or days or forever. When the user does respond and + * supply a new password, the account manager will broadcast the + * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can + * use to try again. * - * <p>If notifications are not enabled, it is the application's - * responsibility to launch the returned intent at some point to let - * the user enter credentials. In either case, the result from this - * call will not wait for user action. + * <p>If notifyAuthFailure is not set, it is the application's + * responsibility to launch the returned Intent at some point. + * Either way, the result from this call will not wait for user action. * - * <p>The value of the auth token type depends on the authenticator. - * Some services use different tokens to access different functionality -- - * for example, Google uses different auth tokens to access Gmail and - * Google Calendar for the same account. + * <p>Some authenticators have auth token <em>types</em>, whose value + * is authenticator-dependent. Some services use different token types to + * access different functionality -- for example, Google uses different auth + * tokens to access Gmail and Google Calendar for the same account. * * <p>This method may be called from any thread, but the returned * {@link AccountManagerFuture} must not be used on the main thread. @@ -851,7 +855,7 @@ public class AccountManager { * must enter credentials, the returned Bundle contains only * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt. * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * If an error occurred, {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation is canceled for @@ -859,6 +863,9 @@ public class AccountManager { * <li> {@link IOException} if the authenticator experienced an I/O problem * creating a new auth token, usually because of network trouble * </ul> + * If the account is no longer present on the device, the return value is + * authenticator-dependent. The caller should verify the validity of the + * account before requesting an auth token. */ public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final boolean notifyAuthFailure, @@ -910,9 +917,8 @@ public class AccountManager { * * If no activity was specified, the returned Bundle contains only * {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * actual account creation process. - * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * actual account creation process. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if no authenticator was registered for * this account type or the authenticator failed to respond @@ -979,9 +985,8 @@ public class AccountManager { * * If no activity or password was specified, the returned Bundle contains * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. - * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * password prompt. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation was canceled for @@ -1040,9 +1045,8 @@ public class AccountManager { * * If no activity was specified, the returned Bundle contains only * {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. - * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * password prompt. If an error occurred, + * {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation was canceled for @@ -1091,8 +1095,8 @@ public class AccountManager { * which is empty if properties were edited successfully, or * if no activity was specified, contains only {@link #KEY_INTENT} * needed to launch the authenticator's settings dialog. - * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * If an error occurred, {@link AccountManagerFuture#getResult()} + * throws: * <ul> * <li> {@link AuthenticatorException} if no authenticator was registered for * this account type or the authenticator failed to respond @@ -1617,7 +1621,7 @@ public class AccountManager { * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted * </ul> * - * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * If an error occurred, {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if no authenticator was registered for * this account type or the authenticator failed to respond diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java index 3278a3e612d3..2406d8af5722 100644 --- a/core/java/android/animation/Sequencer.java +++ b/core/java/android/animation/Sequencer.java @@ -140,6 +140,24 @@ public final class Sequencer extends Animatable { } /** + * Sets the target object for all current {@link #getChildAnimations() child animations} + * of this Sequencer that take targets ({@link android.animation.PropertyAnimator} and + * Sequencer). + * + * @param target The object being animated + */ + public void setTarget(Object target) { + for (Node node : mNodes) { + Animatable animation = node.animation; + if (animation instanceof Sequencer) { + ((Sequencer)animation).setTarget(target); + } else if (animation instanceof PropertyAnimator) { + ((PropertyAnimator)animation).setTarget(target); + } + } + } + + /** * This method creates a <code>Builder</code> object, which is used to * set up playing constraints. This initial <code>play()</code> method * tells the <code>Builder</code> the animation that is the dependency for diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index d33494b7ac51..38086f072745 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -18,6 +18,7 @@ package android.app; import android.graphics.drawable.Drawable; import android.view.View; +import android.view.Window; import android.widget.SpinnerAdapter; /** @@ -383,6 +384,34 @@ public abstract class ActionBar { public abstract void selectTab(Tab tab); /** + * Retrieve the current height of the ActionBar. + * + * @return The ActionBar's height + */ + public abstract int getHeight(); + + /** + * Show the ActionBar if it is not currently showing. + * If the window hosting the ActionBar does not have the feature + * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application + * content to fit the new space available. + */ + public abstract void show(); + + /** + * Hide the ActionBar if it is not currently showing. + * If the window hosting the ActionBar does not have the feature + * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application + * content to fit the new space available. + */ + public abstract void hide(); + + /** + * @return <code>true</code> if the ActionBar is showing, <code>false</code> otherwise. + */ + public abstract boolean isShowing(); + + /** * Callback interface for ActionBar navigation events. */ public interface NavigationCallback { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d49adc23554e..d7bab1bc8080 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1748,13 +1748,11 @@ public class Activity extends ContextThemeWrapper /** * Retrieve a reference to this activity's ActionBar. - * - * <p><em>Note:</em> The ActionBar is initialized when a content view - * is set. This function will return null if called before {@link #setContentView} - * or {@link #addContentView}. + * * @return The Activity's ActionBar, or null if it does not have one. */ public ActionBar getActionBar() { + initActionBar(); return mActionBar; } @@ -1764,7 +1762,7 @@ public class Activity extends ContextThemeWrapper */ private void initActionBar() { Window window = getWindow(); - if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { + if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } @@ -2074,7 +2072,7 @@ public class Activity extends ContextThemeWrapper * to pop, else false. */ public boolean popBackStack() { - return popBackStack(null, -1); + return popBackStack(null, 0); } /** @@ -4093,6 +4091,7 @@ public class Activity extends ContextThemeWrapper } public ActionMode onStartActionMode(ActionMode.Callback callback) { + initActionBar(); if (mActionBar != null) { return mActionBar.startActionMode(callback); } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index a9420b4edeff..b4c138e1660e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -16,6 +16,7 @@ package android.app; +import com.android.internal.app.ActionBarImpl; import com.android.internal.policy.PolicyManager; import android.content.ComponentName; @@ -27,9 +28,9 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.ActionMode; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; @@ -77,6 +78,7 @@ public class Dialog implements DialogInterface, Window.Callback, final WindowManager mWindowManager; Window mWindow; View mDecor; + private ActionBarImpl mActionBar; /** * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -178,6 +180,15 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * Retrieve the {@link ActionBar} attached to this dialog, if present. + * + * @return The ActionBar attached to the dialog or null if no ActionBar is present. + */ + public ActionBar getActionBar() { + return mActionBar; + } + + /** * Sets the Activity that owns this dialog. An example use: This Dialog will * use the suggested volume control stream of the Activity. * @@ -228,6 +239,11 @@ public class Dialog implements DialogInterface, Window.Callback, onStart(); mDecor = mWindow.getDecorView(); + + if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { + mActionBar = new ActionBarImpl(this); + } + WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { @@ -834,12 +850,14 @@ public class Dialog implements DialogInterface, Window.Callback, } public ActionMode onStartActionMode(ActionMode.Callback callback) { - // TODO Support context modes in dialogs + if (mActionBar != null) { + return mActionBar.startActionMode(callback); + } return null; } /** - * @return The activity associated with this dialog, or null if there is no assocaited activity. + * @return The activity associated with this dialog, or null if there is no associated activity. */ private ComponentName getAssociatedActivity() { Activity activity = mOwnerActivity; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 0bb200c95353..2f61345e4195 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -16,6 +16,7 @@ package android.app; +import android.animation.Animatable; import android.content.ComponentCallbacks; import android.content.Context; import android.content.Intent; @@ -35,7 +36,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.ContextMenu.ContextMenuInfo; import android.view.View.OnCreateContextMenuListener; -import android.view.animation.Animation; import android.widget.AdapterView; import java.lang.reflect.InvocationTargetException; @@ -495,7 +495,10 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener mCalled = true; } - public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { + /** + * Called when a fragment loads an animation. + */ + public Animatable onCreateAnimatable(int transit, boolean enter, int nextAnim) { return null; } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 35b461011f66..54e37b0df573 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -16,6 +16,9 @@ package android.app; +import android.animation.Animatable; +import android.animation.PropertyAnimator; +import android.animation.Sequencer; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; @@ -28,7 +31,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.animation.Animation; import android.view.animation.AnimationUtils; import java.util.ArrayList; @@ -106,16 +108,16 @@ public class FragmentManager { } }; - Animation loadAnimation(Fragment fragment, int transit, boolean enter, + Animatable loadAnimatable(Fragment fragment, int transit, boolean enter, int transitionStyle) { - Animation animObj = fragment.onCreateAnimation(transitionStyle, enter, + Animatable animObj = fragment.onCreateAnimatable(transit, enter, fragment.mNextAnim); if (animObj != null) { return animObj; } if (fragment.mNextAnim != 0) { - Animation anim = AnimationUtils.loadAnimation(mActivity, fragment.mNextAnim); + Animatable anim = AnimationUtils.loadAnimator(mActivity, fragment.mNextAnim); if (anim != null) { return anim; } @@ -138,7 +140,7 @@ public class FragmentManager { } TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle, - com.android.internal.R.styleable.WindowAnimation); + com.android.internal.R.styleable.FragmentAnimation); int anim = attrs.getResourceId(styleIndex, 0); attrs.recycle(); @@ -146,7 +148,7 @@ public class FragmentManager { return null; } - return AnimationUtils.loadAnimation(mActivity, anim); + return AnimationUtils.loadAnimator(mActivity, anim); } void moveToState(Fragment f, int newState, int transit, int transitionStyle) { @@ -208,10 +210,15 @@ public class FragmentManager { if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (container != null) { - Animation anim = loadAnimation(f, transit, true, + Animatable anim = loadAnimatable(f, transit, true, transitionStyle); if (anim != null) { - f.mView.setAnimation(anim); + if (anim instanceof Sequencer) { + ((Sequencer)anim).setTarget(f.mView); + } else if (anim instanceof PropertyAnimator) { + ((PropertyAnimator)anim).setTarget(f.mView); + } + anim.start(); } container.addView(f.mView); f.restoreViewState(); @@ -290,10 +297,15 @@ public class FragmentManager { } if (f.mContainer != null) { if (mCurState > Fragment.INITIALIZING) { - Animation anim = loadAnimation(f, transit, false, + Animatable anim = loadAnimatable(f, transit, true, transitionStyle); if (anim != null) { - f.mView.setAnimation(anim); + if (anim instanceof Sequencer) { + ((Sequencer)anim).setTarget(f.mView); + } else if (anim instanceof PropertyAnimator) { + ((PropertyAnimator)anim).setTarget(f.mView); + } + anim.start(); } } f.mContainer.removeView(f.mView); @@ -420,10 +432,15 @@ public class FragmentManager { if (!fragment.mHidden) { fragment.mHidden = true; if (fragment.mView != null) { - Animation anim = loadAnimation(fragment, transition, false, + Animatable anim = loadAnimatable(fragment, transition, true, transitionStyle); if (anim != null) { - fragment.mView.setAnimation(anim); + if (anim instanceof Sequencer) { + ((Sequencer)anim).setTarget(fragment.mView); + } else if (anim instanceof PropertyAnimator) { + ((PropertyAnimator)anim).setTarget(fragment.mView); + } + anim.start(); } fragment.mView.setVisibility(View.GONE); } @@ -439,10 +456,15 @@ public class FragmentManager { if (fragment.mHidden) { fragment.mHidden = false; if (fragment.mView != null) { - Animation anim = loadAnimation(fragment, transition, true, + Animatable anim = loadAnimatable(fragment, transition, true, transitionStyle); if (anim != null) { - fragment.mView.setAnimation(anim); + if (anim instanceof Sequencer) { + ((Sequencer)anim).setTarget(fragment.mView); + } else if (anim instanceof PropertyAnimator) { + ((PropertyAnimator)anim).setTarget(fragment.mView); + } + anim.start(); } fragment.mView.setVisibility(View.VISIBLE); } @@ -981,47 +1003,11 @@ public class FragmentManager { public static int reverseTransit(int transit) { int rev = 0; switch (transit) { - case FragmentTransaction.TRANSIT_ENTER: - rev = FragmentTransaction.TRANSIT_EXIT; - break; - case FragmentTransaction.TRANSIT_EXIT: - rev = FragmentTransaction.TRANSIT_ENTER; - break; - case FragmentTransaction.TRANSIT_SHOW: - rev = FragmentTransaction.TRANSIT_HIDE; - break; - case FragmentTransaction.TRANSIT_HIDE: - rev = FragmentTransaction.TRANSIT_SHOW; - break; - case FragmentTransaction.TRANSIT_ACTIVITY_OPEN: - rev = FragmentTransaction.TRANSIT_ACTIVITY_CLOSE; - break; - case FragmentTransaction.TRANSIT_ACTIVITY_CLOSE: - rev = FragmentTransaction.TRANSIT_ACTIVITY_OPEN; - break; - case FragmentTransaction.TRANSIT_TASK_OPEN: - rev = FragmentTransaction.TRANSIT_TASK_CLOSE; - break; - case FragmentTransaction.TRANSIT_TASK_CLOSE: - rev = FragmentTransaction.TRANSIT_TASK_OPEN; + case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: + rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE; break; - case FragmentTransaction.TRANSIT_TASK_TO_FRONT: - rev = FragmentTransaction.TRANSIT_TASK_TO_BACK; - break; - case FragmentTransaction.TRANSIT_TASK_TO_BACK: - rev = FragmentTransaction.TRANSIT_TASK_TO_FRONT; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_OPEN: - rev = FragmentTransaction.TRANSIT_WALLPAPER_CLOSE; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_CLOSE: - rev = FragmentTransaction.TRANSIT_WALLPAPER_OPEN; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN: - rev = FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE: - rev = FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN; + case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: + rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN; break; } return rev; @@ -1031,67 +1017,15 @@ public class FragmentManager { public static int transitToStyleIndex(int transit, boolean enter) { int animAttr = -1; switch (transit) { - case FragmentTransaction.TRANSIT_ENTER: - animAttr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation; - break; - case FragmentTransaction.TRANSIT_EXIT: - animAttr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation; - break; - case FragmentTransaction.TRANSIT_SHOW: - animAttr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation; - break; - case FragmentTransaction.TRANSIT_HIDE: - animAttr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation; - break; - case FragmentTransaction.TRANSIT_ACTIVITY_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; - break; - case FragmentTransaction.TRANSIT_ACTIVITY_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; - break; - case FragmentTransaction.TRANSIT_TASK_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation; - break; - case FragmentTransaction.TRANSIT_TASK_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation; - break; - case FragmentTransaction.TRANSIT_TASK_TO_FRONT: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation; - break; - case FragmentTransaction.TRANSIT_TASK_TO_BACK: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation; - break; - case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN: + case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; + ? com.android.internal.R.styleable.FragmentAnimation_fragmentOpenEnterAnimation + : com.android.internal.R.styleable.FragmentAnimation_fragmentOpenExitAnimation; break; - case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE: + case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; + ? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation + : com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation; break; } return animAttr; diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 65cf85cff724..04598a375c90 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -100,47 +100,11 @@ public interface FragmentTransaction { public final int TRANSIT_UNSET = -1; /** No animation for transition. */ public final int TRANSIT_NONE = 0; - /** Window has been added to the screen. */ - public final int TRANSIT_ENTER = 1 | TRANSIT_ENTER_MASK; - /** Window has been removed from the screen. */ - public final int TRANSIT_EXIT = 2 | TRANSIT_EXIT_MASK; - /** Window has been made visible. */ - public final int TRANSIT_SHOW = 3 | TRANSIT_ENTER_MASK; - /** Window has been made invisible. */ - public final int TRANSIT_HIDE = 4 | TRANSIT_EXIT_MASK; - /** The "application starting" preview window is no longer needed, and will - * animate away to show the real window. */ - public final int TRANSIT_PREVIEW_DONE = 5; - /** A window in a new activity is being opened on top of an existing one - * in the same task. */ - public final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK; - /** The window in the top-most activity is being closed to reveal the - * previous activity in the same task. */ - public final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK; - /** A window in a new task is being opened on top of an existing one - * in another activity's task. */ - public final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK; - /** A window in the top-most activity is being closed to reveal the - * previous activity in a different task. */ - public final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK; - /** A window in an existing task is being displayed on top of an existing one - * in another activity's task. */ - public final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK; - /** A window in an existing task is being put below all other tasks. */ - public final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK; - /** A window in a new activity that doesn't have a wallpaper is being - * opened on top of one that does, effectively closing the wallpaper. */ - public final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK; - /** A window in a new activity that does have a wallpaper is being - * opened on one that didn't, effectively opening the wallpaper. */ - public final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK; - /** A window in a new activity is being opened on top of an existing one, - * and both are on top of the wallpaper. */ - public final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK; - /** The window in the top-most activity is being closed to reveal the - * previous activity, and both are on top of he wallpaper. */ - public final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK; - + /** Fragment is being added */ + public final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK; + /** Fragment is being removed */ + public final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK; + public FragmentTransaction setCustomAnimations(int enter, int exit); public FragmentTransaction setTransition(int transit); diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index e7bdd8b2731e..28abcaa7735f 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -185,11 +185,14 @@ class LoaderManagerImpl implements LoaderManager { void stop() { if (DEBUG) Log.v(TAG, " Stopping: " + this); mStarted = false; - if (mLoader != null && mListenerRegistered) { - // Let the loader know we're done with it - mListenerRegistered = false; - mLoader.unregisterListener(this); - mLoader.stopLoading(); + if (!mRetaining) { + if (mLoader != null && mListenerRegistered) { + // Let the loader know we're done with it + mListenerRegistered = false; + mLoader.unregisterListener(this); + mLoader.stopLoading(); + } + mData = null; } } diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index eaf06757b6f7..4dc88b31f1d9 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -10,6 +10,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Build; import android.os.Bundle; @@ -32,12 +33,27 @@ import java.lang.ref.WeakReference; /** * Convenience for implementing an activity that will be implemented - * purely in native code. That is, a game (or game-like thing). + * purely in native code. That is, a game (or game-like thing). There + * is no need to derive from this class; you can simply declare it in your + * manifest, and use the NDK APIs from there. + * + * <p>A typical manifest would look like: + * + * {@sample development/ndk/platforms/android-9/samples/native-activity/AndroidManifest.xml + * manifest} + * + * <p>A very simple example of native code that is run by NativeActivity + * follows. This reads input events from the user and uses OpenGLES to + * draw into the native activity's window. + * + * {@sample development/ndk/platforms/android-9/samples/native-activity/jni/main.c all} */ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, InputQueue.Callback, OnGlobalLayoutListener { public static final String META_DATA_LIB_NAME = "android.app.lib_name"; + public static final String KEY_NATIVE_SAVED_STATE = "android:native_state"; + private NativeContentView mNativeContentView; private InputMethodManager mIMM; private InputMethodCallback mInputMethodCallback; @@ -59,14 +75,15 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, private native int loadNativeCode(String path, MessageQueue queue, String internalDataPath, String externalDataPath, int sdkVersion, - AssetManager assetMgr); + AssetManager assetMgr, byte[] savedState); private native void unloadNativeCode(int handle); private native void onStartNative(int handle); private native void onResumeNative(int handle); - private native void onSaveInstanceStateNative(int handle); + private native byte[] onSaveInstanceStateNative(int handle); private native void onPauseNative(int handle); private native void onStopNative(int handle); + private native void onConfigurationChangedNative(int handle); private native void onLowMemoryNative(int handle); private native void onWindowFocusChangedNative(int handle, boolean focused); private native void onSurfaceCreatedNative(int handle, Surface surface); @@ -165,10 +182,13 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, throw new IllegalArgumentException("Unable to find native library: " + libname); } + byte[] nativeSavedState = savedInstanceState != null + ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null; + mNativeHandle = loadNativeCode(path, Looper.myQueue(), getFilesDir().toString(), Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(), - Build.VERSION.SDK_INT, getAssets()); + Build.VERSION.SDK_INT, getAssets(), nativeSavedState); if (mNativeHandle == 0) { throw new IllegalArgumentException("Unable to load native library: " + path); @@ -206,7 +226,10 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - onSaveInstanceStateNative(mNativeHandle); + byte[] state = onSaveInstanceStateNative(mNativeHandle); + if (state != null) { + outState.putByteArray(KEY_NATIVE_SAVED_STATE, state); + } } @Override @@ -222,6 +245,14 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, } @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (!mDestroyed) { + onConfigurationChangedNative(mNativeHandle); + } + } + + @Override public void onLowMemory() { super.onLowMemory(); if (!mDestroyed) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 8eda844380e0..03bcadc69b85 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -468,12 +468,17 @@ public final class BluetoothAdapter { * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however * many remote devices can only display the first 40 characters, and some * may be limited to just 20. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param name a valid Bluetooth name * @return true if the name was set, false otherwise */ public boolean setName(String name) { + if (getState() != STATE_ON) return false; try { return mService.setName(name); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -488,11 +493,16 @@ public final class BluetoothAdapter { * {@link #SCAN_MODE_NONE}, * {@link #SCAN_MODE_CONNECTABLE}, * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @return scan mode */ public int getScanMode() { + if (getState() != STATE_ON) return SCAN_MODE_NONE; try { return mService.getScanMode(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -511,6 +521,10 @@ public final class BluetoothAdapter { * {@link #SCAN_MODE_NONE}, * {@link #SCAN_MODE_CONNECTABLE}, * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} * <p>Applications cannot set the scan mode. They should use * <code>startActivityForResult( @@ -524,6 +538,7 @@ public final class BluetoothAdapter { * @hide */ public boolean setScanMode(int mode, int duration) { + if (getState() != STATE_ON) return false; try { return mService.setScanMode(mode, duration); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -532,11 +547,13 @@ public final class BluetoothAdapter { /** @hide */ public boolean setScanMode(int mode) { + if (getState() != STATE_ON) return false; return setScanMode(mode, 120); } /** @hide */ public int getDiscoverableTimeout() { + if (getState() != STATE_ON) return -1; try { return mService.getDiscoverableTimeout(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -545,6 +562,7 @@ public final class BluetoothAdapter { /** @hide */ public void setDiscoverableTimeout(int timeout) { + if (getState() != STATE_ON) return; try { mService.setDiscoverableTimeout(timeout); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -572,11 +590,16 @@ public final class BluetoothAdapter { * <p>Device discovery will only find remote devices that are currently * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are * not discoverable by default, and need to be entered into a special mode. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * * @return true on success, false on error */ public boolean startDiscovery() { + if (getState() != STATE_ON) return false; try { return mService.startDiscovery(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -593,10 +616,15 @@ public final class BluetoothAdapter { * the Activity, but is run as a system service, so an application should * always call cancel discovery even if it did not directly request a * discovery, just to be sure. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * * @return true on success, false on error */ public boolean cancelDiscovery() { + if (getState() != STATE_ON) return false; try { mService.cancelDiscovery(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -614,11 +642,16 @@ public final class BluetoothAdapter { * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED} * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery * starts or completes. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * * @return true if discovering */ public boolean isDiscovering() { + if (getState() != STATE_ON) return false; try { return mService.isDiscovering(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -628,11 +661,18 @@ public final class BluetoothAdapter { /** * Return the set of {@link BluetoothDevice} objects that are bonded * (paired) to the local adapter. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return an empty set. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * * @return unmodifiable set of {@link BluetoothDevice}, or null on error */ public Set<BluetoothDevice> getBondedDevices() { + if (getState() != STATE_ON) { + return toDeviceSet(new String[0]); + } try { return toDeviceSet(mService.listBonds()); } catch (RemoteException e) {Log.e(TAG, "", e);} diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 5371fa5b29c0..d685cf37dde5 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -34,6 +34,12 @@ import java.util.ArrayList; * You do not instantiate this class directly; instead, retrieve it through * {@link android.content.Context#getSystemService}. * + * <p> + * The ClipboardManager API itself is very simple: it consists of methods + * to atomically get and set the current primary clipboard data. That data + * is expressed as a {@link ClippedData} object, which defines the protocol + * for data exchange between applications. + * * @see android.content.Context#getSystemService */ public class ClipboardManager extends android.text.ClipboardManager { @@ -152,7 +158,7 @@ public class ClipboardManager extends android.text.ClipboardManager { public CharSequence getText() { ClippedData clip = getPrimaryClip(); if (clip != null && clip.getItemCount() > 0) { - return clip.getItem(0).getText(); + return clip.getItem(0).coerceToText(mContext); } return null; } @@ -167,11 +173,11 @@ public class ClipboardManager extends android.text.ClipboardManager { } /** - * Returns true if the clipboard has a primary clip containing text; false otherwise. + * @deprecated Use {@link #hasPrimaryClip()} instead. */ public boolean hasText() { try { - return getService().hasClipboardText(); + return getService().hasPrimaryClip(); } catch (RemoteException e) { return false; } diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClippedData.java index ebb194f3b55b..c3f0237eb36d 100644 --- a/core/java/android/content/ClippedData.java +++ b/core/java/android/content/ClippedData.java @@ -16,12 +16,18 @@ package android.content; +import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.ArrayList; /** @@ -31,19 +37,88 @@ import java.util.ArrayList; * each of which can hold one or more representations of an item of data. * For display to the user, it also has a label and iconic representation.</p> * - * <p>The types than an individial item can currently contain are:</p> - * - * <ul> - * <li> Text: a basic string of text. This is actually a CharSequence, - * so it can be formatted text supported by corresponding Android built-in - * style spans. (Custom application spans are not supported and will be - * stripped when transporting through the clipboard.) - * <li> Intent: an arbitrary Intent object. A typical use is the shortcut - * to create when pasting a clipped item on to the home screen. - * <li> Uri: a URI reference. Currently this should only be a content: URI. - * This representation allows an application to share complex or large clips, - * by providing a URI to a content provider holding the data. - * </ul> + * <p>Each Item instance can be one of three main classes of data: a simple + * CharSequence of text, a single Intent object, or a Uri. See {@link Item} + * for more details. + * + * <a name="ImplementingPaste"></a> + * <h3>Implementing Paste or Drop</h3> + * + * <p>To implement a paste or drop of a ClippedData object into an application, + * the application must correctly interpret the data for its use. If the {@link Item} + * it contains is simple text or an Intent, there is little to be done: text + * can only be interpreted as text, and an Intent will typically be used for + * creating shortcuts (such as placing icons on the home screen) or other + * actions. + * + * <p>If all you want is the textual representation of the clipped data, you + * can use the convenience method {@link Item#coerceToText Item.coerceToText}. + * + * <p>More complicated exchanges will be done through URIs, in particular + * "content:" URIs. A content URI allows the recipient of a ClippedData item + * to interact closely with the ContentProvider holding the data in order to + * negotiate the transfer of that data. + * + * <p>For example, here is the paste function of a simple NotePad application. + * When retrieving the data from the clipboard, it can do either two things: + * if the clipboard contains a URI reference to an existing note, it copies + * the entire structure of the note into a new note; otherwise, it simply + * coerces the clip into text and uses that as the new note's contents. + * + * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java + * paste} + * + * <p>In many cases an application can paste various types of streams of data. For + * example, an e-mail application may want to allow the user to paste an image + * or other binary data as an attachment. This is accomplished through the + * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and + * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)} + * methods. These allow a client to discover the type(s) of data that a particular + * content URI can make available as a stream and retrieve the stream of data. + * + * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText} + * itself uses this to try to retrieve a URI clip as a stream of text: + * + * {@sample frameworks/base/core/java/android/content/ClippedData.java coerceToText} + * + * <a name="ImplementingCopy"></a> + * <h3>Implementing Copy or Drag</h3> + * + * <p>To be the source of a clip, the application must construct a ClippedData + * object that any recipient can interpret best for their context. If the clip + * is to contain a simple text, Intent, or URI, this is easy: an {@link Item} + * containing the appropriate data type can be constructed and used. + * + * <p>More complicated data types require the implementation of support in + * a ContentProvider for describing and generating the data for the recipient. + * A common scenario is one where an application places on the clipboard the + * content: URI of an object that the user has copied, with the data at that + * URI consisting of a complicated structure that only other applications with + * direct knowledge of the structure can use. + * + * <p>For applications that do not have intrinsic knowledge of the data structure, + * the content provider holding it can make the data available as an arbitrary + * number of types of data streams. This is done by implementing the + * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and + * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)} + * methods. + * + * <p>Going back to our simple NotePad application, this is the implementation + * it may have to convert a single note URI (consisting of a title and the note + * text) into a stream of plain text data. + * + * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java + * stream} + * + * <p>The copy operation in our NotePad application is now just a simple matter + * of making a clip containing the URI of the note being copied: + * + * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java + * copy} + * + * <p>Note if a paste operation needs this clip as text (for example to paste + * into an editor), then {@link Item#coerceToText(Context)} will ask the content + * provider for the clip URI as text and successfully paste the entire note. */ public class ClippedData implements Parcelable { CharSequence mLabel; @@ -51,40 +126,166 @@ public class ClippedData implements Parcelable { final ArrayList<Item> mItems = new ArrayList<Item>(); + /** + * Description of a single item in a ClippedData. + * + * <p>The types than an individual item can currently contain are:</p> + * + * <ul> + * <li> Text: a basic string of text. This is actually a CharSequence, + * so it can be formatted text supported by corresponding Android built-in + * style spans. (Custom application spans are not supported and will be + * stripped when transporting through the clipboard.) + * <li> Intent: an arbitrary Intent object. A typical use is the shortcut + * to create when pasting a clipped item on to the home screen. + * <li> Uri: a URI reference. This may be any URI (such as an http: URI + * representing a bookmark), however it is often a content: URI. Using + * content provider references as clips like this allows an application to + * share complex or large clips through the standard content provider + * facilities. + * </ul> + */ public static class Item { CharSequence mText; Intent mIntent; Uri mUri; + /** + * Create an Item consisting of a single block of (possibly styled) text. + */ public Item(CharSequence text) { mText = text; } + /** + * Create an Item consisting of an arbitrary Intent. + */ public Item(Intent intent) { mIntent = intent; } + /** + * Create an Item consisting of an arbitrary URI. + */ public Item(Uri uri) { mUri = uri; } + /** + * Create a complex Item, containing multiple representations of + * text, intent, and/or URI. + */ public Item(CharSequence text, Intent intent, Uri uri) { mText = text; mIntent = intent; mUri = uri; } + /** + * Retrieve the raw text contained in this Item. + */ public CharSequence getText() { return mText; } + /** + * Retrieve the raw Intent contained in this Item. + */ public Intent getIntent() { return mIntent; } + /** + * Retrieve the raw URI contained in this Item. + */ public Uri getUri() { return mUri; } + + /** + * Turn this item into text, regardless of the type of data it + * actually contains. + * + * <p>The algorithm for deciding what text to return is: + * <ul> + * <li> If {@link #getText} is non-null, return that. + * <li> If {@link #getUri} is non-null, try to retrieve its data + * as a text stream from its content provider. If this succeeds, copy + * the text into a String and return it. If it is not a content: URI or + * the content provider does not supply a text representation, return + * the raw URI as a string. + * <li> If {@link #getIntent} is non-null, convert that to an intent: + * URI and returnit. + * <li> Otherwise, return an empty string. + * </ul> + * + * @param context The caller's Context, from which its ContentResolver + * and other things can be retrieved. + * @return Returns the item's textual representation. + */ +//BEGIN_INCLUDE(coerceToText) + public CharSequence coerceToText(Context context) { + // If this Item has an explicit textual value, simply return that. + if (mText != null) { + return mText; + } + + // If this Item has a URI value, try using that. + if (mUri != null) { + + // First see if the URI can be opened as a plain text stream + // (of any sub-type). If so, this is the best textual + // representation for it. + FileInputStream stream = null; + try { + // Ask for a stream of the desired type. + AssetFileDescriptor descr = context.getContentResolver() + .openTypedAssetFileDescriptor(mUri, "text/*", null); + stream = descr.createInputStream(); + InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); + + // Got it... copy the stream into a local string and return it. + StringBuilder builder = new StringBuilder(128); + char[] buffer = new char[8192]; + int len; + while ((len=reader.read(buffer)) > 0) { + builder.append(buffer, 0, len); + } + return builder.toString(); + + } catch (FileNotFoundException e) { + // Unable to open content URI as text... not really an + // error, just something to ignore. + + } catch (IOException e) { + // Something bad has happened. + Log.w("ClippedData", "Failure loading text", e); + return e.toString(); + + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + + // If we couldn't open the URI as a stream, then the URI itself + // probably serves fairly well as a textual representation. + return mUri.toString(); + } + + // Finally, if all we have is an Intent, then we can just turn that + // into text. Not the most user-friendly thing, but it's something. + if (mIntent != null) { + return mIntent.toUri(Intent.URI_INTENT_SCHEME); + } + + // Shouldn't get here, but just in case... + return ""; + } +//END_INCLUDE(coerceToText) } /** diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index a3252ed2f40c..e1d431f67811 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -28,6 +28,7 @@ import android.database.IBulkCursor; import android.database.IContentObserver; import android.database.SQLException; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -36,6 +37,7 @@ import android.util.Log; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; /** @@ -251,6 +253,18 @@ public abstract class ContentProvider implements ComponentCallbacks { return ContentProvider.this.call(method, request, args); } + @Override + public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { + return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter); + } + + @Override + public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts) + throws FileNotFoundException { + enforceReadPermission(uri); + return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts); + } + private void enforceReadPermission(Uri uri) { final int uid = Binder.getCallingUid(); if (uid == mMyUid) { @@ -752,6 +766,164 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** + * Helper to compare two MIME types, where one may be a pattern. + * @param concreteType A fully-specified MIME type. + * @param desiredType A desired MIME type that may be a pattern such as *\/*. + * @return Returns true if the two MIME types match. + */ + public static boolean compareMimeTypes(String concreteType, String desiredType) { + final int typeLength = desiredType.length(); + if (typeLength == 3 && desiredType.equals("*/*")) { + return true; + } + + final int slashpos = desiredType.indexOf('/'); + if (slashpos > 0) { + if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') { + if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) { + return true; + } + } else if (desiredType.equals(concreteType)) { + return true; + } + } + + return false; + } + + /** + * Called by a client to determine the types of data streams that this + * content provider supports for the given URI. The default implementation + * returns null, meaning no types. If your content provider stores data + * of a particular type, return that MIME type if it matches the given + * mimeTypeFilter. If it can perform type conversions, return an array + * of all supported MIME types that match mimeTypeFilter. + * + * @param uri The data in the content provider being queried. + * @param mimeTypeFilter The type of data the client desires. May be + * a pattern, such as *\/* to retrieve all possible data types. + * @returns Returns null if there are no possible data streams for the + * given mimeTypeFilter. Otherwise returns an array of all available + * concrete MIME types. + * + * @see #getType(Uri) + * @see #openTypedAssetFile(Uri, String, Bundle) + * @see #compareMimeTypes(String, String) + */ + public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { + return null; + } + + /** + * Called by a client to open a read-only stream containing data of a + * particular MIME type. This is like {@link #openAssetFile(Uri, String)}, + * except the file can only be read-only and the content provider may + * perform data conversions to generate data of the desired type. + * + * <p>The default implementation compares the given mimeType against the + * result of {@link #getType(Uri)} and, if the match, simple calls + * {@link #openAssetFile(Uri, String)}. + * + * <p>See {@link ClippedData} for examples of the use and implementation + * of this method. + * + * @param uri The data in the content provider being queried. + * @param mimeTypeFilter The type of data the client desires. May be + * a pattern, such as *\/*, if the caller does not have specific type + * requirements; in this case the content provider will pick its best + * type matching the pattern. + * @param opts Additional options from the client. The definitions of + * these are specific to the content provider being called. + * + * @return Returns a new AssetFileDescriptor from which the client can + * read data of the desired type. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the data. + * @throws IllegalArgumentException Throws IllegalArgumentException if the + * content provider does not support the requested MIME type. + * + * @see #getStreamTypes(Uri, String) + * @see #openAssetFile(Uri, String) + * @see #compareMimeTypes(String, String) + */ + public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) + throws FileNotFoundException { + String baseType = getType(uri); + if (baseType != null && compareMimeTypes(baseType, mimeTypeFilter)) { + return openAssetFile(uri, "r"); + } + throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter); + } + + /** + * Interface to write a stream of data to a pipe. Use with + * {@link ContentProvider#openPipeHelper}. + */ + public interface PipeDataWriter<T> { + /** + * Called from a background thread to stream data out to a pipe. + * Note that the pipe is blocking, so this thread can block on + * writes for an arbitrary amount of time if the client is slow + * at reading. + * + * @param output The pipe where data should be written. This will be + * closed for you upon returning from this function. + * @param uri The URI whose data is to be written. + * @param mimeType The desired type of data to be written. + * @param opts Options supplied by caller. + * @param args Your own custom arguments. + */ + public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, + Bundle opts, T args); + } + + /** + * A helper function for implementing {@link #openTypedAssetFile}, for + * creating a data pipe and background thread allowing you to stream + * generated data back to the client. This function returns a new + * ParcelFileDescriptor that should be returned to the caller (the caller + * is responsible for closing it). + * + * @param uri The URI whose data is to be written. + * @param mimeType The desired type of data to be written. + * @param opts Options supplied by caller. + * @param args Your own custom arguments. + * @param func Interface implementing the function that will actually + * stream the data. + * @return Returns a new ParcelFileDescriptor holding the read side of + * the pipe. This should be returned to the caller for reading; the caller + * is responsible for closing it when done. + */ + public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType, + final Bundle opts, final T args, final PipeDataWriter<T> func) + throws FileNotFoundException { + try { + final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); + + AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() { + @Override + protected Object doInBackground(Object... params) { + func.writeDataToPipe(fds[1], uri, mimeType, opts, args); + try { + fds[1].close(); + } catch (IOException e) { + Log.w(TAG, "Failure closing pipe", e); + } + return null; + } + }; + task.execute((Object[])null); + + return fds[0]; + } catch (IOException e) { + throw new FileNotFoundException("failure making pipe"); + } + } + + /** * Returns true if this instance is a temporary content provider. * @return true if this instance is a temporary content provider */ @@ -777,6 +949,11 @@ public abstract class ContentProvider implements ComponentCallbacks { * @param info Registered information about this content provider */ public void attachInfo(Context context, ProviderInfo info) { + /* + * We may be using AsyncTask from binder threads. Make it init here + * so its static handler is on the main thread. + */ + AsyncTask.init(); /* * Only allow it to be set once, so after the content service gives @@ -824,7 +1001,7 @@ public abstract class ContentProvider implements ComponentCallbacks { /** * @hide -- until interface has proven itself * - * Call an provider-defined method. This can be used to implement + * Call a provider-defined method. This can be used to implement * interfaces that are cheaper than using a Cursor. * * @param method Method name to call. Opaque to framework. @@ -836,29 +1013,26 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** - * Shuts down this instance of the ContentProvider. It is useful when writing tests that use - * the ContentProvider. + * Implement this to shut down the ContentProvider instance. You can then + * invoke this method in unit tests. + * * <p> - * If a unittest starts the ContentProvider in its test(..() methods, it could run into sqlite - * errors "disk I/O error" or "corruption" in the following scenario: - * <ul> - * <li>Say, there are 2 test methods in the unittest</li> - * <li>test1() (or setUp()) causes ContentProvider object to be initialized and - * assume it opens a database connection to "foo.db"</li> - * <li>est1() completes and test2() starts</li> - * <li>During the execution of test2() there will be 2 connections to "foo.db"</li> - * <li>Different threads in the ContentProvider may have one of these two connection - * handles. This is not a problem per se</li> - * <li>But if the two threads with 2 database connections don't interact correctly, - * there could be unexpected errors from sqlite</li> - * <li>Some of those unexpected errros are "disk I/O error" or "corruption" error</li> - * <li>Common practice in tearDown() is to delete test directory (and the database files)</li> - * <li>If this is done while some threads are still holding unclosed database connections, - * sqlite quite easily gets into corruption and disk I/O errors</li> - * </ul> + * Android normally handles ContentProvider startup and shutdown + * automatically. You do not need to start up or shut down a + * ContentProvider. When you invoke a test method on a ContentProvider, + * however, a ContentProvider instance is started and keeps running after + * the test finishes, even if a succeeding test instantiates another + * ContentProvider. A conflict develops because the two instances are + * usually running against the same underlying data source (for example, an + * sqlite database). + * </p> * <p> - * tearDown() in the unittests should call this method to have ContentProvider gracefully - * shutdown all database connections. + * Implementing shutDown() avoids this conflict by providing a way to + * terminate the ContentProvider. This method can also prevent memory leaks + * from multiple instantiations of the ContentProvider, and it can ensure + * unit test isolation by allowing you to completely clean up the test + * fixture before moving on to the next test. + * </p> */ public void shutdown() { Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " + diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 0858ea51c76c..0540109b4f91 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -18,6 +18,7 @@ package android.content; import android.database.Cursor; import android.net.Uri; +import android.os.Bundle; import android.os.RemoteException; import android.os.ParcelFileDescriptor; import android.content.res.AssetFileDescriptor; @@ -43,53 +44,77 @@ public class ContentProviderClient { mContentResolver = contentResolver; } - /** see {@link ContentProvider#query} */ + /** See {@link ContentProvider#query ContentProvider.query} */ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder); } - /** see {@link ContentProvider#getType} */ + /** See {@link ContentProvider#getType ContentProvider.getType} */ public String getType(Uri url) throws RemoteException { return mContentProvider.getType(url); } - /** see {@link ContentProvider#insert} */ + /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */ + public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { + return mContentProvider.getStreamTypes(url, mimeTypeFilter); + } + + /** See {@link ContentProvider#insert ContentProvider.insert} */ public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { return mContentProvider.insert(url, initialValues); } - /** see {@link ContentProvider#bulkInsert} */ + /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { return mContentProvider.bulkInsert(url, initialValues); } - /** see {@link ContentProvider#delete} */ + /** See {@link ContentProvider#delete ContentProvider.delete} */ public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { return mContentProvider.delete(url, selection, selectionArgs); } - /** see {@link ContentProvider#update} */ + /** See {@link ContentProvider#update ContentProvider.update} */ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { return mContentProvider.update(url, values, selection, selectionArgs); } - /** see {@link ContentProvider#openFile} */ + /** + * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that + * this <em>does not</em> + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openFileDescriptor + * ContentResolver.openFileDescriptor} API instead. + */ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { return mContentProvider.openFile(url, mode); } - /** see {@link ContentProvider#openAssetFile} */ + /** + * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}. + * Note that this <em>does not</em> + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openAssetFileDescriptor + * ContentResolver.openAssetFileDescriptor} API instead. + */ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { return mContentProvider.openAssetFile(url, mode); } - /** see {@link ContentProvider#applyBatch} */ + /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ + public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, + String mimeType, Bundle opts) + throws RemoteException, FileNotFoundException { + return mContentProvider.openTypedAssetFile(uri, mimeType, opts); + } + + /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { return mContentProvider.applyBatch(operations); diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index fdb3d20c9f99..d1ab8d576412 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -257,6 +257,38 @@ abstract public class ContentProviderNative extends Binder implements IContentPr reply.writeBundle(responseBundle); return true; } + + case GET_STREAM_TYPES_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + Uri url = Uri.CREATOR.createFromParcel(data); + String mimeTypeFilter = data.readString(); + String[] types = getStreamTypes(url, mimeTypeFilter); + reply.writeNoException(); + reply.writeStringArray(types); + + return true; + } + + case OPEN_TYPED_ASSET_FILE_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + Uri url = Uri.CREATOR.createFromParcel(data); + String mimeType = data.readString(); + Bundle opts = data.readBundle(); + + AssetFileDescriptor fd; + fd = openTypedAssetFile(url, mimeType, opts); + reply.writeNoException(); + if (fd != null) { + reply.writeInt(1); + fd.writeToParcel(reply, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } else { + reply.writeInt(0); + } + return true; + } } } catch (Exception e) { DatabaseUtils.writeExceptionToParcel(reply, e); @@ -568,5 +600,50 @@ final class ContentProviderProxy implements IContentProvider return bundle; } + public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + + data.writeInterfaceToken(IContentProvider.descriptor); + + url.writeToParcel(data, 0); + data.writeString(mimeTypeFilter); + + mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + String[] out = reply.createStringArray(); + + data.recycle(); + reply.recycle(); + + return out; + } + + public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) + throws RemoteException, FileNotFoundException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + + data.writeInterfaceToken(IContentProvider.descriptor); + + url.writeToParcel(data, 0); + data.writeString(mimeType); + data.writeBundle(opts); + + mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply); + int has = reply.readInt(); + AssetFileDescriptor fd = has != 0 + ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null; + + data.recycle(); + reply.recycle(); + + return fd; + } + private IBinder mRemote; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 2ea0df967881..22feb9a984a6 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -186,8 +186,7 @@ public abstract class ContentResolver { * using the content:// scheme. * @return A MIME type for the content, or null if the URL is invalid or the type is unknown */ - public final String getType(Uri url) - { + public final String getType(Uri url) { IContentProvider provider = acquireProvider(url); if (provider == null) { return null; @@ -204,6 +203,39 @@ public abstract class ContentResolver { } /** + * Query for the possible MIME types for the representations the given + * content URL can be returned when opened as as stream with + * {@link #openTypedAssetFileDescriptor}. Note that the types here are + * not necessarily a superset of the type returned by {@link #getType} -- + * many content providers can not return a raw stream for the structured + * data that they contain. + * + * @param url A Uri identifying content (either a list or specific type), + * using the content:// scheme. + * @param mimeTypeFilter The desired MIME type. This may be a pattern, + * such as *\/*, to query for all available MIME types that match the + * pattern. + * @return Returns an array of MIME type strings for all availablle + * data streams that match the given mimeTypeFilter. If there are none, + * null is returned. + */ + public String[] getStreamTypes(Uri url, String mimeTypeFilter) { + IContentProvider provider = acquireProvider(url); + if (provider == null) { + return null; + } + try { + return provider.getStreamTypes(url, mimeTypeFilter); + } catch (RemoteException e) { + return null; + } catch (java.lang.Exception e) { + return null; + } finally { + releaseProvider(provider); + } + } + + /** * <p> * Query the given URI, returning a {@link Cursor} over the result set. * </p> @@ -349,7 +381,7 @@ public abstract class ContentResolver { } /** - * Open a raw file descriptor to access data under a "content:" URI. This + * Open a raw file descriptor to access data under a URI. This * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the * underlying {@link ContentProvider#openFile} * ContentProvider.openFile()} method, so will <em>not</em> work with @@ -399,7 +431,7 @@ public abstract class ContentResolver { } /** - * Open a raw file descriptor to access data under a "content:" URI. This + * Open a raw file descriptor to access data under a URI. This * interacts with the underlying {@link ContentProvider#openAssetFile} * method of the provider associated with the given URI, to retrieve any file stored there. * @@ -433,6 +465,11 @@ public abstract class ContentResolver { * </li> * </ul> * + * <p>Note that if this function is called for read-only input (mode is "r") + * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor} + * for you with a MIME type of "*\/*". This allows such callers to benefit + * from any built-in data conversion that a provider implements. + * * @param uri The desired URI to open. * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile * ContentProvider.openAssetFile}. @@ -459,29 +496,97 @@ public abstract class ContentResolver { new File(uri.getPath()), modeToMode(uri, mode)); return new AssetFileDescriptor(pfd, 0, -1); } else { - IContentProvider provider = acquireProvider(uri); - if (provider == null) { - throw new FileNotFoundException("No content provider: " + uri); - } - try { - AssetFileDescriptor fd = provider.openAssetFile(uri, mode); - if(fd == null) { - releaseProvider(provider); - return null; + if ("r".equals(mode)) { + return openTypedAssetFileDescriptor(uri, "*/*", null); + } else { + IContentProvider provider = acquireProvider(uri); + if (provider == null) { + throw new FileNotFoundException("No content provider: " + uri); } - ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( - fd.getParcelFileDescriptor(), provider); - return new AssetFileDescriptor(pfd, fd.getStartOffset(), - fd.getDeclaredLength()); - } catch (RemoteException e) { - releaseProvider(provider); - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException e) { + try { + AssetFileDescriptor fd = provider.openAssetFile(uri, mode); + if(fd == null) { + releaseProvider(provider); + return null; + } + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), provider); + + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + provider = null; + + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + } catch (RemoteException e) { + throw new FileNotFoundException("Dead content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (provider != null) { + releaseProvider(provider); + } + } + } + } + } + + /** + * Open a raw file descriptor to access (potentially type transformed) + * data from a "content:" URI. This interacts with the underlying + * {@link ContentProvider#openTypedAssetFile} method of the provider + * associated with the given URI, to retrieve retrieve any appropriate + * data stream for the data stored there. + * + * <p>Unlike {@link #openAssetFileDescriptor}, this function only works + * with "content:" URIs, because content providers are the only facility + * with an associated MIME type to ensure that the returned data stream + * is of the desired type. + * + * <p>All text/* streams are encoded in UTF-8. + * + * @param uri The desired URI to open. + * @param mimeType The desired MIME type of the returned data. This can + * be a pattern such as *\/*, which will allow the content provider to + * select a type, though there is no way for you to determine what type + * it is returning. + * @param opts Additional provider-dependent options. + * @return Returns a new ParcelFileDescriptor from which you can read the + * data stream from the provider. Note that this may be a pipe, meaning + * you can't seek in it. The only seek you should do is if the + * AssetFileDescriptor contains an offset, to move to that offset before + * reading. You own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * data of the desired type exists under the URI. + */ + public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, + String mimeType, Bundle opts) throws FileNotFoundException { + IContentProvider provider = acquireProvider(uri); + if (provider == null) { + throw new FileNotFoundException("No content provider: " + uri); + } + try { + AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); + if (fd == null) { releaseProvider(provider); - throw e; - } catch (RuntimeException e) { + return null; + } + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), provider); + + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + provider = null; + + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + } catch (RemoteException e) { + throw new FileNotFoundException("Dead content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (provider != null) { releaseProvider(provider); - throw e; } } } diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 850ff7f0cf2b..42599edb6ce1 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -100,8 +100,8 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { public void stopLoading() { if (mCursor != null && !mCursor.isClosed()) { mCursor.close(); - mCursor = null; } + mCursor = null; // Attempt to cancel the current load task if possible. cancelLoad(); diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 67e7581e5ffd..8f122ce8f39d 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -59,6 +59,7 @@ public interface IContentProvider extends IInterface { throws RemoteException, FileNotFoundException; public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException; + /** * @hide -- until interface has proven itself * @@ -71,6 +72,11 @@ public interface IContentProvider extends IInterface { */ public Bundle call(String method, String request, Bundle args) throws RemoteException; + // Data interchange. + public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; + public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) + throws RemoteException, FileNotFoundException; + /* IPC constants */ static final String descriptor = "android.content.IContentProvider"; @@ -84,4 +90,6 @@ public interface IContentProvider extends IInterface { static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14; static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19; static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20; + static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21; + static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2acc4a05792a..58174fbaa0de 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -985,6 +985,15 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_INSERT = "android.intent.action.INSERT"; /** + * Activity Action: Create a new item in the given container, initializing it + * from the current contents of the clipboard. + * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*) + * in which to place the data. + * <p>Output: URI of the new data that was created. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PASTE = "android.intent.action.PASTE"; + /** * Activity Action: Delete the given data from its container. * <p>Input: {@link #getData} is URI of data to be deleted. * <p>Output: nothing. diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index a37e4e8cc3bf..ccb86050facd 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -129,8 +129,12 @@ public class AssetFileDescriptor implements Parcelable { /** * Checks whether this file descriptor is for a memory file. */ - private boolean isMemoryFile() throws IOException { - return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); + private boolean isMemoryFile() { + try { + return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); + } catch (IOException e) { + return false; + } } /** diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ba1b3a9b8a3b..812af5a1d950 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1333,7 +1333,7 @@ public class Resources { height = mMetrics.widthPixels; } int keyboardHidden = mConfiguration.keyboardHidden; - if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO + if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 9ac45d81105a..c07c3c61e196 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -1203,4 +1203,18 @@ public class DatabaseUtils { } return STATEMENT_OTHER; } + + /** + * Appends one set of selection args to another. This is useful when adding a selection + * argument to a user provided set. + */ + public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) { + if (originalValues == null || originalValues.length == 0) { + return newValues; + } + String[] result = new String[originalValues.length + newValues.length ]; + System.arraycopy(originalValues, 0, result, 0, originalValues.length); + System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); + return result; + } } diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java index 1d88c18ea511..e69c3241350b 100644 --- a/core/java/android/net/DownloadManager.java +++ b/core/java/android/net/DownloadManager.java @@ -241,12 +241,6 @@ public class DownloadManager { */ public static class Request { /** - * Bit flag for {@link #setShowNotification} indicating a notification should be created - * while the download is running. - */ - public static final int NOTIFICATION_WHEN_RUNNING = 1; - - /** * Bit flag for {@link #setAllowedNetworkTypes} corresponding to * {@link ConnectivityManager#TYPE_MOBILE}. */ @@ -269,7 +263,7 @@ public class DownloadManager { private Map<String, String> mRequestHeaders = new HashMap<String, String>(); private String mTitle; private String mDescription; - private int mNotificationFlags = 0; + private boolean mShowNotification = true; private String mMediaType; private boolean mRoamingAllowed = true; private int mAllowedNetworkTypes = ~0; // default to all network types allowed @@ -344,15 +338,20 @@ public class DownloadManager { } /** - * Control system notifications posted by the download manager for this download. If - * enabled, the download manager posts notifications about downloads through the system - * {@link android.app.NotificationManager}. By default, no notification is shown. + * Control whether a system notification is posted by the download manager while this + * download is running. If enabled, the download manager posts notifications about downloads + * through the system {@link android.app.NotificationManager}. By default, a notification is + * shown. * - * @param flags any combination of the NOTIFICATION_* bit flags + * If set to false, this requires the permission + * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. + * + * @param show whether the download manager should show a notification for this download. * @return this object + * @hide */ - public Request setShowNotification(int flags) { - mNotificationFlags = flags; + public Request setShowRunningNotification(boolean show) { + mShowNotification = show; return this; } @@ -404,11 +403,9 @@ public class DownloadManager { putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription); putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMediaType); - int visibility = Downloads.VISIBILITY_HIDDEN; - if ((mNotificationFlags & NOTIFICATION_WHEN_RUNNING) != 0) { - visibility = Downloads.VISIBILITY_VISIBLE; - } - values.put(Downloads.COLUMN_VISIBILITY, visibility); + values.put(Downloads.COLUMN_VISIBILITY, + mShowNotification ? Downloads.VISIBILITY_VISIBLE + : Downloads.VISIBILITY_HIDDEN); values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 11cd52613ad7..efbccd2d79ae 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -526,12 +526,6 @@ public class MobileDataStateTracker implements NetworkStateTracker { return -1; } - /** - * This is not supported. - */ - public void interpretScanResultsAvailable() { - } - @Override public String toString() { StringBuffer sb = new StringBuffer("Mobile data state: "); diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index 44215e7bf452..82735e5f76c1 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -27,18 +27,17 @@ package android.net; public interface NetworkStateTracker { public static final int EVENT_STATE_CHANGED = 1; - public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2; /** * arg1: 1 to show, 0 to hide * arg2: ID of the notification * obj: Notification (if showing) */ - public static final int EVENT_NOTIFICATION_CHANGED = 3; - public static final int EVENT_CONFIGURATION_CHANGED = 4; - public static final int EVENT_ROAMING_CHANGED = 5; - public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6; - public static final int EVENT_RESTORE_DEFAULT_NETWORK = 7; - public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8; + public static final int EVENT_NOTIFICATION_CHANGED = 2; + public static final int EVENT_CONFIGURATION_CHANGED = 3; + public static final int EVENT_ROAMING_CHANGED = 4; + public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 5; + public static final int EVENT_RESTORE_DEFAULT_NETWORK = 6; + public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 7; /** * Fetch NetworkInfo for the network @@ -147,10 +146,4 @@ public interface NetworkStateTracker { */ public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid); - /** - * Interprets scan results. This will be called at a safe time for - * processing, and from a safe thread. - */ - public void interpretScanResultsAvailable(); - } diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 47faaba8d0c5..63adcd09e519 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1568,7 +1568,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { throw new UnsupportedOperationException(NOT_HIERARCHICAL); } if (key == null) { - throw new NullPointerException("key"); + throw new NullPointerException("key"); } final String query = getEncodedQuery(); @@ -1608,6 +1608,24 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return null; } + /** + * Searches the query string for the first value with the given key and interprets it + * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything + * else is interpreted as <code>true</code>. + * + * @param key which will be decoded + * @param defaultValue the default value to return if there is no query parameter for key + * @return the boolean interpretation of the query parameter key + */ + public boolean getBooleanQueryParameter(String key, boolean defaultValue) { + String flag = getQueryParameter(key); + if (flag == null) { + return defaultValue; + } + flag = flag.toLowerCase(); + return (!"false".equals(flag) && !"0".equals(flag)); + } + /** Identifies a null parcelled Uri. */ private static final int NULL_TYPE_ID = 0; diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 832ce84f52ef..aadacabe5e3d 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -175,6 +175,11 @@ public abstract class AsyncTask<Params, Progress, Result> { FINISHED, } + /** @hide Used to force static handler to be created. */ + public static void init() { + sHandler.getLooper(); + } + /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 69b35404a61b..a9d7342ec374 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -180,6 +180,11 @@ public class Looper { return mThread; } + /** @hide */ + public MessageQueue getQueue() { + return mQueue; + } + public void dump(Printer pw, String prefix) { pw.println(prefix + this); pw.println(prefix + "mRun=" + mRun); diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 9d213b344dc8..d853f1375680 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -134,6 +134,25 @@ public class ParcelFileDescriptor implements Parcelable { private static native FileDescriptor getFileDescriptorFromSocket(Socket socket); /** + * Create two ParcelFileDescriptors structured as a data pipe. The first + * ParcelFileDescriptor in the returned array is the read side; the second + * is the write side. + */ + public static ParcelFileDescriptor[] createPipe() throws IOException { + FileDescriptor[] fds = new FileDescriptor[2]; + int res = createPipeNative(fds); + if (res == 0) { + ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2]; + pfds[0] = new ParcelFileDescriptor(fds[0]); + pfds[1] = new ParcelFileDescriptor(fds[1]); + return pfds; + } + throw new IOException("Unable to create pipe: errno=" + -res); + } + + private static native int createPipeNative(FileDescriptor[] outFds); + + /** * Retrieve the actual FileDescriptor associated with this object. * * @return Returns the FileDescriptor associated with this object. diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl index ca7efe780743..5c69214c32c1 100644 --- a/core/java/android/os/storage/IMountService.aidl +++ b/core/java/android/os/storage/IMountService.aidl @@ -19,6 +19,7 @@ package android.os.storage; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; +import android.os.storage.IObbActionListener; /** WARNING! Update IMountService.h and IMountService.cpp if you change this file. * In particular, the ordering of the methods below must match the @@ -156,14 +157,20 @@ interface IMountService /** * Mounts an Opaque Binary Blob (OBB) with the specified decryption key and only * allows the calling process's UID access to the contents. + * + * MountService will call back to the supplied IObbActionListener to inform + * it of the terminal state of the call. */ - int mountObb(String filename, String key); + void mountObb(String filename, String key, IObbActionListener token); /** * Unmounts an Opaque Binary Blob (OBB). When the force flag is specified, any * program using it will be forcibly killed to unmount the image. + * + * MountService will call back to the supplied IObbActionListener to inform + * it of the terminal state of the call. */ - int unmountObb(String filename, boolean force); + void unmountObb(String filename, boolean force, IObbActionListener token); /** * Checks whether the specified Opaque Binary Blob (OBB) is mounted somewhere. diff --git a/core/java/android/os/storage/IObbActionListener.aidl b/core/java/android/os/storage/IObbActionListener.aidl new file mode 100644 index 000000000000..78d7a9ed391c --- /dev/null +++ b/core/java/android/os/storage/IObbActionListener.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.storage; + +/** + * Callback class for receiving events from MountService about + * Opaque Binary Blobs (OBBs). + * + * @hide - Applications should use android.os.storage.StorageManager + * to interact with OBBs. + */ +interface IObbActionListener { + /** + * Return from an OBB action result. + * + * @param filename the path to the OBB the operation was performed on + * @param returnCode status of the operation + */ + void onObbResult(String filename, String status); +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 61cdace618bf..7c9effadb940 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,28 +16,14 @@ package android.os.storage; -import android.content.Context; -import android.os.Binder; -import android.os.Bundle; -import android.os.Looper; -import android.os.Parcelable; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; import android.os.Handler; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.ServiceManager; -import android.os.storage.IMountService; -import android.os.storage.IMountServiceListener; import android.util.Log; -import android.util.SparseArray; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; /** * StorageManager is the interface to the systems storage service. @@ -88,6 +74,17 @@ public class StorageManager } /** + * Binder listener for OBB action results. + */ + private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener(); + private class ObbActionBinderListener extends IObbActionListener.Stub { + @Override + public void onObbResult(String filename, String status) throws RemoteException { + Log.i(TAG, "filename = " + filename + ", result = " + status); + } + } + + /** * Private base class for messages sent between the callback thread * and the target looper handler. */ @@ -290,12 +287,23 @@ public class StorageManager } /** - * Mount an OBB file. + * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is + * specified, it is supplied to the mounting process to be used in any + * encryption used in the OBB. + * <p> + * <em>Note:</em> you can only mount OBB files for which the OBB tag on the + * file matches a package ID that is owned by the calling program's UID. + * That is, shared UID applications can obtain access to any other + * application's OBB that shares its UID. + * + * @param filename the path to the OBB file + * @param key decryption key + * @return whether the mount call was successfully queued or not */ public boolean mountObb(String filename, String key) { try { - return mMountService.mountObb(filename, key) - == StorageResultCode.OperationSucceeded; + mMountService.mountObb(filename, key, mObbActionListener); + return true; } catch (RemoteException e) { Log.e(TAG, "Failed to mount OBB", e); } @@ -304,12 +312,24 @@ public class StorageManager } /** - * Mount an OBB file. + * Unmount an Opaque Binary Blob (OBB) file. If the <code>force</code> flag + * is true, it will kill any application needed to unmount the given OBB. + * <p> + * <em>Note:</em> you can only mount OBB files for which the OBB tag on the + * file matches a package ID that is owned by the calling program's UID. + * That is, shared UID applications can obtain access to any other + * application's OBB that shares its UID. + * + * @param filename path to the OBB file + * @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) { + public boolean unmountObb(String filename, boolean force) throws IllegalArgumentException { try { - return mMountService.unmountObb(filename, force) - == StorageResultCode.OperationSucceeded; + mMountService.unmountObb(filename, force, mObbActionListener); + return true; } catch (RemoteException e) { Log.e(TAG, "Failed to mount OBB", e); } @@ -317,7 +337,13 @@ public class StorageManager return false; } - public boolean isObbMounted(String filename) { + /** + * Check whether an Opaque Binary Blob (OBB) is mounted or not. + * + * @param filename path to OBB image + * @return true if OBB is mounted; false if not mounted or on error + */ + public boolean isObbMounted(String filename) throws IllegalArgumentException { try { return mMountService.isObbMounted(filename); } catch (RemoteException e) { @@ -328,13 +354,21 @@ public class StorageManager } /** - * Check the mounted path of an OBB file. + * Check the mounted path of an Opaque Binary Blob (OBB) file. This will + * give you the path to where you can obtain access to the internals of the + * OBB. + * + * @param filename path to OBB image + * @return absolute path to mounted OBB image data or <code>null</code> if + * not mounted or exception encountered trying to read status */ public String getMountedObbPath(String filename) { try { return mMountService.getMountedObbPath(filename); } catch (RemoteException e) { Log.e(TAG, "Failed to find mounted path for OBB", e); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Couldn't read OBB file", e); } return null; diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index e13c3e8e7b55..114f67dc7a83 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -125,6 +125,9 @@ public abstract class PreferenceActivity extends ListActivity implements // Back will then return RESULT_CANCELED and Next RESULT_OK private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; + // add a Skip button? + private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; + // specify custom text for the Back or Next buttons, or cause a button to not appear // at all by setting it to null private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; @@ -307,6 +310,13 @@ public abstract class PreferenceActivity extends ListActivity implements finish(); } }); + Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button); + skipButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + setResult(RESULT_OK); + finish(); + } + }); mNextButton = (Button)findViewById(com.android.internal.R.id.next_button); mNextButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -334,6 +344,9 @@ public abstract class PreferenceActivity extends ListActivity implements backButton.setText(buttonText); } } + if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { + skipButton.setVisibility(View.VISIBLE); + } } } diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index 1b37107e1d60..c9b55121aa84 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -624,13 +624,19 @@ public final class Downloads { "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"; /** - * The permission to downloads files to the cache partition that won't be automatically + * The permission to download files to the cache partition that won't be automatically * purged when space is needed. */ public static final String PERMISSION_CACHE_NON_PURGEABLE = "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE"; /** + * The permission to download files without any system notification being shown. + */ + public static final String PERMISSION_NO_NOTIFICATION = + "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"; + + /** * The content:// URI for the data table in the provider */ public static final Uri CONTENT_URI = diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Mtp.java index 15f8666b0fe2..bc764acb2f64 100644 --- a/core/java/android/provider/Mtp.java +++ b/core/java/android/provider/Mtp.java @@ -308,6 +308,13 @@ public final class Mtp public static final int FORMAT_ABSTRACT_CONTACT = 0xBB81; public static final int FORMAT_VCARD_2 = 0xBB82; + // Object properties we support + public static final int PROPERTY_STORAGE_ID = 0xDC01; + public static final int PROPERTY_OBJECT_FORMAT = 0xDC02; + public static final int PROPERTY_OBJECT_SIZE = 0xDC04; + public static final int PROPERTY_OBJECT_FILE_NAME = 0xDC07; + public static final int PROPERTY_PARENT_OBJECT = 0xDC0B; + /** * Object is not protected. It may be modified and deleted, and its properties * may be modified. diff --git a/core/java/android/util/Finalizers.java b/core/java/android/util/Finalizers.java new file mode 100644 index 000000000000..671f2d496e63 --- /dev/null +++ b/core/java/android/util/Finalizers.java @@ -0,0 +1,127 @@ +/* + * 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.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +/** + * This class can be used to implement reliable finalizers. + * + * @hide + */ +public final class Finalizers { + private static final String LOG_TAG = "Finalizers"; + + private static final Object[] sLock = new Object[0]; + private static boolean sInit; + private static Reclaimer sReclaimer; + + /** + * Subclass of PhantomReference used to reclaim resources. + */ + public static abstract class ReclaimableReference<T> extends PhantomReference<T> { + public ReclaimableReference(T r, ReferenceQueue<Object> q) { + super(r, q); + } + + public abstract void reclaim(); + } + + /** + * Returns the queue used to reclaim ReclaimableReferences. + * + * @return A reference queue or null before initialization + */ + public static ReferenceQueue<Object> getQueue() { + synchronized (sLock) { + if (!sInit) { + return null; + } + if (!sReclaimer.isRunning()) { + sReclaimer = new Reclaimer(sReclaimer.mQueue); + sReclaimer.start(); + } + return sReclaimer.mQueue; + } + } + + /** + * Invoked by Zygote. Don't touch! + */ + public static void init() { + synchronized (sLock) { + if (!sInit && sReclaimer == null) { + sReclaimer = new Reclaimer(); + sReclaimer.start(); + sInit = true; + } + } + } + + private static class Reclaimer extends Thread { + ReferenceQueue<Object> mQueue; + + private volatile boolean mRunning = false; + + Reclaimer() { + this(new ReferenceQueue<Object>()); + } + + Reclaimer(ReferenceQueue<Object> queue) { + super("Reclaimer"); + setDaemon(true); + mQueue = queue; + } + + @Override + public void start() { + mRunning = true; + super.start(); + } + + boolean isRunning() { + return mRunning; + } + + @SuppressWarnings({"InfiniteLoopStatement"}) + @Override + public void run() { + try { + while (true) { + try { + cleanUp(mQueue.remove()); + } catch (InterruptedException e) { + // Ignore + } + } + } catch (Exception e) { + Log.e(LOG_TAG, "Reclaimer thread exiting: ", e); + } finally { + mRunning = false; + } + } + + private void cleanUp(Reference<?> reference) { + do { + reference.clear(); + ((ReclaimableReference<?>) reference).reclaim(); + } while ((reference = mQueue.poll()) != null); + } + } +} diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index e47be0d6d28c..3345bfab59fc 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -172,6 +172,9 @@ public final class JsonReader implements Closeable { /** The input JSON. */ private final Reader in; + /** True to accept non-spec compliant JSON */ + private boolean lenient = false; + /** * Use a manual buffer to easily read and unread upcoming characters, and * also so we can create strings without an intermediate StringBuilder. @@ -207,8 +210,8 @@ public final class JsonReader implements Closeable { /** The text of the next literal value. */ private String value; - // TODO: make this parser strict and offer an optional lenient mode? - // TODO: document how this reader is non-strict + /** True if we're currently handling a skipValue() call. */ + private boolean skipping = false; /** * Creates a new instance that reads a JSON-encoded stream from {@code in}. @@ -221,6 +224,31 @@ public final class JsonReader implements Closeable { } /** + * Configure this parser to be be liberal in what it accepts. By default, + * this parser is strict and only accepts JSON as specified by <a + * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the + * parser to lenient causes it to ignore the following syntax errors: + * + * <ul> + * <li>End of line comments starting with {@code //} or {@code #} and + * ending with a newline character. + * <li>C-style comments starting with {@code /*} and ending with + * {@code *}{@code /}. Such comments may not be nested. + * <li>Names that are unquoted or {@code 'single quoted'}. + * <li>Strings that are unquoted or {@code 'single quoted'}. + * <li>Array elements separated by {@code ;} instead of {@code ,}. + * <li>Unnecessary array separators. These are interpreted as if null + * was the omitted value. + * <li>Names and values separated by {@code =} or {@code =>} instead of + * {@code :}. + * <li>Name/value pairs separated by {@code ;} instead of {@code ,}. + * </ul> + */ + public void setLenient(boolean lenient) { + this.lenient = lenient; + } + + /** * Consumes the next token from the JSON stream and asserts that it is the * beginning of a new array. */ @@ -253,11 +281,12 @@ public final class JsonReader implements Closeable { } /** - * Consumes {@code token}. + * Consumes {@code expected}. */ - private void expect(JsonToken token) throws IOException { - if (quickPeek() != token) { - throw new IllegalStateException("Expected " + token + " but was " + peek()); + private void expect(JsonToken expected) throws IOException { + quickPeek(); + if (token != expected) { + throw new IllegalStateException("Expected " + expected + " but was " + peek()); } advance(); } @@ -266,8 +295,8 @@ public final class JsonReader implements Closeable { * Returns true if the current array or object has another element. */ public boolean hasNext() throws IOException { - JsonToken peek = quickPeek(); - return peek != JsonToken.END_OBJECT && peek != JsonToken.END_ARRAY; + quickPeek(); + return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; } /** @@ -285,11 +314,8 @@ public final class JsonReader implements Closeable { /** * Ensures that a token is ready. After this call either {@code token} or - * {@code value} will be non-null. - * - * @return the type of the next token, of {@code null} if it is unknown. For - * a definitive result, use {@link #peek()} which decodes the token - * type. + * {@code value} will be non-null. To ensure {@code token} has a definitive + * value, use {@link #peek()} */ private JsonToken quickPeek() throws IOException { if (hasToken) { @@ -347,7 +373,8 @@ public final class JsonReader implements Closeable { * name. */ public String nextName() throws IOException { - if (quickPeek() != JsonToken.NAME) { + quickPeek(); + if (token != JsonToken.NAME) { throw new IllegalStateException("Expected a name but was " + peek()); } String result = name; @@ -364,8 +391,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public String nextString() throws IOException { - JsonToken peek = peek(); - if (value == null || (peek != JsonToken.STRING && peek != JsonToken.NUMBER)) { + peek(); + if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) { throw new IllegalStateException("Expected a string but was " + peek()); } @@ -382,8 +409,8 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public boolean nextBoolean() throws IOException { - JsonToken peek = quickPeek(); - if (value == null || peek == JsonToken.STRING) { + quickPeek(); + if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected a boolean but was " + peek()); } @@ -408,8 +435,8 @@ public final class JsonReader implements Closeable { * reader is closed. */ public void nextNull() throws IOException { - JsonToken peek = quickPeek(); - if (value == null || peek == JsonToken.STRING) { + quickPeek(); + if (value == null || token == JsonToken.STRING) { throw new IllegalStateException("Expected null but was " + peek()); } @@ -536,17 +563,20 @@ public final class JsonReader implements Closeable { * stream contains unrecognized or unhandled values. */ public void skipValue() throws IOException { - // TODO: suppress string creation while elements are being skipped! - - int count = 0; - do { - JsonToken token = advance(); - if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) { - count++; - } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) { - count--; - } - } while (count != 0); + skipping = true; + try { + int count = 0; + do { + JsonToken token = advance(); + if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) { + count++; + } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) { + count--; + } + } while (count != 0); + } finally { + skipping = false; + } } private JsonScope peekStack() { @@ -570,36 +600,43 @@ public final class JsonReader implements Closeable { private JsonToken nextInArray(boolean firstElement) throws IOException { if (firstElement) { - switch (nextNonWhitespace()) { - case ']': - pop(); - hasToken = true; - return token = JsonToken.END_ARRAY; - case ',': - case ';': - /* a separator without a value first means "null". */ - // TODO: forbid this in strict mode - hasToken = true; - return token = JsonToken.NULL; - default: - replaceTop(JsonScope.NONEMPTY_ARRAY); - pos--; - } + replaceTop(JsonScope.NONEMPTY_ARRAY); } else { + /* Look for a comma before each element after the first element. */ switch (nextNonWhitespace()) { case ']': pop(); hasToken = true; return token = JsonToken.END_ARRAY; - case ',': case ';': + checkLenient(); // fall-through + case ',': break; default: throw syntaxError("Unterminated array"); } } - return nextValue(); + switch (nextNonWhitespace()) { + case ']': + if (firstElement) { + pop(); + hasToken = true; + return token = JsonToken.END_ARRAY; + } + // fall-through to handle ",]" + case ';': + case ',': + /* In lenient mode, a 0-length literal means 'null' */ + checkLenient(); + pos--; + hasToken = true; + value = "null"; + return token = JsonToken.NULL; + default: + pos--; + return nextValue(); + } } private JsonToken nextInObject(boolean firstElement) throws IOException { @@ -636,10 +673,12 @@ public final class JsonReader implements Closeable { int quote = nextNonWhitespace(); switch (quote) { case '\'': + checkLenient(); // fall-through case '"': name = nextString((char) quote); break; default: + checkLenient(); pos--; name = nextLiteral(); if (name.isEmpty()) { @@ -653,19 +692,21 @@ public final class JsonReader implements Closeable { } private JsonToken objectValue() throws IOException { - // TODO: accept only ":" in strict mode - /* - * Read the name/value separator. Usually a colon ':', an equals sign - * '=', or an arrow "=>". The last two are bogus but we include them - * because that's what org.json does. + * Read the name/value separator. Usually a colon ':'. In lenient mode + * we also accept an equals sign '=', or an arrow "=>". */ - int separator = nextNonWhitespace(); - if (separator != ':' && separator != '=') { - throw syntaxError("Expected ':'"); - } - if (separator == '=' && (pos < limit || fillBuffer(1)) && buffer[pos] == '>') { - pos++; + switch (nextNonWhitespace()) { + case ':': + break; + case '=': + checkLenient(); + if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { + pos++; + } + break; + default: + throw syntaxError("Expected ':'"); } replaceTop(JsonScope.NONEMPTY_OBJECT); @@ -686,6 +727,7 @@ public final class JsonReader implements Closeable { return token = JsonToken.BEGIN_ARRAY; case '\'': + checkLenient(); // fall-through case '"': value = nextString((char) c); hasToken = true; @@ -722,8 +764,6 @@ public final class JsonReader implements Closeable { } private int nextNonWhitespace() throws IOException { - // TODO: no comments in strict mode - while (pos < limit || fillBuffer(1)) { int c = buffer[pos++]; switch (c) { @@ -738,6 +778,7 @@ public final class JsonReader implements Closeable { return c; } + checkLenient(); char peek = buffer[pos]; switch (peek) { case '*': @@ -765,6 +806,7 @@ public final class JsonReader implements Closeable { * specify this behaviour, but it's required to parse * existing documents. See http://b/2571423. */ + checkLenient(); skipToEndOfLine(); continue; @@ -776,6 +818,12 @@ public final class JsonReader implements Closeable { throw syntaxError("End of input"); } + private void checkLenient() throws IOException { + if (!lenient) { + throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON"); + } + } + /** * Advances the position until after the next newline character. If the line * is terminated by "\r\n", the '\n' must be consumed as whitespace by the @@ -822,7 +870,9 @@ public final class JsonReader implements Closeable { int c = buffer[pos++]; if (c == quote) { - if (builder == null) { + if (skipping) { + return "skipped!"; + } else if (builder == null) { return new String(buffer, start, pos - start - 1); } else { builder.append(buffer, start, pos - start - 1); @@ -853,9 +903,6 @@ public final class JsonReader implements Closeable { * does not consume the delimiter character. */ private String nextLiteral() throws IOException { - // TODO: use a much smaller set of permitted literal characters in strict mode; - // these characters are derived from org.json's lenient mode - StringBuilder builder = null; do { /* the index of the first character not yet appended to the builder. */ @@ -863,24 +910,28 @@ public final class JsonReader implements Closeable { while (pos < limit) { int c = buffer[pos++]; switch (c) { + case '/': + case '\\': + case ';': + case '#': + case '=': + checkLenient(); // fall-through + case '{': case '}': case '[': case ']': - case '/': - case '\\': case ':': - case '=': case ',': - case ';': - case '#': case ' ': case '\t': case '\f': case '\r': case '\n': pos--; - if (builder == null) { + if (skipping) { + return "skipped!"; + } else if (builder == null) { return new String(buffer, start, pos - start); } else { builder.append(buffer, start, pos - start); @@ -965,7 +1016,7 @@ public final class JsonReader implements Closeable { /** * Assigns {@code nextToken} based on the value of {@code nextValue}. */ - private void decodeLiteral() { + private void decodeLiteral() throws IOException { if (value.equalsIgnoreCase("null")) { token = JsonToken.NULL; } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { @@ -975,7 +1026,8 @@ public final class JsonReader implements Closeable { Double.parseDouble(value); // this work could potentially be cached token = JsonToken.NUMBER; } catch (NumberFormatException ignored) { - /* an unquoted string. This document is not well-formed! */ + // this must be an unquoted string + checkLenient(); token = JsonToken.STRING; } } diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java index 913b04784d98..fecc1c873771 100644 --- a/core/java/android/util/JsonWriter.java +++ b/core/java/android/util/JsonWriter.java @@ -16,10 +16,10 @@ package android.util; +import java.io.Closeable; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -117,7 +117,7 @@ import java.util.List; * Instances of this class are not thread safe. Calls that would result in a * malformed JSON string will fail with an {@link IllegalStateException}. */ -public final class JsonWriter { +public final class JsonWriter implements Closeable { /** The output data, containing at most one top-level array or object. */ private final Writer out; @@ -151,23 +151,20 @@ public final class JsonWriter { } /** - * Sets the number of spaces to indent each line in the encoded document. - * If {@code indent == 0} the encoded document will be compact. If {@code - * indent > 0}, the encoded document will be more human-readable. + * Sets the indentation string to be repeated for each level of indentation + * in the encoded document. If {@code indent.isEmpty()} the encoded document + * will be compact. Otherwise the encoded document will be more + * human-readable. + * + * @param indent a string containing only whitespace. */ - public void setIndentSpaces(int indent) { - if (indent < 0) { - throw new IllegalArgumentException("indent < 0"); - } - - if (indent > 0) { - char[] indentChars = new char[indent]; - Arrays.fill(indentChars, ' '); - this.indent = new String(indentChars); - this.separator = ": "; - } else { + public void setIndent(String indent) { + if (indent.isEmpty()) { this.indent = null; this.separator = ":"; + } else { + this.indent = indent; + this.separator = ": "; } } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 8e1338dda795..91dbe1ff8cb0 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -40,7 +40,6 @@ import javax.microedition.khronos.opengles.GL; /** * An implementation of Canvas on top of OpenGL ES 2.0. */ -@SuppressWarnings({"deprecation"}) class GLES20Canvas extends Canvas { @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) private final GL mGl; @@ -56,6 +55,17 @@ class GLES20Canvas extends Canvas { private final Rect mClipBounds = new Rect(); private DrawFilter mFilter; + + /////////////////////////////////////////////////////////////////////////// + // JNI + /////////////////////////////////////////////////////////////////////////// + + private static native boolean nIsAvailable(); + private static boolean sIsAvailable = nIsAvailable(); + + static boolean isAvailable() { + return sIsAvailable; + } /////////////////////////////////////////////////////////////////////////// // Constructors @@ -91,11 +101,6 @@ class GLES20Canvas extends Canvas { } @Override - public GL getGL() { - throw new UnsupportedOperationException(); - } - - @Override public void setBitmap(Bitmap bitmap) { throw new UnsupportedOperationException(); } @@ -522,11 +527,18 @@ class GLES20Canvas extends Canvas { @Override public void drawPath(Path path, Paint paint) { boolean hasModifier = setupModifiers(paint); - nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint); + if (path.isSimplePath) { + if (path.rects != null) { + nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint); + } + } else { + nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint); + } if (hasModifier) nResetModifiers(mRenderer); } private native void nDrawPath(int renderer, int path, int paint); + private native void nDrawRects(int renderer, int region, int paint); @Override public void drawPicture(Picture picture) { @@ -605,9 +617,13 @@ class GLES20Canvas extends Canvas { if ((index | count | (index + count) | (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } + boolean hasModifier = setupModifiers(paint); - nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint); - if (hasModifier) nResetModifiers(mRenderer); + try { + nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint); + } finally { + if (hasModifier) nResetModifiers(mRenderer); + } } private native void nDrawText(int renderer, char[] text, int index, int count, float x, float y, @@ -616,20 +632,23 @@ class GLES20Canvas extends Canvas { @Override public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { boolean hasModifier = setupModifiers(paint); - if (text instanceof String || text instanceof SpannedString || - text instanceof SpannableString) { - nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, - paint.mNativePaint); - } else if (text instanceof GraphicsOperations) { - ((GraphicsOperations) text).drawText(this, start, end, x, y, - paint); - } else { - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint); - TemporaryBuffer.recycle(buf); + try { + if (text instanceof String || text instanceof SpannedString || + text instanceof SpannableString) { + nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, + paint.mNativePaint); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawText(this, start, end, x, y, + paint); + } else { + char[] buf = TemporaryBuffer.obtain(end - start); + TextUtils.getChars(text, start, end, buf, 0); + nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint); + TemporaryBuffer.recycle(buf); + } + } finally { + if (hasModifier) nResetModifiers(mRenderer); } - if (hasModifier) nResetModifiers(mRenderer); } @Override @@ -637,9 +656,13 @@ class GLES20Canvas extends Canvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } + boolean hasModifier = setupModifiers(paint); - nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint); - if (hasModifier) nResetModifiers(mRenderer); + try { + nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint); + } finally { + if (hasModifier) nResetModifiers(mRenderer); + } } private native void nDrawText(int renderer, String text, int start, int end, float x, float y, @@ -648,8 +671,12 @@ class GLES20Canvas extends Canvas { @Override public void drawText(String text, float x, float y, Paint paint) { boolean hasModifier = setupModifiers(paint); - nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint); - if (hasModifier) nResetModifiers(mRenderer); + try { + nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, + paint.mNativePaint); + } finally { + if (hasModifier) nResetModifiers(mRenderer); + } } @Override @@ -666,15 +693,59 @@ class GLES20Canvas extends Canvas { @Override public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, int dir, Paint paint) { - throw new UnsupportedOperationException(); + if ((index | count | text.length - index - count) < 0) { + throw new IndexOutOfBoundsException(); + } + if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { + throw new IllegalArgumentException("Unknown direction: " + dir); + } + + boolean hasModifier = setupModifiers(paint); + try { + nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir, + paint.mNativePaint); + } finally { + if (hasModifier) nResetModifiers(mRenderer); + } } + private native void nDrawTextRun(int renderer, char[] text, int index, int count, + int contextIndex, int contextCount, float x, float y, int dir, int nativePaint); + @Override public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, int dir, Paint paint) { - throw new UnsupportedOperationException(); + if ((start | end | end - start | text.length() - end) < 0) { + throw new IndexOutOfBoundsException(); + } + + boolean hasModifier = setupModifiers(paint); + try { + int flags = dir == 0 ? 0 : 1; + if (text instanceof String || text instanceof SpannedString || + text instanceof SpannableString) { + nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, + contextEnd, x, y, flags, paint.mNativePaint); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawTextRun(this, start, end, + contextStart, contextEnd, x, y, flags, paint); + } else { + int contextLen = contextEnd - contextStart; + int len = end - start; + char[] buf = TemporaryBuffer.obtain(contextLen); + TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, + x, y, flags, paint.mNativePaint); + TemporaryBuffer.recycle(buf); + } + } finally { + if (hasModifier) nResetModifiers(mRenderer); + } } + private native void nDrawTextRun(int renderer, String text, int start, int end, + int contextStart, int contextEnd, float x, float y, int flags, int nativePaint); + @Override public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 090a74372c1a..44bd6d47d678 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -28,22 +28,29 @@ import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL11; - -import static javax.microedition.khronos.opengles.GL10.GL_COLOR_BUFFER_BIT; -import static javax.microedition.khronos.opengles.GL10.GL_SCISSOR_TEST; /** * Interface for rendering a ViewRoot using hardware acceleration. * * @hide */ -abstract class HardwareRenderer { +public abstract class HardwareRenderer { private boolean mEnabled; private boolean mRequested = true; private static final String LOG_TAG = "HardwareRenderer"; /** + * Indicates whether hardware acceleration is available under any form for + * the view hierarchy. + * + * @return True if the view hierarchy can potentially be hardware accelerated, + * false otherwise + */ + public static boolean isAvailable() { + return GLES20Canvas.isAvailable(); + } + + /** * Destroys the hardware rendering context. */ abstract void destroy(); @@ -110,10 +117,8 @@ abstract class HardwareRenderer { */ static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) { switch (glVersion) { - case 1: - return new Gl10Renderer(translucent); case 2: - return new Gl20Renderer(translucent); + return Gl20Renderer.create(translucent); } throw new IllegalArgumentException("Unknown GL version: " + glVersion); } @@ -295,7 +300,6 @@ abstract class HardwareRenderer { */ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { throw new RuntimeException("eglMakeCurrent failed"); - } return mEglContext.getGL(); @@ -369,6 +373,15 @@ abstract class HardwareRenderer { attachInfo.mIgnoreDirtyState = true; view.mPrivateFlags |= View.DRAWN; + // TODO: Don't check the current context when we have one per UI thread + // TODO: Use a threadlocal flag to know whether the surface has changed + if (mEgl.eglGetCurrentContext() != mEglContext || + mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW) != mEglSurface) { + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + onPreDraw(); Canvas canvas = mCanvas; @@ -520,43 +533,13 @@ abstract class HardwareRenderer { @Override void onPreDraw() { mGlCanvas.onPreDraw(); - } - } - - /** - * Hardware renderer using OpenGL ES 1.0. - */ - @SuppressWarnings({"deprecation"}) - static class Gl10Renderer extends GlRenderer { - Gl10Renderer(boolean translucent) { - super(1, translucent); - } - - @Override - Canvas createCanvas() { - return new Canvas(mGl); } - @Override - void destroy() { - if (isEnabled()) { - nativeAbandonGlCaches(); + static HardwareRenderer create(boolean translucent) { + if (GLES20Canvas.isAvailable()) { + return new Gl20Renderer(translucent); } - - super.destroy(); - } - - @Override - void onPreDraw() { - GL11 gl = (GL11) mGl; - gl.glDisable(GL_SCISSOR_TEST); - gl.glClearColor(0, 0, 0, 0); - gl.glClear(GL_COLOR_BUFFER_BIT); - gl.glEnable(GL_SCISSOR_TEST); + return null; } } - - // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures - // Used only by the native Skia OpenGL ES 1.x implementation - private static native void nativeAbandonGlCaches(); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index d6b92125490c..e86e3bf3bcba 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -28,6 +28,7 @@ import android.view.IWindowSession; import android.view.KeyEvent; import android.view.InputEvent; import android.view.MotionEvent; +import android.view.InputChannel; /** * System private interface to the window manager. @@ -119,6 +120,7 @@ interface IWindowManager int getKeycodeStateForDevice(int devid, int sw); int getTrackballKeycodeState(int sw); int getDPadKeycodeState(int sw); + InputChannel monitorInput(String inputChannelName); // Report whether the hardware supports the given keys; returns true if successful boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index 13d810431612..43c957adebb6 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -132,9 +132,9 @@ public final class InputQueue { synchronized (sLock) { FinishedCallback callback = sRecycleHead; if (callback != null) { - callback.mRecycleNext = null; sRecycleHead = callback.mRecycleNext; sRecycleCount -= 1; + callback.mRecycleNext = null; } else { callback = new FinishedCallback(); } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 9223e17fb5a5..ed10e412ac51 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -156,7 +156,7 @@ public class KeyEvent extends InputEvent implements Parcelable { // those new codes. This is intended to maintain a consistent // set of key code definitions across all Android devices. - private static final int LAST_KEYCODE = KEYCODE_SWITCH_CHARSET; + private static final int LAST_KEYCODE = KEYCODE_BUTTON_MODE; /** * @deprecated There are now more than MAX_KEYCODE keycodes. diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index ff34f4add39d..09995987e65d 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -312,7 +312,7 @@ public class ScaleGestureDetector { * MotionEvent has no getRawX(int) method; simulate it pending future API approval. */ private static float getRawX(MotionEvent event, int pointerIndex) { - float offset = event.getX() - event.getRawX(); + float offset = event.getRawX() - event.getX(); return event.getX(pointerIndex) + offset; } @@ -320,7 +320,7 @@ public class ScaleGestureDetector { * MotionEvent has no getRawY(int) method; simulate it pending future API approval. */ private static float getRawY(MotionEvent event, int pointerIndex) { - float offset = event.getY() - event.getRawY(); + float offset = event.getRawY() - event.getY(); return event.getY(pointerIndex) + offset; } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 068e7b6efbc9..fb88c7135ba6 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -33,14 +33,15 @@ import android.util.PoolableManager; * and {@link #getXVelocity()}. */ public final class VelocityTracker implements Poolable<VelocityTracker> { - static final String TAG = "VelocityTracker"; - static final boolean DEBUG = false; - static final boolean localLOGV = DEBUG || Config.LOGV; + private static final String TAG = "VelocityTracker"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG || Config.LOGV; - static final int NUM_PAST = 10; - static final int MAX_AGE_MILLISECONDS = 200; + private static final int NUM_PAST = 10; + private static final int MAX_AGE_MILLISECONDS = 200; + + private static final int POINTER_POOL_CAPACITY = 20; - static final VelocityTracker[] mPool = new VelocityTracker[1]; private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( Pools.finitePool(new PoolableManager<VelocityTracker>() { public VelocityTracker newInstance() { @@ -48,16 +49,19 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { } public void onAcquired(VelocityTracker element) { - element.clear(); } public void onReleased(VelocityTracker element) { + element.clear(); } }, 2)); - private static final int INITIAL_POINTERS = 5; + private static Pointer sRecycledPointerListHead; + private static int sRecycledPointerCount; - private static final class PointerData { + private static final class Pointer { + public Pointer next; + public int id; public float xVelocity; public float yVelocity; @@ -65,11 +69,13 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { public final float[] pastX = new float[NUM_PAST]; public final float[] pastY = new float[NUM_PAST]; public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel + + public int generation; } - private PointerData[] mPointers = new PointerData[INITIAL_POINTERS]; - private int mNumPointers; + private Pointer mPointerListHead; // sorted by id in increasing order private int mLastTouchIndex; + private int mGeneration; private VelocityTracker mNext; @@ -115,7 +121,9 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * Reset the velocity tracker back to its initial state. */ public void clear() { - mNumPointers = 0; + releasePointerList(mPointerListHead); + + mPointerListHead = null; mLastTouchIndex = 0; } @@ -134,56 +142,62 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { final int lastTouchIndex = mLastTouchIndex; final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST; final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST; + final int generation = mGeneration++; - if (pointerCount < mNumPointers) { - final PointerData[] pointers = mPointers; - int i = mNumPointers; - while (--i >= 0) { - final PointerData pointerData = pointers[i]; - if (ev.findPointerIndex(pointerData.id) == -1) { - // Pointer went up. - // Shuffle pointers down to fill the hole. Place the old pointer data at - // the end so we can recycle it if more pointers are added later. - mNumPointers -= 1; - final int remaining = mNumPointers - i; - if (remaining != 0) { - System.arraycopy(pointers, i + 1, pointers, i, remaining); - pointers[mNumPointers] = pointerData; - } - } - } - } - + mLastTouchIndex = finalTouchIndex; + + // Update pointer data. + Pointer previousPointer = null; for (int i = 0; i < pointerCount; i++){ final int pointerId = ev.getPointerId(i); - PointerData pointerData = getPointerData(pointerId); - if (pointerData == null) { - // Pointer went down. - // Add a new entry. Write a sentinel at the end of the pastTime trace so we - // will be able to tell where the trace started. - final PointerData[] oldPointers = mPointers; - final int newPointerIndex = mNumPointers; - if (newPointerIndex < oldPointers.length) { - pointerData = oldPointers[newPointerIndex]; - if (pointerData == null) { - pointerData = new PointerData(); - oldPointers[newPointerIndex] = pointerData; + + // Find the pointer data for this pointer id. + // This loop is optimized for the common case where pointer ids in the event + // are in sorted order. However, we check for this case explicitly and + // perform a full linear scan from the start if needed. + Pointer nextPointer; + if (previousPointer == null || pointerId < previousPointer.id) { + previousPointer = null; + nextPointer = mPointerListHead; + } else { + nextPointer = previousPointer.next; + } + + final Pointer pointer; + for (;;) { + if (nextPointer != null) { + final int nextPointerId = nextPointer.id; + if (nextPointerId == pointerId) { + pointer = nextPointer; + break; + } + if (nextPointerId < pointerId) { + nextPointer = nextPointer.next; + continue; } + } + + // Pointer went down. Add it to the list. + // Write a sentinel at the end of the pastTime trace so we will be able to + // tell when the trace started. + pointer = obtainPointer(); + pointer.id = pointerId; + pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE; + pointer.next = nextPointer; + if (previousPointer == null) { + mPointerListHead = pointer; } else { - final PointerData[] newPointers = new PointerData[newPointerIndex * 2]; - System.arraycopy(oldPointers, 0, newPointers, 0, newPointerIndex); - mPointers = newPointers; - pointerData = new PointerData(); - newPointers[newPointerIndex] = pointerData; + previousPointer.next = pointer; } - pointerData.id = pointerId; - pointerData.pastTime[lastTouchIndex] = Long.MIN_VALUE; - mNumPointers += 1; + break; } - final float[] pastX = pointerData.pastX; - final float[] pastY = pointerData.pastY; - final long[] pastTime = pointerData.pastTime; + pointer.generation = generation; + previousPointer = pointer; + + final float[] pastX = pointer.pastX; + final float[] pastY = pointer.pastY; + final long[] pastTime = pointer.pastTime; for (int j = 0; j < historySize; j++) { final int touchIndex = (nextTouchIndex + j) % NUM_PAST; @@ -196,7 +210,23 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { pastTime[finalTouchIndex] = ev.getEventTime(); } - mLastTouchIndex = finalTouchIndex; + // Find removed pointers. + previousPointer = null; + for (Pointer pointer = mPointerListHead; pointer != null; ) { + final Pointer nextPointer = pointer.next; + if (pointer.generation != generation) { + // Pointer went up. Remove it from the list. + if (previousPointer == null) { + mPointerListHead = nextPointer; + } else { + previousPointer.next = nextPointer; + } + releasePointer(pointer); + } else { + previousPointer = pointer; + } + pointer = nextPointer; + } } /** @@ -223,13 +253,10 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * must be positive. */ public void computeCurrentVelocity(int units, float maxVelocity) { - final int numPointers = mNumPointers; - final PointerData[] pointers = mPointers; final int lastTouchIndex = mLastTouchIndex; - for (int p = 0; p < numPointers; p++) { - final PointerData pointerData = pointers[p]; - final long[] pastTime = pointerData.pastTime; + for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { + final long[] pastTime = pointer.pastTime; // Search backwards in time for oldest acceptable time. // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE. @@ -253,8 +280,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { } // Kind-of stupid. - final float[] pastX = pointerData.pastX; - final float[] pastY = pointerData.pastY; + final float[] pastX = pointer.pastX; + final float[] pastY = pointer.pastY; final float oldestX = pastX[oldestTouchIndex]; final float oldestY = pastY[oldestTouchIndex]; @@ -290,11 +317,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { accumY = maxVelocity; } - pointerData.xVelocity = accumX; - pointerData.yVelocity = accumY; + pointer.xVelocity = accumX; + pointer.yVelocity = accumY; if (localLOGV) { - Log.v(TAG, "[" + p + "] Pointer " + pointerData.id + Log.v(TAG, "Pointer " + pointer.id + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches); } } @@ -307,8 +334,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed X velocity. */ public float getXVelocity() { - PointerData pointerData = getPointerData(0); - return pointerData != null ? pointerData.xVelocity : 0; + Pointer pointer = getPointer(0); + return pointer != null ? pointer.xVelocity : 0; } /** @@ -318,8 +345,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed Y velocity. */ public float getYVelocity() { - PointerData pointerData = getPointerData(0); - return pointerData != null ? pointerData.yVelocity : 0; + Pointer pointer = getPointer(0); + return pointer != null ? pointer.yVelocity : 0; } /** @@ -330,8 +357,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed X velocity. */ public float getXVelocity(int id) { - PointerData pointerData = getPointerData(id); - return pointerData != null ? pointerData.xVelocity : 0; + Pointer pointer = getPointer(id); + return pointer != null ? pointer.xVelocity : 0; } /** @@ -342,19 +369,68 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed Y velocity. */ public float getYVelocity(int id) { - PointerData pointerData = getPointerData(id); - return pointerData != null ? pointerData.yVelocity : 0; + Pointer pointer = getPointer(id); + return pointer != null ? pointer.yVelocity : 0; } - private final PointerData getPointerData(int id) { - final PointerData[] pointers = mPointers; - final int numPointers = mNumPointers; - for (int p = 0; p < numPointers; p++) { - PointerData pointerData = pointers[p]; - if (pointerData.id == id) { - return pointerData; + private final Pointer getPointer(int id) { + for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { + if (pointer.id == id) { + return pointer; } } return null; } + + private static final Pointer obtainPointer() { + synchronized (sPool) { + if (sRecycledPointerCount != 0) { + Pointer element = sRecycledPointerListHead; + sRecycledPointerCount -= 1; + sRecycledPointerListHead = element.next; + element.next = null; + return element; + } + } + return new Pointer(); + } + + private static final void releasePointer(Pointer pointer) { + synchronized (sPool) { + if (sRecycledPointerCount < POINTER_POOL_CAPACITY) { + pointer.next = sRecycledPointerListHead; + sRecycledPointerCount += 1; + sRecycledPointerListHead = pointer; + } + } + } + + private static final void releasePointerList(Pointer pointer) { + if (pointer != null) { + synchronized (sPool) { + int count = sRecycledPointerCount; + if (count >= POINTER_POOL_CAPACITY) { + return; + } + + Pointer tail = pointer; + for (;;) { + count += 1; + if (count >= POINTER_POOL_CAPACITY) { + break; + } + + Pointer next = tail.next; + if (next == null) { + break; + } + tail = next; + } + + tail.next = sRecycledPointerListHead; + sRecycledPointerCount = count; + sRecycledPointerListHead = pointer; + } + } + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7c644e41b946..570793bda6e0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,6 +16,7 @@ package android.view; +import android.graphics.Camera; import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; @@ -1374,14 +1375,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * Width as measured during measure pass. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") protected int mMeasuredWidth; /** * Height as measured during measure pass. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") protected int mMeasuredHeight; /** @@ -1436,8 +1437,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility static final int MEASURED_DIMENSION_SET = 0x00000800; /** {@hide} */ static final int FORCE_LAYOUT = 0x00001000; - - private static final int LAYOUT_REQUIRED = 0x00002000; + /** {@hide} */ + static final int LAYOUT_REQUIRED = 0x00002000; private static final int PRESSED = 0x00004000; @@ -1537,6 +1538,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** + * Indicates that pivotX or pivotY were explicitly set and we should not assume the center + * for transform operations + * + * @hide + */ + private static final int PIVOT_EXPLICITLY_SET = 0x10000000; + + /** * The parent this view is attached to. * {@hide} * @@ -1627,6 +1636,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private boolean mMatrixIsIdentity = true; /** + * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set. + */ + private Camera mCamera = null; + + /** + * This matrix is used when computing the matrix for 3D rotations. + */ + private Matrix matrix3D = null; + + /** + * These prev values are used to recalculate a centered pivot point when necessary. The + * pivot point is only used in matrix operations (when rotation, scale, or translation are + * set), so thes values are only used then as well. + */ + private int mPrevWidth = -1; + private int mPrevHeight = -1; + + /** + * Convenience value to check for float values that are close enough to zero to be considered + * zero. + */ + private static float NONZERO_EPSILON = .001f; + + /** + * The degrees rotation around the vertical axis through the pivot point. + */ + @ViewDebug.ExportedProperty + private float mRotationY = 0f; + + /** + * The degrees rotation around the horizontal axis through the pivot point. + */ + @ViewDebug.ExportedProperty + private float mRotationX = 0f; + + /** * The degrees rotation around the pivot point. */ @ViewDebug.ExportedProperty @@ -1684,28 +1729,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * to the left edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mLeft; /** * The distance in pixels from the left edge of this view's parent * to the right edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mRight; /** * The distance in pixels from the top edge of this view's parent * to the top edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mTop; /** * The distance in pixels from the top edge of this view's parent * to the bottom edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mBottom; /** @@ -1713,14 +1758,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * horizontally. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") protected int mScrollX; /** * The offset, in pixels, by which the content of this view is scrolled * vertically. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") protected int mScrollY; /** @@ -1728,28 +1773,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * left edge of this view and the left edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingLeft; /** * The right padding in pixels, that is the distance in pixels between the * right edge of this view and the right edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingRight; /** * The top padding in pixels, that is the distance in pixels between the * top edge of this view and the top edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingTop; /** * The bottom padding in pixels, that is the distance in pixels between the * bottom edge of this view and the bottom edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingBottom; /** @@ -1760,13 +1805,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility /** * Cache the paddingRight set by the user to append to the scrollbar's size. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") int mUserPaddingRight; /** * Cache the paddingBottom set by the user to append to the scrollbar's size. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") int mUserPaddingBottom; /** @@ -1828,8 +1873,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private int[] mDrawableState = null; - private SoftReference<Bitmap> mDrawingCache; - private SoftReference<Bitmap> mUnscaledDrawingCache; + private Bitmap mDrawingCache; + private Bitmap mUnscaledDrawingCache; /** * When this view has focus and the next focus is {@link #FOCUS_LEFT}, @@ -1873,14 +1918,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * The minimum height of the view. We'll try our best to have the height * of this view to at least this amount. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mMinHeight; /** * The minimum width of the view. We'll try our best to have the width * of this view to at least this amount. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mMinWidth; /** @@ -2722,7 +2767,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this view has or contains focus, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public boolean hasFocus() { return (mPrivateFlags & FOCUSED) != 0; } @@ -2900,7 +2945,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this view has focus, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public boolean isFocused() { return (mPrivateFlags & FOCUSED) != 0; } @@ -3311,7 +3356,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return true if this view has nothing to draw, false otherwise */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean willNotDraw() { return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW; } @@ -3334,7 +3379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return true if this view does not cache its drawing, false otherwise */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean willNotCacheDrawing() { return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING; } @@ -3509,7 +3554,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @return True if this view can take focus, or false otherwise. * @attr ref android.R.styleable#View_focusable */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public final boolean isFocusable() { return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); } @@ -4820,7 +4865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return The width of your view, in pixels. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public final int getWidth() { return mRight - mLeft; } @@ -4830,7 +4875,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return The height of your view, in pixels. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public final int getHeight() { return mBottom - mTop; } @@ -4888,6 +4933,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Utility function to determine if the value is far enough away from zero to be + * considered non-zero. + * @param value A floating point value to check for zero-ness + * @return whether the passed-in value is far enough away from zero to be considered non-zero + */ + private static boolean nonzero(float value) { + return (value < -NONZERO_EPSILON || value > NONZERO_EPSILON); + } + + /** * Recomputes the transform matrix if necessary. * * @return True if the transform matrix is the identity matrix, false otherwise. @@ -4896,10 +4951,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility if (mMatrixDirty) { // transform-related properties have changed since the last time someone // asked for the matrix; recalculate it with the current values + + // Figure out if we need to update the pivot point + if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { + if ((mRight - mLeft) != mPrevWidth && (mBottom - mTop) != mPrevHeight) { + mPrevWidth = mRight - mLeft; + mPrevHeight = mBottom - mTop; + mPivotX = (float) mPrevWidth / 2f; + mPivotY = (float) mPrevHeight / 2f; + } + } mMatrix.reset(); mMatrix.setTranslate(mTranslationX, mTranslationY); mMatrix.preRotate(mRotation, mPivotX, mPivotY); mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY); + if (nonzero(mRotationX) || nonzero(mRotationY)) { + if (mCamera == null) { + mCamera = new Camera(); + matrix3D = new Matrix(); + } + mCamera.save(); + mCamera.rotateX(mRotationX); + mCamera.rotateY(mRotationY); + mCamera.getMatrix(matrix3D); + matrix3D.preTranslate(-mPivotX, -mPivotY); + matrix3D.postTranslate(mPivotX, mPivotY); + mMatrix.postConcat(matrix3D); + mCamera.restore(); + } mMatrixDirty = false; mMatrixIsIdentity = mMatrix.isIdentity(); mInverseMatrixDirty = true; @@ -4955,6 +5034,64 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * The degrees that the view is rotated around the vertical axis through the pivot point. + * + * @see #getPivotX() + * @see #getPivotY() + * @return The degrees of Y rotation. + */ + public float getRotationY() { + return mRotationY; + } + + /** + * Sets the degrees that the view is rotated around the vertical axis through pivot point. + * + * @param rotationY The degrees of Y rotation. + * @see #getPivotX() + * @see #getPivotY() + */ + public void setRotationY(float rotationY) { + if (mRotationY != rotationY) { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + mRotationY = rotationY; + mMatrixDirty = true; + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + + /** + * The degrees that the view is rotated around the horizontal axis through the pivot point. + * + * @see #getPivotX() + * @see #getPivotY() + * @return The degrees of X rotation. + */ + public float getRotationX() { + return mRotationX; + } + + /** + * Sets the degrees that the view is rotated around the horizontal axis through pivot point. + * + * @param rotationX The degrees of X rotation. + * @see #getPivotX() + * @see #getPivotY() + */ + public void setRotationX(float rotationX) { + if (mRotationX != rotationX) { + // Double-invalidation is necessary to capture view's old and new areas + invalidate(); + mRotationX = rotationX; + mMatrixDirty = true; + mPrivateFlags |= DRAWN; // force another invalidation with the new orientation + invalidate(); + } + } + + /** * The amount that the view is scaled in x around the pivot point, as a proportion of * the view's unscaled width. A value of 1, the default, means that no scaling is applied. * @@ -5035,6 +5172,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility /** * Sets the x location of the point around which the view is * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}. + * By default, the pivot point is centered on the object. + * Setting this property disables this behavior and causes the view to use only the + * explicitly set pivotX and pivotY values. * * @param pivotX The x location of the pivot point. * @see #getRotation() @@ -5043,6 +5183,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see #getPivotY() */ public void setPivotX(float pivotX) { + mPrivateFlags |= PIVOT_EXPLICITLY_SET; if (mPivotX != pivotX) { // Double-invalidation is necessary to capture view's old and new areas invalidate(); @@ -5069,7 +5210,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility /** * Sets the y location of the point around which the view is {@link #setRotation(float) rotated} - * and {@link #setScaleY(float) scaled}. + * and {@link #setScaleY(float) scaled}. By default, the pivot point is centered on the object. + * Setting this property disables this behavior and causes the view to use only the + * explicitly set pivotX and pivotY values. * * @param pivotY The y location of the pivot point. * @see #getRotation() @@ -5078,6 +5221,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see #getPivotY() */ public void setPivotY(float pivotY) { + mPrivateFlags |= PIVOT_EXPLICITLY_SET; if (mPivotY != pivotY) { // Double-invalidation is necessary to capture view's old and new areas invalidate(); @@ -5312,15 +5456,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * is still within the view. */ private boolean pointInView(float localX, float localY, float slop) { - if (!hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = localX; - localXY[1] = localY; - getInverseMatrix().mapPoints(localXY); - localX = localXY[0]; - localY = localXY[1]; - } return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) && localY < ((mBottom - mTop) + slop); } @@ -5781,7 +5916,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this View is guaranteed to be fully opaque, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isOpaque() { return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK; } @@ -6866,7 +7001,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see #setDrawingCacheEnabled(boolean) * @see #getDrawingCache() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isDrawingCacheEnabled() { return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED; } @@ -6916,8 +7051,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) { buildDrawingCache(autoScale); } - return autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) : - (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get()); + return autoScale ? mDrawingCache : mUnscaledDrawingCache; } /** @@ -6932,13 +7066,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public void destroyDrawingCache() { if (mDrawingCache != null) { - final Bitmap bitmap = mDrawingCache.get(); - if (bitmap != null) bitmap.recycle(); + mDrawingCache.recycle(); mDrawingCache = null; } if (mUnscaledDrawingCache != null) { - final Bitmap bitmap = mUnscaledDrawingCache.get(); - if (bitmap != null) bitmap.recycle(); + mUnscaledDrawingCache.recycle(); mUnscaledDrawingCache = null; } } @@ -6999,8 +7131,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ? - (mDrawingCache == null || mDrawingCache.get() == null) : - (mUnscaledDrawingCache == null || mUnscaledDrawingCache.get() == null))) { + mDrawingCache == null : mUnscaledDrawingCache == null)) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); @@ -7033,8 +7164,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } boolean clear = true; - Bitmap bitmap = autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) : - (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get()); + Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { Bitmap.Config quality; @@ -7066,9 +7196,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility bitmap = Bitmap.createBitmap(width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); if (autoScale) { - mDrawingCache = new SoftReference<Bitmap>(bitmap); + mDrawingCache = bitmap; } else { - mUnscaledDrawingCache = new SoftReference<Bitmap>(bitmap); + mUnscaledDrawingCache = bitmap; } if (opaque && translucentWindow) bitmap.setHasAlpha(false); } catch (OutOfMemoryError e) { @@ -8735,7 +8865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @return the offset of the baseline within the widget's bounds or -1 * if baseline alignment is not supported */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int getBaseline() { return -1; } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 5dd45f9df03c..2ca08ea8be29 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -255,6 +255,14 @@ public class ViewDebug { * @see #deepExport() */ String prefix() default ""; + + /** + * Specifies the category the property falls into, such as measurement, + * layout, drawing, etc. + * + * @return the category as String + */ + String category() default ""; } /** @@ -934,65 +942,76 @@ public class ViewDebug { private static void profileViewAndChildren(final View view, BufferedWriter out) throws IOException { - final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() { - public Void[] pre() { - forceLayout(view); - return null; - } - - private void forceLayout(View view) { - view.forceLayout(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - forceLayout(group.getChildAt(i)); - } - } - } + profileViewAndChildren(view, out, true); + } - public void run(Void... data) { - view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); - } + private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root) + throws IOException { - public void post(Void... data) { - } - }); + long durationMeasure = + (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation( + view, new ViewOperation<Void>() { + public Void[] pre() { + forceLayout(view); + return null; + } - final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() { - public Void[] pre() { - return null; - } + private void forceLayout(View view) { + view.forceLayout(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + forceLayout(group.getChildAt(i)); + } + } + } - public void run(Void... data) { - view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); - } + public void run(Void... data) { + view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); + } - public void post(Void... data) { - } - }); + public void post(Void... data) { + } + }) + : 0; + long durationLayout = + (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation( + view, new ViewOperation<Void>() { + public Void[] pre() { + return null; + } - final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() { - public Object[] pre() { - final DisplayMetrics metrics = view.getResources().getDisplayMetrics(); - final Bitmap bitmap = - Bitmap.createBitmap(metrics.widthPixels, metrics.heightPixels, - Bitmap.Config.RGB_565); - final Canvas canvas = new Canvas(bitmap); - return new Object[] { - bitmap, canvas - }; - } + public void run(Void... data) { + view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); + } - public void run(Object... data) { - view.draw((Canvas) data[1]); - } + public void post(Void... data) { + } + }) : 0; + long durationDraw = + (root || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(view, + new ViewOperation<Object>() { + public Object[] pre() { + final DisplayMetrics metrics = + view.getResources().getDisplayMetrics(); + final Bitmap bitmap = + Bitmap.createBitmap(metrics.widthPixels, + metrics.heightPixels, Bitmap.Config.RGB_565); + final Canvas canvas = new Canvas(bitmap); + return new Object[] { + bitmap, canvas + }; + } - public void post(Object... data) { - ((Bitmap) data[0]).recycle(); - } - }); + public void run(Object... data) { + view.draw((Canvas) data[1]); + } + public void post(Object... data) { + ((Bitmap) data[0]).recycle(); + } + }) : 0; out.write(String.valueOf(durationMeasure)); out.write(' '); out.write(String.valueOf(durationLayout)); @@ -1003,7 +1022,7 @@ public class ViewDebug { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); for (int i = 0; i < count; i++) { - profileViewAndChildren(group.getChildAt(i), out); + profileViewAndChildren(group.getChildAt(i), out, false); } } } @@ -1033,7 +1052,10 @@ public class ViewDebug { }); try { - latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS); + if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) { + Log.w("View", "Could not complete the profiling of the view " + view); + return -1; + } } catch (InterruptedException e) { Log.w("View", "Could not complete the profiling of the view " + view); Thread.currentThread().interrupt(); @@ -1354,9 +1376,12 @@ public class ViewDebug { // TODO: This should happen on the UI thread Object methodValue = method.invoke(view, (Object[]) null); final Class<?> returnType = method.getReturnType(); + final ExportedProperty property = sAnnotations.get(method); + String categoryPrefix = + property.category().length() != 0 ? property.category() + ":" : ""; if (returnType == int.class) { - final ExportedProperty property = sAnnotations.get(method); + if (property.resolveId() && context != null) { final int id = (Integer) methodValue; methodValue = resolveId(context, id); @@ -1364,7 +1389,8 @@ public class ViewDebug { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = (Integer) methodValue; - final String valuePrefix = prefix + method.getName() + '_'; + final String valuePrefix = + categoryPrefix + prefix + method.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } @@ -1388,21 +1414,22 @@ public class ViewDebug { } } } else if (returnType == int[].class) { - final ExportedProperty property = sAnnotations.get(method); final int[] array = (int[]) methodValue; - final String valuePrefix = prefix + method.getName() + '_'; + final String valuePrefix = categoryPrefix + prefix + method.getName() + '_'; final String suffix = "()"; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); + + // Probably want to return here, same as for fields. + return; } else if (!returnType.isPrimitive()) { - final ExportedProperty property = sAnnotations.get(method); if (property.deepExport()) { dumpViewProperties(context, methodValue, out, prefix + property.prefix()); continue; } } - writeEntry(out, prefix, method.getName(), "()", methodValue); + writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue); } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } @@ -1422,9 +1449,12 @@ public class ViewDebug { try { Object fieldValue = null; final Class<?> type = field.getType(); + final ExportedProperty property = sAnnotations.get(field); + String categoryPrefix = + property.category().length() != 0 ? property.category() + ":" : ""; if (type == int.class) { - final ExportedProperty property = sAnnotations.get(field); + if (property.resolveId() && context != null) { final int id = field.getInt(view); fieldValue = resolveId(context, id); @@ -1432,7 +1462,8 @@ public class ViewDebug { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = field.getInt(view); - final String valuePrefix = prefix + field.getName() + '_'; + final String valuePrefix = + categoryPrefix + prefix + field.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } @@ -1454,9 +1485,8 @@ public class ViewDebug { } } } else if (type == int[].class) { - final ExportedProperty property = sAnnotations.get(field); final int[] array = (int[]) field.get(view); - final String valuePrefix = prefix + field.getName() + '_'; + final String valuePrefix = categoryPrefix + prefix + field.getName() + '_'; final String suffix = ""; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); @@ -1464,10 +1494,9 @@ public class ViewDebug { // We exit here! return; } else if (!type.isPrimitive()) { - final ExportedProperty property = sAnnotations.get(field); if (property.deepExport()) { - dumpViewProperties(context, field.get(view), out, - prefix + property.prefix()); + dumpViewProperties(context, field.get(view), out, prefix + + property.prefix()); continue; } } @@ -1476,7 +1505,7 @@ public class ViewDebug { fieldValue = field.get(view); } - writeEntry(out, prefix, field.getName(), "", fieldValue); + writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue); } catch (IllegalAccessException e) { } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 9da56378684c..e2f9c1513238 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -377,7 +377,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS}, * {@link #FOCUS_BLOCK_DESCENDANTS}. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "focus", mapping = { @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS") @@ -905,19 +905,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } // Calculate the offset point into the target's local coordinates - float xc; - float yc; - if (target.hasIdentityMatrix() || mAttachInfo == null) { - xc = scrolledXFloat - (float) target.mLeft; - yc = scrolledYFloat - (float) target.mTop; - } else { + 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] = scrolledXFloat; - localXY[1] = scrolledYFloat; + localXY[0] = xc; + localXY[1] = yc; target.getInverseMatrix().mapPoints(localXY); - xc = localXY[0] - (float) target.mLeft; - yc = localXY[1] - (float) target.mTop; + xc = localXY[0]; + yc = localXY[1]; } // if have a target, see if we're allowed to and want to intercept its @@ -2835,7 +2832,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setChildrenDrawnWithCacheEnabled(boolean) * @see View#setDrawingCacheEnabled(boolean) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isAlwaysDrawnWithCacheEnabled() { return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE; } @@ -2870,7 +2867,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setAlwaysDrawnWithCacheEnabled(boolean) * @see #setChildrenDrawnWithCacheEnabled(boolean) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean isChildrenDrawnWithCacheEnabled() { return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE; } @@ -2902,7 +2899,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setChildrenDrawingOrderEnabled(boolean) * @see #getChildDrawingOrder(int, int) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean isChildrenDrawingOrderEnabled() { return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER; } @@ -2939,7 +2936,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "drawing", mapping = { @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"), @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"), @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"), @@ -3572,7 +3569,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @@ -3583,7 +3580,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @@ -3708,25 +3705,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * The left margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int leftMargin; /** * The top margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int topMargin; /** * The right margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int rightMargin; /** * The bottom margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int bottomMargin; /** diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 8abbf58f0118..faa478309fc4 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -33,6 +33,7 @@ import android.util.Config; import android.util.DisplayMetrics; import android.util.Log; import android.util.EventLog; +import android.util.Slog; import android.util.SparseArray; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; @@ -1719,7 +1720,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn if (LOCAL_LOGV) Log.v( TAG, "Dispatching key " + msg.obj + " to " + mView); - deliverKeyEvent((KeyEvent)msg.obj, true); + deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0); break; case DISPATCH_POINTER: { MotionEvent event = (MotionEvent) msg.obj; @@ -1860,9 +1861,14 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn } private void finishKeyEvent(KeyEvent event) { + if (LOCAL_LOGV) Log.v(TAG, "Telling window manager key is finished"); + if (mFinishedCallback != null) { mFinishedCallback.run(); mFinishedCallback = null; + } else { + Slog.w(TAG, "Attempted to tell the input queue that the current key event " + + "is finished but there is no key event actually in progress."); } } @@ -2321,8 +2327,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn boolean handled = mView == null || mView.dispatchKeyEventPreIme(event); if (handled) { if (sendDone) { - if (LOCAL_LOGV) Log.v( - TAG, "Telling window manager key is finished"); finishKeyEvent(event); } return; @@ -2353,8 +2357,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn if (!handled) { deliverKeyEventToViewHierarchy(event, sendDone); } else if (sendDone) { - if (LOCAL_LOGV) Log.v( - TAG, "Telling window manager key is finished"); finishKeyEvent(event); } else { Log.w(TAG, "handleFinishedEvent(seq=" + seq @@ -2428,8 +2430,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn } finally { if (sendDone) { - if (LOCAL_LOGV) Log.v( - TAG, "Telling window manager key is finished"); finishKeyEvent(event); } // Let the exception fall through -- the looper will catch @@ -2613,9 +2613,14 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn private final InputHandler mInputHandler = new InputHandler() { public void handleKey(KeyEvent event, Runnable finishedCallback) { + if (mFinishedCallback != null) { + Slog.w(TAG, "Received a new key event from the input queue but there is " + + "already an unfinished key event in progress."); + } + mFinishedCallback = finishedCallback; - dispatchKey(event); + dispatchKey(event, true); } public void handleMotion(MotionEvent event, Runnable finishedCallback) { @@ -2626,9 +2631,13 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn }; public void dispatchKey(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - //noinspection ConstantConditions - if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { + dispatchKey(event, false); + } + + private void dispatchKey(KeyEvent event, boolean sendDone) { + //noinspection ConstantConditions + if (false && event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { if (DBG) Log.d("keydisp", "==================================================="); if (DBG) Log.d("keydisp", "Focused view Hierarchy is:"); @@ -2640,6 +2649,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn Message msg = obtainMessage(DISPATCH_KEY); msg.obj = event; + msg.arg1 = sendDone ? 1 : 0; if (LOCAL_LOGV) Log.v( TAG, "sending key " + event + " to " + mView); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index f32ff77a8391..36f6bb2466de 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -64,14 +64,24 @@ public abstract class Window { */ public static final int FEATURE_ACTION_BAR = 8; /** + * Flag for requesting an Action Bar that overlays window content. + * Normally an Action Bar will sit in the space above window content, but if this + * feature is requested along with {@link #FEATURE_ACTION_BAR} it will be layered over + * the window content itself. This is useful if you would like your app to have more control + * over how the Action Bar is displayed, such as letting application content scroll beneath + * an Action Bar with a transparent background or otherwise displaying a transparent/translucent + * Action Bar over application content. + */ + public static final int FEATURE_ACTION_BAR_OVERLAY = 9; + /** * Flag for specifying the behavior of action modes when an Action Bar is not present. * If overlay is enabled, the action mode UI will be allowed to cover existing window content. */ - public static final int FEATURE_ACTION_MODE_OVERLAY = 9; + public static final int FEATURE_ACTION_MODE_OVERLAY = 10; /** * Flag for requesting this window to be hardware accelerated, if possible. */ - public static final int FEATURE_HARDWARE_ACCELERATED = 10; + public static final int FEATURE_HARDWARE_ACCELERATED = 11; /** Flag for setting the progress bar's visibility to VISIBLE */ public static final int PROGRESS_VISIBILITY_ON = -1; /** Flag for setting the progress bar's visibility to GONE */ diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 33757f023ec3..659f9cd673c6 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -779,11 +779,6 @@ public interface WindowManagerPolicy { */ public void enableScreenAfterBoot(); - /** - * Called every time the window manager is dispatching a pointer event. - */ - public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY); - public void setCurrentOrientationLw(int newOrientation); /** diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 25df1f41b18d..fed55dcb10a3 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -68,7 +68,7 @@ public abstract class WindowOrientationListener { mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist - mSensorEventListener = new SensorEventListenerImpl(); + mSensorEventListener = new SensorEventListenerImpl(this); } } @@ -109,8 +109,35 @@ public abstract class WindowOrientationListener { } return -1; } - - class SensorEventListenerImpl implements SensorEventListener { + + /** + * This class filters the raw accelerometer data and tries to detect actual changes in + * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, + * but here's the outline: + * + * - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're + * dealing with rotation of the device, this is the sensible coordinate system to work in. The + * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance + * is referred to as the magnitude below. The elevation angle is referred to as the "tilt" + * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is + * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. + * + * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior. + * + * - When the orientation angle reaches a certain threshold, transition to the corresponding + * orientation. These thresholds have some hysteresis built-in to avoid oscillation. + * + * - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude + * should equal to that of gravity. When it differs significantly, we know the device is under + * external acceleration and we can't trust the data. + * + * - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high + * in magnitude, we distrust the orientation data, because when the device is nearly flat, small + * physical movements produce large changes in orientation angle. + * + * Details are explained below. + */ + static class SensorEventListenerImpl implements SensorEventListener { // We work with all angles in degrees in this class. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); @@ -125,54 +152,50 @@ public abstract class WindowOrientationListener { private static final int ROTATION_90 = 1; private static final int ROTATION_270 = 2; - // Current orientation state - private int mRotation = ROTATION_0; - // Mapping our internal aliases into actual Surface rotation values - private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90, - Surface.ROTATION_270}; + private static final int[] SURFACE_ROTATIONS = new int[] { + Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; // Threshold ranges of orientation angle to transition into other orientation states. // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc. // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept // in sync with this. - // The thresholds are nearly regular -- we generally transition about the halfway point - // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180, - // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180. - private final int[][][] THRESHOLDS = new int[][][] { + // We generally transition about the halfway point between two states with a swing of 30 + // degrees for hysteresis. + private static final int[][][] THRESHOLDS = new int[][][] { {{60, 180}, {180, 300}}, + {{0, 30}, {195, 315}, {315, 360}}, {{0, 45}, {45, 165}, {330, 360}}, - {{0, 30}, {195, 315}, {315, 360}} }; // See THRESHOLDS - private final int[][] ROTATE_TO = new int[][] { - {ROTATION_270, ROTATION_90}, + private static final int[][] ROTATE_TO = new int[][] { + {ROTATION_90, ROTATION_270}, {ROTATION_0, ROTATION_270, ROTATION_0}, - {ROTATION_0, ROTATION_90, ROTATION_0} + {ROTATION_0, ROTATION_90, ROTATION_0}, }; - // Maximum absolute tilt angle at which to consider orientation changes. Beyond this (i.e. - // when screen is facing the sky or ground), we refuse to make any orientation changes. - private static final int MAX_TILT = 65; + // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. + // when screen is facing the sky or ground), we completely ignore orientation data. + private static final int MAX_TILT = 75; // Additional limits on tilt angle to transition to each new orientation. We ignore all - // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a + // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a // particular orientation here. - private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT}; + private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65}; // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter // with a higher time constant, making us less sensitive to change. This primarily helps // prevent momentary orientation changes when placing a device on a table from the side (or // picking one up). - private static final int PARTIAL_TILT = 45; + private static final int PARTIAL_TILT = 50; // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity, // in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust // the sensor data. However, under constantly vibrating conditions (think car mount), we // still want to pick up changes, so rather than ignore the data, we filter it with a very // high time constant. - private static final int MAX_DEVIATION_FROM_GRAVITY = 1; + private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f; // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no // way to get this information from SensorManager. @@ -185,28 +208,46 @@ public abstract class WindowOrientationListener { // background. // When device is near-vertical (screen approximately facing the horizon) - private static final int DEFAULT_TIME_CONSTANT_MS = 200; + private static final int DEFAULT_TIME_CONSTANT_MS = 50; // When device is partially tilted towards the sky or ground - private static final int TILTED_TIME_CONSTANT_MS = 600; + private static final int TILTED_TIME_CONSTANT_MS = 300; // When device is under external acceleration, i.e. not just gravity. We heavily distrust // such readings. - private static final int ACCELERATING_TIME_CONSTANT_MS = 5000; + private static final int ACCELERATING_TIME_CONSTANT_MS = 2000; private static final float DEFAULT_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS); private static final float TILTED_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(TILTED_TIME_CONSTANT_MS); private static final float ACCELERATING_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS); + + private WindowOrientationListener mOrientationListener; + private int mRotation = ROTATION_0; // Current orientation state + private float mTiltAngle = 0; // low-pass filtered + private float mOrientationAngle = 0; // low-pass filtered - // The low-pass filtered accelerometer data - private float[] mFilteredVector = new float[] {0, 0, 0}; + /* + * Each "distrust" counter represents our current level of distrust in the data based on + * a certain signal. For each data point that is deemed unreliable based on that signal, + * the counter increases; otherwise, the counter decreases. Exact rules vary. + */ + private int mAccelerationDistrust = 0; // based on magnitude != gravity + private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees + + public SensorEventListenerImpl(WindowOrientationListener orientationListener) { + mOrientationListener = orientationListener; + } + + private static float computeLowpassAlpha(int timeConstantMs) { + return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS); + } int getCurrentRotation() { return SURFACE_ROTATIONS[mRotation]; } - private void calculateNewRotation(int orientation, int tiltAngle) { + private void calculateNewRotation(float orientation, float tiltAngle) { if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation); int thresholdRanges[][] = THRESHOLDS[mRotation]; int row = -1; @@ -226,7 +267,7 @@ public abstract class WindowOrientationListener { if (localLOGV) Log.i(TAG, " new rotation = " + rotation); mRotation = rotation; - onOrientationChanged(SURFACE_ROTATIONS[rotation]); + mOrientationListener.onOrientationChanged(getCurrentRotation()); } private float lowpassFilter(float newValue, float oldValue, float alpha) { @@ -238,11 +279,11 @@ public abstract class WindowOrientationListener { } /** - * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90]. - * 90 degrees = screen facing the sky or ground. + * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90]. + * +/- 90 degrees = screen facing the sky or ground. */ private float tiltAngle(float z, float magnitude) { - return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES); + return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES; } public void onSensorChanged(SensorEvent event) { @@ -253,34 +294,124 @@ public abstract class WindowOrientationListener { float z = event.values[_DATA_Z]; float magnitude = vectorMagnitude(x, y, z); float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY); - float tiltAngle = tiltAngle(z, magnitude); + handleAccelerationDistrust(deviation); + + // only filter tilt when we're accelerating + float alpha = 1; + if (mAccelerationDistrust > 0) { + alpha = ACCELERATING_LOWPASS_ALPHA; + } + float newTiltAngle = tiltAngle(z, magnitude); + mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha); + + float absoluteTilt = Math.abs(mTiltAngle); + if (checkFullyTilted(absoluteTilt)) { + return; // when fully tilted, ignore orientation entirely + } + + float newOrientationAngle = computeNewOrientation(x, y); + filterOrientation(absoluteTilt, newOrientationAngle); + calculateNewRotation(mOrientationAngle, absoluteTilt); + } + + /** + * When accelerating, increment distrust; otherwise, decrement distrust. The idea is that + * if a single jolt happens among otherwise good data, we should keep trusting the good + * data. On the other hand, if a series of many bad readings comes in (as if the phone is + * being rapidly shaken), we should wait until things "settle down", i.e. we get a string + * of good readings. + * + * @param deviation absolute difference between the current magnitude and gravity + */ + private void handleAccelerationDistrust(float deviation) { + if (deviation > MAX_DEVIATION_FROM_GRAVITY) { + if (mAccelerationDistrust < 5) { + mAccelerationDistrust++; + } + } else if (mAccelerationDistrust > 0) { + mAccelerationDistrust--; + } + } + + /** + * Check if the phone is tilted towards the sky or ground and handle that appropriately. + * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we + * decrement it. The idea is to distrust the first few readings after the phone gets + * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is + * picked up from a table. + * + * We also reset the orientation angle to the center of the current screen orientation. + * Since there is no real orientation of the phone, we want to ignore the most recent sensor + * data and reset it to this value to avoid a premature transition when the phone starts to + * get un-tilted. + * + * @param absoluteTilt the absolute value of the current tilt angle + * @return true if the phone is fully tilted + */ + private boolean checkFullyTilted(float absoluteTilt) { + boolean fullyTilted = absoluteTilt > MAX_TILT; + if (fullyTilted) { + if (mRotation == ROTATION_0) { + mOrientationAngle = 0; + } else if (mRotation == ROTATION_90) { + mOrientationAngle = 90; + } else { // ROTATION_270 + mOrientationAngle = 270; + } + + if (mTiltDistrust < 3) { + mTiltDistrust = 3; + } + } else if (mTiltDistrust > 0) { + mTiltDistrust--; + } + return fullyTilted; + } + + /** + * Angle between the x-y projection of upVector and the +y-axis, increasing + * clockwise. + * 0 degrees = speaker end towards the sky + * 90 degrees = right edge of device towards the sky + */ + private float computeNewOrientation(float x, float y) { + float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES; + // atan2 returns [-180, 180]; normalize to [0, 360] + if (orientationAngle < 0) { + orientationAngle += 360; + } + return orientationAngle; + } + + /** + * Compute a new filtered orientation angle. + */ + private void filterOrientation(float absoluteTilt, float orientationAngle) { float alpha = DEFAULT_LOWPASS_ALPHA; - if (tiltAngle > MAX_TILT) { - return; - } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) { + if (mTiltDistrust > 0 || mAccelerationDistrust > 1) { + // when fully tilted, or under more than a transient acceleration, distrust heavily alpha = ACCELERATING_LOWPASS_ALPHA; - } else if (tiltAngle > PARTIAL_TILT) { + } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) { + // when tilted partway, or under transient acceleration, distrust lightly alpha = TILTED_LOWPASS_ALPHA; } - x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha); - y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha); - z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha); - magnitude = vectorMagnitude(x, y, z); - tiltAngle = tiltAngle(z, magnitude); - - // Angle between the x-y projection of upVector and the +y-axis, increasing - // counter-clockwise. - // 0 degrees = speaker end towards the sky - // 90 degrees = left edge of device towards the sky - float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES; - int orientation = Math.round(orientationAngle); - // atan2 returns (-180, 180]; normalize to [0, 360) - if (orientation < 0) { - orientation += 360; + // since we're lowpass filtering a value with periodic boundary conditions, we need to + // adjust the new value to filter in the right direction... + float deltaOrientation = orientationAngle - mOrientationAngle; + if (deltaOrientation > 180) { + orientationAngle -= 360; + } else if (deltaOrientation < -180) { + orientationAngle += 360; + } + mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha); + // ...and then adjust back to ensure we're in the range [0, 360] + if (mOrientationAngle > 360) { + mOrientationAngle -= 360; + } else if (mOrientationAngle < 0) { + mOrientationAngle += 360; } - calculateNewRotation(orientation, Math.round(tiltAngle)); } public void onAccuracyChanged(Sensor sensor, int accuracy) { diff --git a/core/java/android/webkit/DeviceOrientationManager.java b/core/java/android/webkit/DeviceOrientationManager.java new file mode 100644 index 000000000000..f65dccf124ff --- /dev/null +++ b/core/java/android/webkit/DeviceOrientationManager.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +/** + * This class is simply a container for the methods used to configure WebKit's + * mock DeviceOrientationClient for use in LayoutTests. + * + * This could be part of WebViewCore, but have moved it to its own class to + * avoid bloat there. + * @hide + */ +public final class DeviceOrientationManager { + private WebViewCore mWebViewCore; + + public DeviceOrientationManager(WebViewCore webViewCore) { + mWebViewCore = webViewCore; + } + + /** + * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation + * client. + */ + public void useMock() { + assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); + nativeUseMock(mWebViewCore); + } + + /** + * Set the position for the mock DeviceOrientation service for this WebViewCore. + */ + public void setMockOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta, + double beta, boolean canProvideGamma, double gamma) { + assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); + nativeSetMockOrientation(mWebViewCore, canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } + + // Native functions + private static native void nativeUseMock(WebViewCore webViewCore); + private static native void nativeSetMockOrientation(WebViewCore webViewCore, + boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, + boolean canProvideGamma, double gamma); +} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index d1b0902d8e86..eb363c7ca225 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -3747,6 +3747,26 @@ public class WebView extends AbsoluteLayout } /** + * Called by DRT on UI thread, need to proxy to WebCore thread. + * + * @hide debug only + */ + public void useMockDeviceOrientation() { + mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION); + } + + /** + * Called by DRT on WebCore thread. + * + * @hide debug only + */ + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } + + /** * Dump the V8 counters to standard output. * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to * true. Otherwise, this will do nothing. diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 3c28c943ca8e..1e7e6c00434f 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -33,6 +33,7 @@ import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.SurfaceView; import android.view.View; +import android.webkit.DeviceOrientationManager; import java.util.ArrayList; import java.util.Collection; @@ -116,6 +117,8 @@ final class WebViewCore { private int mWebkitScrollX = 0; private int mWebkitScrollY = 0; + private DeviceOrientationManager mDeviceOrientationManager = new DeviceOrientationManager(this); + // The thread name used to identify the WebCore thread and for use in // debugging other classes that require operation within the WebCore thread. /* package */ static final String THREAD_NAME = "WebViewCoreThread"; @@ -878,6 +881,8 @@ final class WebViewCore { // accessibility support static final int MODIFY_SELECTION = 190; + static final int USE_MOCK_DEVICE_ORIENTATION = 191; + // private message ids private static final int DESTROY = 200; @@ -1409,6 +1414,10 @@ final class WebViewCore { WebView.SET_TOUCH_HIGHLIGHT_RECTS, null) .sendToTarget(); break; + + case USE_MOCK_DEVICE_ORIENTATION: + useMockDeviceOrientation(); + break; } } }; @@ -2481,6 +2490,16 @@ final class WebViewCore { hMode, vMode).sendToTarget(); } + private void useMockDeviceOrientation() { + mDeviceOrientationManager.useMock(); + } + + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + mDeviceOrientationManager.setMockOrientation(canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } + private native void nativePause(); private native void nativeResume(); private native void nativeFreeMemory(); diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index d75d4212e048..d7b4452ef66f 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -772,6 +772,9 @@ public class WebViewDatabase { } long getCacheTotalSize() { + if (mCacheDatabase == null) { + return 0; + } long size = 0; Cursor cursor = null; final String query = "SELECT SUM(contentlength) as sum FROM cache"; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 372cc833ced6..e572d3dc65a4 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3998,7 +3998,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * View type for this view, as returned by * {@link android.widget.Adapter#getItemViewType(int) } */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "list", mapping = { @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") }) @@ -4010,7 +4010,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * been added to the list view and whether they should be treated as * recycled views or not. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") boolean recycledHeaderFooter; /** @@ -4021,7 +4021,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * view to be attached to the window rather than just attached to the * parent. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") boolean forceAdd; public LayoutParams(Context c, AttributeSet attrs) { diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index fe6d91ac9d63..10a87295ccf5 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -56,7 +56,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The position of the first child displayed */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") int mFirstPosition = 0; /** @@ -141,7 +141,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { * The position within the adapter's data set of the item to select * during the next layout. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mNextSelectedPosition = INVALID_POSITION; /** @@ -152,7 +152,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The position within the adapter's data set of the currently selected item. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mSelectedPosition = INVALID_POSITION; /** @@ -168,7 +168,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The number of items in the current adapter. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mItemCount; /** diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index a6d51702c403..2b723c9b4e4b 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -16,14 +16,22 @@ package android.widget; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; + +import android.animation.PropertyAnimator; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -35,23 +43,100 @@ import android.view.animation.AnimationUtils; * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView */ -public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ +public abstract class AdapterViewAnimator extends AdapterView<Adapter> + implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{ private static final String TAG = "RemoteViewAnimator"; + /** + * The index of the current child, which appears anywhere from the beginning + * to the end of the current set of children, as specified by {@link #mActiveOffset} + */ int mWhichChild = 0; - boolean mFirstTime = true; + + /** + * Whether or not the first view(s) should be animated in + */ boolean mAnimateFirstTime = true; + /** + * Represents where the in the current window of + * views the current <code>mDisplayedChild</code> sits + */ + int mActiveOffset = 0; + + /** + * The number of views that the {@link AdapterViewAnimator} keeps as children at any + * given time (not counting views that are pending removal, see {@link #mPreviousViews}). + */ + int mNumActiveViews = 1; + + /** + * Array of the children of the {@link AdapterViewAnimator}. This array + * is accessed in a circular fashion + */ + View[] mActiveViews; + + /** + * List of views pending removal from the {@link AdapterViewAnimator} + */ + ArrayList<View> mPreviousViews; + + /** + * The index, relative to the adapter, of the beginning of the window of views + */ + int mCurrentWindowStart = 0; + + /** + * The index, relative to the adapter, of the end of the window of views + */ + int mCurrentWindowEnd = -1; + + /** + * The same as {@link #mCurrentWindowStart}, except when the we have bounded + * {@link #mCurrentWindowStart} to be non-negative + */ + int mCurrentWindowStartUnbounded = 0; + + /** + * Indicates whether to treat the adapter to be a circular structure, ie. + * the view before 0 is considered to be <code>mAdapter.getCount() - 1</code> + * + * TODO: this doesn't do anything yet + * + */ + boolean mCycleViews = false; + + /** + * Handler to post events to the main thread + */ + Handler mMainQueue; + + /** + * Listens for data changes from the adapter + */ AdapterDataSetObserver mDataSetObserver; - View mPreviousView; - View mCurrentView; + /** + * The {@link Adapter} for this {@link AdapterViewAnimator} + */ + Adapter mAdapter; + /** + * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator} + */ + RemoteViewsAdapter mRemoteViewsAdapter; + + /** + * Specifies whether this is the first time the animator is showing views + */ + boolean mFirstTime = true; + + /** + * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit. + */ Animation mInAnimation; Animation mOutAnimation; - Adapter mAdapter; - RemoteViewsAdapter mRemoteViewsAdapter; - private Handler mMainQueue; + private ArrayList<View> mViewsToBringToFront; public AdapterViewAnimator(Context context) { super(context); @@ -61,8 +146,10 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV public AdapterViewAnimator(Context context, AttributeSet attrs) { super(context, attrs); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator); - int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ViewAnimator); + int resource = a.getResourceId( + com.android.internal.R.styleable.ViewAnimator_inAnimation, 0); if (resource > 0) { setInAnimation(context, resource); } @@ -72,7 +159,8 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV setOutAnimation(context, resource); } - boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true); + boolean flag = a.getBoolean( + com.android.internal.R.styleable.ViewAnimator_animateFirstView, true); setAnimateFirstView(flag); a.recycle(); @@ -85,6 +173,54 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV */ private void initViewAnimator(Context context, AttributeSet attrs) { mMainQueue = new Handler(Looper.myLooper()); + mActiveViews = new View[mNumActiveViews]; + mPreviousViews = new ArrayList<View>(); + mViewsToBringToFront = new ArrayList<View>(); + } + + /** + * This method is used by subclasses to configure the animator to display the + * desired number of views, and specify the offset + * + * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup} + * @param activeOffset This parameter specifies where the current index ({@link mWhichChild}) + * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, + * and {@link setDisplayedChild} is called with 10, then the effective window will be + * the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the + * window would instead contain indexes 10, 11 and 12. + */ + void configureViewAnimator(int numVisibleViews, int activeOffset) { + if (activeOffset > numVisibleViews - 1) { + // Throw an exception here. + } + mNumActiveViews = numVisibleViews; + mActiveOffset = activeOffset; + mActiveViews = new View[mNumActiveViews]; + mPreviousViews.clear(); + removeAllViewsInLayout(); + mCurrentWindowStart = 0; + mCurrentWindowEnd = -1; + } + + /** + * This class should be overridden by subclasses to customize view transitions within + * the set of visible views + * + * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't + * in the window + * @param toIndex The relative index within the window that the view is going to, -1 if it is + * being removed + * @param view The view that is being animated + */ + void animateViewForTransition(int fromIndex, int toIndex, View view) { + PropertyAnimator pa; + if (fromIndex == -1) { + pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f); + pa.start(); + } else if (toIndex == -1) { + pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f); + pa.start(); + } } /** @@ -114,18 +250,28 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV /** * Return default inAnimation. To be overriden by subclasses. */ - public Animation getDefaultInAnimation() { + Animation getDefaultInAnimation() { return null; } /** - * Return default outAnimation. To be overriden by subclasses. + * Return default outAnimation. To be overridden by subclasses. */ - public Animation getDefaultOutAnimation() { + Animation getDefaultOutAnimation() { return null; } /** + * To be overridden by subclasses. This method applies a view / index specific + * transform to the child view. + * + * @param child + * @param relativeIndex + */ + void applyTransformForChildAtIndex(View child, int relativeIndex) { + } + + /** * Returns the index of the currently displayed child view. */ public int getDisplayedChild() { @@ -160,70 +306,137 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV showOnly(childIndex, animate, false); } - private LayoutParams makeLayoutParams() { - int width = mMeasuredWidth - mPaddingLeft - mPaddingRight; - int height = mMeasuredHeight - mPaddingTop - mPaddingBottom; - return new LayoutParams(width, height); + private int modulo(int pos, int size) { + return (size + (pos % size)) % size; } - protected void showOnly(int childIndex, boolean animate, boolean onLayout) { - if (mAdapter != null) { - // The previous view should be removed from the ViewGroup - if (mPreviousView != null) { - mPreviousView.clearAnimation(); + /** + * Get the view at this index relative to the current window's start + * + * @param relativeIndex Position relative to the current window's start + * @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)]; + } + return null; + } - // TODO: this is where we would store the the view for - // recycling - removeViewInLayout(mPreviousView); - } + private LayoutParams createOrReuseLayoutParams(View v) { + final LayoutParams currentLp = (LayoutParams) v.getLayoutParams(); + if (currentLp instanceof LayoutParams) { + return currentLp; + } + return new LayoutParams(v); + } - // If the current view is still being animated, we should - // force the animation to end - if (mCurrentView != null) { - mCurrentView.clearAnimation(); - } + void showOnly(int childIndex, boolean animate, boolean onLayout) { + if (mAdapter == null) return; - // load the new mCurrentView from our adapter - mPreviousView = mCurrentView; - mCurrentView = mAdapter.getView(childIndex, null, this); - if (mPreviousView != mCurrentView) { - addViewInLayout(mCurrentView, 0, makeLayoutParams(), true); - mCurrentView.bringToFront(); + for (int i = 0; i < mPreviousViews.size(); i++) { + View viewToRemove = mPreviousViews.get(i); + viewToRemove.clearAnimation(); + // applyTransformForChildAtIndex here just allows for any cleanup + // associated with this view that may need to be done by a subclass + applyTransformForChildAtIndex(viewToRemove, -1); + removeViewInLayout(viewToRemove); + } + mPreviousViews.clear(); + int newWindowStartUnbounded = childIndex - mActiveOffset; + int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1; + int newWindowStart = Math.max(0, newWindowStartUnbounded); + int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded); + + // This section clears out any items that are in our mActiveViews 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); + } } + } + // If the window has changed + 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 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]; + 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) + } 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); + } + } - - // Animate as necessary - if (mPreviousView != null && mPreviousView != mCurrentView) { - if (animate && mOutAnimation != null) { - mPreviousView.startAnimation(mOutAnimation); + // 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); + if (newView != null) { + mActiveViews[index] = newView; + addViewInLayout(newView, -1, createOrReuseLayoutParams(newView)); + applyTransformForChildAtIndex(newView, newRelativeIndex); + animateViewForTransition(-1, newRelativeIndex, newView); + } } - // This line results in the view becoming invisible *after* - // the above animation is complete, or, if there is no animation - // then it becomes invisble immediately - mPreviousView.setVisibility(View.GONE); + mActiveViews[index].bringToFront(); } - if (mCurrentView != null && animate && mInAnimation != null) { - mCurrentView.startAnimation(mInAnimation); + for (int i = 0; i < mViewsToBringToFront.size(); i++) { + View v = mViewsToBringToFront.get(i); + v.bringToFront(); } + mViewsToBringToFront.clear(); - mFirstTime = false; - if (!onLayout) { - requestLayout(); - invalidate(); - } else { - // If the Adapter tries to layout the current view when we get it using getView above - // the layout will end up being ignored since we are currently laying out, so - // we post a delayed requestLayout and invalidate - mMainQueue.post(new Runnable() { - @Override - public void run() { - mCurrentView.requestLayout(); - mCurrentView.invalidate(); - } - }); - } + mCurrentWindowStart = newWindowStart; + mCurrentWindowEnd = newWindowEnd; + mCurrentWindowStartUnbounded = newWindowStartUnbounded; + } + + mFirstTime = false; + if (!onLayout) { + requestLayout(); + invalidate(); + } else { + // If the Adapter tries to layout the current view when we get it using getView + // above the layout will end up being ignored since we are currently laying out, so + // we post a delayed requestLayout and invalidate + mMainQueue.post(new Runnable() { + @Override + public void run() { + requestLayout(); + invalidate(); + } + }); } } @@ -247,10 +460,11 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV int childRight = mPaddingLeft + child.getMeasuredWidth(); int childBottom = mPaddingTop + child.getMeasuredHeight(); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); - child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); + child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, + childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); } - mDataChanged = false; } @@ -261,8 +475,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - Log.v(TAG, "onMeasure"); - for (int i = 0; i < count; i++) { final View child = getChildAt(i); @@ -278,7 +490,6 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV child.measure(childWidthMeasureSpec, childheightMeasureSpec); } - setMeasuredDimension(widthSpecSize, heightSpecSize); } @@ -302,7 +513,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV * @see #getDisplayedChild() */ public View getCurrentView() { - return mCurrentView; + return getViewAtRelativeIndex(mActiveOffset); } /** @@ -402,22 +613,25 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV @Override public void setAdapter(Adapter adapter) { + if (mAdapter != null && mDataSetObserver != null) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + } + mAdapter = adapter; if (mAdapter != null) { - if (mDataSetObserver != null) { - mAdapter.unregisterDataSetObserver(mDataSetObserver); - } - mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); } + setFocusable(true); } /** - * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService - * through the specified intent. - * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to. + * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a + * RemoteViewsService through the specified intent. + * + * @param intent the intent used to identify the RemoteViewsService for the adapter to + * connect to. */ @android.view.RemotableViewMethod public void setRemoteViewsAdapter(Intent intent) { @@ -431,7 +645,7 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV @Override public View getSelectedView() { - return mCurrentView; + return getViewAtRelativeIndex(mActiveOffset); } /** @@ -452,4 +666,61 @@ public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteV setAdapter(mRemoteViewsAdapter); } } + + static class LayoutParams extends ViewGroup.LayoutParams { + int horizontalOffset; + int verticalOffset; + View mView; + + LayoutParams(View view) { + super(0, 0); + horizontalOffset = 0; + verticalOffset = 0; + mView = view; + } + + LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + horizontalOffset = 0; + verticalOffset = 0; + } + + void setHorizontalOffset(int newHorizontalOffset) { + horizontalOffset = newHorizontalOffset; + if (mView != null) { + mView.requestLayout(); + mView.invalidate(); + } + } + + private Rect parentRect = new Rect(); + void invalidateGlobalRegion(View v, Rect r) { + View p = v; + boolean firstPass = true; + parentRect.set(0, 0, 0, 0); + while (p.getParent() != null && p.getParent() instanceof View + && !parentRect.contains(r)) { + if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); + firstPass = false; + p = (View) p.getParent(); + parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(), + p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY()); + } + p.invalidate(r.left, r.top, r.right, r.bottom); + } + + private Rect invalidateRect = new Rect(); + // This is public so that PropertyAnimator can access it + public void setVerticalOffset(int newVerticalOffset) { + int offsetDelta = newVerticalOffset - verticalOffset; + verticalOffset = newVerticalOffset; + if (mView != null) { + mView.requestLayout(); + int top = Math.min(mView.getTop() + offsetDelta, mView.getTop()); + int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom()); + invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom); + invalidateGlobalRegion(mView, invalidateRect); + } + } + } } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index e27bb4fe2073..e4451800752b 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -46,27 +46,32 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class FrameLayout extends ViewGroup { - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") boolean mMeasureAllChildren = false; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") private Drawable mForeground; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingLeft = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingTop = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingRight = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingBottom = 0; private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "drawing") private int mForegroundGravity = Gravity.FILL; /** {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean mForegroundInPadding = true; boolean mForegroundBoundsChanged = false; diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 7254c3cfd359..53187bf01ebd 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -57,7 +57,7 @@ public class LinearLayout extends ViewGroup { * Whether the children of this layout are baseline aligned. Only applicable * if {@link #mOrientation} is horizontal. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private boolean mBaselineAligned = true; /** @@ -67,7 +67,7 @@ public class LinearLayout extends ViewGroup { * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned * with whether the children of this layout are baseline aligned. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private int mBaselineAlignedChildIndex = -1; /** @@ -75,12 +75,13 @@ public class LinearLayout extends ViewGroup { * We'll calculate the baseline of this layout as we measure vertically; for * horizontal linear layouts, the offset of 0 is appropriate. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mBaselineChildTop = 0; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mOrientation; - @ViewDebug.ExportedProperty(mapping = { + + @ViewDebug.ExportedProperty(category = "measurement", mapping = { @ViewDebug.IntToString(from = -1, to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), @@ -95,13 +96,14 @@ public class LinearLayout extends ViewGroup { @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") }) private int mGravity = Gravity.LEFT | Gravity.TOP; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "measurement") private int mTotalLength; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private float mWeightSum; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private boolean mUseLargestChild; private int[] mMaxAscent; @@ -1401,7 +1403,7 @@ public class LinearLayout extends ViewGroup { * 0 if the view should not be stretched. Otherwise the extra pixels * will be pro-rated among all views whose weight is greater than 0. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public float weight; /** @@ -1409,7 +1411,7 @@ public class LinearLayout extends ViewGroup { * * @see android.view.Gravity */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = -1, to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 5c34c2cd7b42..35e0603630cd 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -33,7 +33,7 @@ import android.view.View.OnTouchListener; /** * A ListPopupWindow anchors itself to a host view and displays a - * list of choices. When one is selected, the popup is dismissed. + * list of choices. * * <p>ListPopupWindow contains a number of tricky behaviors surrounding * positioning, scrolling parents to fit the dropdown, interacting diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 0bb41e584eed..0d0a1ba0eae6 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1186,7 +1186,7 @@ public class ListView extends AbsListView { * UNSPECIFIED/AT_MOST modes, false otherwise. * @hide */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") protected boolean recycleOnMeasure() { return true; } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index d404ce70a9aa..b562942aaae4 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -122,7 +122,7 @@ public class PopupWindow { private OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { public void onScrollChanged() { - View anchor = mAnchor.get(); + View anchor = mAnchor != null ? mAnchor.get() : null; if (anchor != null && mPopupView != null) { WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 71f0c2f9320c..0f52fc81e7bb 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -336,7 +336,7 @@ public class ProgressBar extends View { * * @return true if the progress bar is in indeterminate mode */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized boolean isIndeterminate() { return mIndeterminate; } @@ -609,7 +609,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getProgress() { return mIndeterminate ? 0 : mProgress; } @@ -626,7 +626,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getSecondaryProgress() { return mIndeterminate ? 0 : mSecondaryProgress; } @@ -640,7 +640,7 @@ public class ProgressBar extends View { * @see #getProgress() * @see #getSecondaryProgress() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getMax() { return mMax; } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 1aa1df30afa0..64cda49ade65 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -1011,7 +1011,7 @@ public class RelativeLayout extends ViewGroup { * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical */ public static class LayoutParams extends ViewGroup.MarginLayoutParams { - @ViewDebug.ExportedProperty(resolveId = true, indexMapping = { + @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { @ViewDebug.IntToString(from = ABOVE, to = "above"), @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"), @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"), @@ -1040,7 +1040,7 @@ public class RelativeLayout extends ViewGroup { * When true, uses the parent as the anchor if the anchor doesn't exist or if * the anchor's visibility is GONE. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public boolean alignWithParent; public LayoutParams(Context c, AttributeSet attrs) { diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 52635e85fa48..ebf5d6ee9977 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -433,6 +433,7 @@ public class RemoteViewsAdapter extends BaseAdapter { int cacheIndex = getCacheIndex(position); FrameLayout flipper = mViewCache[cacheIndex].flipper; flipper.setVisibility(View.VISIBLE); + flipper.setAlpha(1.0f); if (indexInfo == null) { // hide the item view and show the loading view diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java index c9c217a6f2df..aee411ee4076 100644 --- a/core/java/android/widget/ResourceCursorAdapter.java +++ b/core/java/android/widget/ResourceCursorAdapter.java @@ -36,9 +36,8 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { /** * Constructor. - * - * @param context The context where the ListView associated with this - * SimpleListItemFactory is running + * + * @param context The context where the ListView associated with this adapter is running * @param layout resource identifier of a layout file that defines the views * for this list item. Unless you override them later, this will * define both the item views and the drop down views. @@ -51,9 +50,8 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { /** * Constructor. - * - * @param context The context where the ListView associated with this - * SimpleListItemFactory is running + * + * @param context The context where the ListView associated with this adapter is running * @param layout resource identifier of a layout file that defines the views * for this list item. Unless you override them later, this will * define both the item views and the drop down views. @@ -69,6 +67,22 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { } /** + * Constructor. + * + * @param context The context where the ListView associated with this adapter is running + * @param layout resource identifier of a layout file that defines the views + * for this list item. Unless you override them later, this will + * define both the item views and the drop down views. + * @param c The cursor from which to get the data. + * @param flags flags used to determine the behavior of the adapter + */ + public ResourceCursorAdapter(Context context, int layout, Cursor c, int flags) { + super(context, c, flags); + mLayout = mDropDownLayout = layout; + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + /** * Inflates view(s) from the specified XML file. * * @see android.widget.CursorAdapter#newView(android.content.Context, diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java new file mode 100644 index 000000000000..4cd44d99a777 --- /dev/null +++ b/core/java/android/widget/StackView.java @@ -0,0 +1,487 @@ +/* + * 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.widget; + +import java.util.WeakHashMap; + +import android.animation.PropertyAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.RemoteViews.RemoteView; + +@RemoteView +/** + * A view that displays its children in a stack and allows users to discretely swipe + * through the children. + */ +public class StackView extends AdapterViewAnimator { + private final String TAG = "StackView"; + + /** + * Default animation parameters + */ + private final int DEFAULT_ANIMATION_DURATION = 400; + private final int MINIMUM_ANIMATION_DURATION = 50; + + /** + * These specify the different gesture states + */ + private final int GESTURE_NONE = 0; + private final int GESTURE_SLIDE_UP = 1; + private final int GESTURE_SLIDE_DOWN = 2; + + /** + * Specifies how far you need to swipe (up or down) before it + * will be consider a completed gesture when you lift your finger + */ + private final float SWIPE_THRESHOLD_RATIO = 0.35f; + private final float SLIDE_UP_RATIO = 0.7f; + + private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); + private final WeakHashMap<View, Integer> + mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>(); + + /** + * Sentinel value for no current active pointer. + * Used by {@link #mActivePointerId}. + */ + private static final int INVALID_POINTER = -1; + + /** + * These variables are all related to the current state of touch interaction + * with the stack + */ + private boolean mGestureComplete = false; + private float mInitialY; + private float mInitialX; + private int mActivePointerId; + private int mYOffset = 0; + private int mYVelocity = 0; + private int mSwipeGestureType = GESTURE_NONE; + private int mViewHeight; + private int mSwipeThreshold; + private int mTouchSlop; + private int mMaximumVelocity; + private VelocityTracker mVelocityTracker; + + private boolean mFirstLayoutHappened = false; + + // TODO: temp hack to get this thing started + int mIndex = 5; + + public StackView(Context context) { + super(context); + initStackView(); + } + + public StackView(Context context, AttributeSet attrs) { + super(context, attrs); + initStackView(); + } + + private void initStackView() { + configureViewAnimator(4, 2); + setStaticTransformationsEnabled(true); + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop();// + 5; + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mActivePointerId = INVALID_POINTER; + } + + /** + * Animate the views between different relative indexes within the {@link AdapterViewAnimator} + */ + void animateViewForTransition(int fromIndex, int toIndex, View view) { + if (fromIndex == -1 && toIndex == 0) { + // Fade item in + if (view.getAlpha() == 1) { + view.setAlpha(0); + } + PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, + view, "alpha", view.getAlpha(), 1.0f); + fadeIn.start(); + } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { + // Slide item in + view.setVisibility(VISIBLE); + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + + int largestDuration = (int) Math.round( + (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); + int duration = largestDuration; + if (mYVelocity != 0) { + duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity); + } + + duration = Math.min(duration, largestDuration); + duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); + + PropertyAnimator slideDown = new PropertyAnimator(duration, lp, + "verticalOffset", lp.verticalOffset, 0); + slideDown.start(); + + PropertyAnimator fadeIn = new PropertyAnimator(duration, view, + "alpha", view.getAlpha(), 1.0f); + fadeIn.start(); + } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { + // Slide item out + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + + int largestDuration = (int) Math.round( + (1 - (lp.verticalOffset*1.0f/-mViewHeight))*DEFAULT_ANIMATION_DURATION); + int duration = largestDuration; + if (mYVelocity != 0) { + duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); + } + + duration = Math.min(duration, largestDuration); + duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); + + PropertyAnimator slideUp = new PropertyAnimator(duration, lp, + "verticalOffset", lp.verticalOffset, -mViewHeight); + slideUp.start(); + + PropertyAnimator fadeOut = new PropertyAnimator(duration, view, + "alpha", view.getAlpha(), 0.0f); + fadeOut.start(); + } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { + // Make sure this view that is "waiting in the wings" is invisible + view.setAlpha(0.0f); + } else if (toIndex == -1) { + // Fade item out + PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, + view, "alpha", view.getAlpha(), 0); + fadeOut.start(); + } + } + + /** + * Apply any necessary tranforms for the child that is being added. + */ + void applyTransformForChildAtIndex(View child, int relativeIndex) { + float rotation; + + if (!mRotations.containsKey(child)) { + rotation = (float) (Math.random()*26 - 13); + mRotations.put(child, rotation); + } else { + rotation = mRotations.get(child); + } + + // Child has been removed + if (relativeIndex == -1) { + if (mRotations.containsKey(child)) { + mRotations.remove(child); + } + if (mChildrenToApplyTransformsTo.containsKey(child)) { + mChildrenToApplyTransformsTo.remove(child); + } + } + + // if this view is already in the layout, we need to + // wait until layout has finished in order to set the + // pivot point of the rotation (requiring getMeasuredWidth/Height()) + mChildrenToApplyTransformsTo.put(child, relativeIndex); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (!mChildrenToApplyTransformsTo.isEmpty()) { + for (View child: mChildrenToApplyTransformsTo.keySet()) { + if (mRotations.containsKey(child)) { + child.setPivotX(child.getMeasuredWidth()/2); + child.setPivotY(child.getMeasuredHeight()/2); + child.setRotation(mRotations.get(child)); + } + } + mChildrenToApplyTransformsTo.clear(); + } + + if (!mFirstLayoutHappened) { + mViewHeight = (int) Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); + mSwipeThreshold = (int) Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); + + // TODO: Right now this walks all the way up the view hierarchy and disables + // ClipChildren and ClipToPadding. We're probably going to want to reset + // these flags as well. + setClipChildren(false); + ViewGroup view = this; + while (view.getParent() != null && view.getParent() instanceof ViewGroup) { + view = (ViewGroup) view.getParent(); + view.setClipChildren(false); + view.setClipToPadding(false); + } + + mFirstLayoutHappened = true; + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + switch(action & MotionEvent.ACTION_MASK) { + + case MotionEvent.ACTION_DOWN: { + if (mActivePointerId == INVALID_POINTER) { + mInitialX = ev.getX(); + mInitialY = ev.getY(); + mActivePointerId = ev.getPointerId(0); + } + break; + } + case MotionEvent.ACTION_MOVE: { + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER) { + // no data for our primary pointer, this shouldn't happen, log it + Log.d(TAG, "Error: No data for our primary pointer."); + return false; + } + + float newY = ev.getY(pointerIndex); + float deltaY = newY - mInitialY; + + if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { + mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; + mGestureComplete = false; + cancelLongPress(); + requestDisallowInterceptTouchEvent(true); + } + break; + } + case MotionEvent.ACTION_POINTER_UP: { + onSecondaryPointerUp(ev); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + mActivePointerId = INVALID_POINTER; + mSwipeGestureType = GESTURE_NONE; + mGestureComplete = true; + } + } + + return mSwipeGestureType != GESTURE_NONE; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER) { + // no data for our primary pointer, this shouldn't happen, log it + Log.d(TAG, "Error: No data for our primary pointer."); + return false; + } + + float newY = ev.getY(pointerIndex); + float deltaY = newY - mInitialY; + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { + if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { + mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; + mGestureComplete = false; + cancelLongPress(); + requestDisallowInterceptTouchEvent(true); + } + + if (!mGestureComplete) { + if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { + View v = getViewAtRelativeIndex(mNumActiveViews - 1); + if (v != null) { + // This view is present but hidden, make sure it's visible + // if they pull down + v.setVisibility(VISIBLE); + + float r = (deltaY-mTouchSlop)*1.0f / (mSwipeThreshold); + mYOffset = Math.min(-mViewHeight + (int) Math.round( + r*mSwipeThreshold) - mTouchSlop, 0); + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + lp.setVerticalOffset(mYOffset); + + float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight)); + alpha = Math.min(1.0f, alpha); + v.setAlpha(alpha); + } + return true; + } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { + View v = getViewAtRelativeIndex(mNumActiveViews - 2); + + if (v != null) { + float r = -(deltaY*1.0f + mTouchSlop) / (mSwipeThreshold); + mYOffset = Math.min((int) Math.round(r*-mSwipeThreshold), 0); + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + lp.setVerticalOffset(mYOffset); + + float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight)); + alpha = Math.min(1.0f, alpha); + v.setAlpha(alpha); + } + return true; + } + } + break; + } + case MotionEvent.ACTION_UP: { + handlePointerUp(ev); + break; + } + case MotionEvent.ACTION_POINTER_UP: { + onSecondaryPointerUp(ev); + break; + } + case MotionEvent.ACTION_CANCEL: { + mActivePointerId = INVALID_POINTER; + mGestureComplete = true; + mSwipeGestureType = GESTURE_NONE; + mYOffset = 0; + break; + } + } + return true; + } + + private final Rect touchRect = new Rect(); + private void onSecondaryPointerUp(MotionEvent ev) { + final int activePointerIndex = ev.getActionIndex(); + final int pointerId = ev.getPointerId(activePointerIndex); + if (pointerId == mActivePointerId) { + + int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1 + : mNumActiveViews - 2; + + View v = getViewAtRelativeIndex(activeViewIndex); + if (v == null) return; + + // Our primary pointer has gone up -- let's see if we can find + // another pointer on the view. If so, then we should replace + // our primary pointer with this new pointer and adjust things + // so that the view doesn't jump + for (int index = 0; index < ev.getPointerCount(); index++) { + if (index != activePointerIndex) { + + float x = ev.getX(index); + float y = ev.getY(index); + + touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + if (touchRect.contains((int) Math.round(x), (int) Math.round(y))) { + float oldX = ev.getX(activePointerIndex); + float oldY = ev.getY(activePointerIndex); + + // adjust our frame of reference to avoid a jump + mInitialY += (y - oldY); + mInitialX += (x - oldX); + + mActivePointerId = ev.getPointerId(index); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + // ok, we're good, we found a new pointer which is touching the active view + return; + } + } + } + // if we made it this far, it means we didn't find a satisfactory new pointer :(, + // so end the + handlePointerUp(ev); + } + } + + private void handlePointerUp(MotionEvent ev) { + int pointerIndex = ev.findPointerIndex(mActivePointerId); + float newY = ev.getY(pointerIndex); + int deltaY = (int) (newY - mInitialY); + + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + + if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN && + !mGestureComplete) { + // Swipe threshold exceeded, swipe down + showNext(); + } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP && + !mGestureComplete) { + // Swipe threshold exceeded, swipe up + showPrevious(); + } else if (mSwipeGestureType == GESTURE_SLIDE_UP && !mGestureComplete) { + // Didn't swipe up far enough, snap back down + View v = getViewAtRelativeIndex(mNumActiveViews - 2); + if (v != null) { + // Compute the animation duration based on how far they pulled it up + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + int duration = (int) Math.round( + lp.verticalOffset*1.0f/-mViewHeight*DEFAULT_ANIMATION_DURATION); + duration = Math.max(MINIMUM_ANIMATION_DURATION, duration); + + // Animate back down + PropertyAnimator slideDown = new PropertyAnimator(duration, lp, + "verticalOffset", lp.verticalOffset, 0); + slideDown.start(); + PropertyAnimator fadeIn = new PropertyAnimator(duration, v, + "alpha",v.getAlpha(), 1.0f); + fadeIn.start(); + } + } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN && !mGestureComplete) { + // Didn't swipe down far enough, snap back up + View v = getViewAtRelativeIndex(mNumActiveViews - 1); + if (v != null) { + // Compute the animation duration based on how far they pulled it down + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + int duration = (int) Math.round( + (1 - lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); + duration = Math.max(MINIMUM_ANIMATION_DURATION, duration); + + // Animate back up + PropertyAnimator slideUp = new PropertyAnimator(duration, lp, + "verticalOffset", lp.verticalOffset, -mViewHeight); + slideUp.start(); + PropertyAnimator fadeOut = new PropertyAnimator(duration, v, + "alpha",v.getAlpha(), 0.0f); + fadeOut.start(); + } + } + + mActivePointerId = INVALID_POINTER; + mGestureComplete = true; + mSwipeGestureType = GESTURE_NONE; + mYOffset = 0; + } + + @Override + public void onRemoteAdapterConnected() { + super.onRemoteAdapterConnected(); + setDisplayedChild(mIndex); + } +} diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index 48d12df8eb16..b612004c5b77 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -387,13 +387,13 @@ public class TableRow extends LinearLayout { /** * <p>The column index of the cell represented by the widget.</p> */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int column; /** * <p>The number of columns the widgets spans over.</p> */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int span; private static final int LOCATION = 0; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 24f14dc8bdc7..b413aee8bbd7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -21,7 +21,9 @@ import com.android.internal.widget.EditableInputConnection; import org.xmlpull.v1.XmlPullParserException; +import android.content.ClippedData; import android.content.Context; +import android.content.ClipboardManager; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -34,6 +36,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -42,7 +45,6 @@ import android.os.Parcelable; import android.os.ResultReceiver; import android.os.SystemClock; import android.text.BoringLayout; -import android.text.ClipboardManager; import android.text.DynamicLayout; import android.text.Editable; import android.text.GetChars; @@ -5816,7 +5818,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionStart}. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "text") public int getSelectionStart() { return Selection.getSelectionStart(getText()); } @@ -5824,7 +5826,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionEnd}. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "text") public int getSelectionEnd() { return Selection.getSelectionEnd(getText()); } @@ -7061,7 +7063,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getSelectionStart() >= 0 && getSelectionEnd() >= 0 && ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). - hasText()); + hasPrimaryClip()); } /** @@ -7270,7 +7272,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int min = Math.max(0, Math.min(selStart, selEnd)); int max = Math.max(0, Math.max(selStart, selEnd)); - ClipboardManager clip = (ClipboardManager)getContext() + ClipboardManager clipboard = (ClipboardManager)getContext() .getSystemService(Context.CLIPBOARD_SERVICE); switch (id) { @@ -7278,8 +7280,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); - if (urls.length == 1) { - clip.setText(urls[0].getURL()); + if (urls.length >= 1) { + ClippedData clip = null; + for (int i=0; i<urls.length; i++) { + Uri uri = Uri.parse(urls[0].getURL()); + ClippedData.Item item = new ClippedData.Item(uri); + if (clip == null) { + clip = new ClippedData(null, null, item); + } else { + clip.addItem(item); + } + } + clipboard.setPrimaryClip(clip); } return true; @@ -7490,8 +7502,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; } - ClipboardManager clip = (ClipboardManager) getContext(). - getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = (ClipboardManager) getContext(). + getSystemService(Context.CLIPBOARD_SERVICE); int min = 0; int max = mText.length(); @@ -7506,23 +7518,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (item.getItemId()) { case ID_PASTE: - CharSequence paste = clip.getText(); - - if (paste != null) { - Selection.setSelection((Spannable) mText, max); - ((Editable) mText).replace(min, max, paste); + ClippedData clip = clipboard.getPrimaryClip(); + if (clip != null) { + boolean didfirst = false; + for (int i=0; i<clip.getItemCount(); i++) { + CharSequence paste = clip.getItem(i).coerceToText(getContext()); + if (paste != null) { + if (!didfirst) { + Selection.setSelection((Spannable) mText, max); + ((Editable) mText).replace(min, max, paste); + } else { + ((Editable) mText).insert(getSelectionEnd(), "\n"); + ((Editable) mText).insert(getSelectionEnd(), paste); + } + } + } finishSelectionActionMode(); } + return true; case ID_CUT: - clip.setText(mTransformed.subSequence(min, max)); + clipboard.setPrimaryClip(new ClippedData(null, null, + new ClippedData.Item(mTransformed.subSequence(min, max)))); ((Editable) mText).delete(min, max); finishSelectionActionMode(); return true; case ID_COPY: - clip.setText(mTransformed.subSequence(min, max)); + clipboard.setPrimaryClip(new ClippedData(null, null, + new ClippedData.Item(mTransformed.subSequence(min, max)))); finishSelectionActionMode(); return true; } @@ -8037,7 +8062,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "text") private CharSequence mText; private CharSequence mTransformed; private BufferType mBufferType = BufferType.NORMAL; diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 281f32c83224..dd2ad6c764dd 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -24,8 +24,10 @@ import com.android.internal.widget.ActionBarView; import android.app.ActionBar; import android.app.Activity; +import android.app.Dialog; import android.app.Fragment; import android.app.FragmentTransaction; +import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.view.ActionMode; @@ -54,7 +56,9 @@ public class ActionBarImpl extends ActionBar { private static final int TAB_SWITCH_SHOW_HIDE = 0; private static final int TAB_SWITCH_ADD_REMOVE = 1; + private Context mContext; private Activity mActivity; + private Dialog mDialog; private ViewAnimator mAnimatorView; private ActionBarView mActionView; @@ -88,8 +92,17 @@ public class ActionBarImpl extends ActionBar { }; public ActionBarImpl(Activity activity) { - final View decor = activity.getWindow().getDecorView(); mActivity = activity; + init(activity.getWindow().getDecorView()); + } + + public ActionBarImpl(Dialog dialog) { + mDialog = dialog; + init(dialog.getWindow().getDecorView()); + } + + private void init(View decor) { + mContext = decor.getContext(); mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); mUpperContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); @@ -109,23 +122,23 @@ public class ActionBarImpl extends ActionBar { @Override public void setStandardNavigationMode(int titleResId, int subtitleResId) { - setStandardNavigationMode(mActivity.getString(titleResId), - mActivity.getString(subtitleResId)); + setStandardNavigationMode(mContext.getString(titleResId), + mContext.getString(subtitleResId)); } @Override public void setStandardNavigationMode(int titleResId) { - setStandardNavigationMode(mActivity.getString(titleResId)); + setStandardNavigationMode(mContext.getString(titleResId)); } @Override public void setTitle(int resId) { - setTitle(mActivity.getString(resId)); + setTitle(mContext.getString(resId)); } @Override public void setSubtitle(int resId) { - setSubtitle(mActivity.getString(resId)); + setSubtitle(mContext.getString(resId)); } public void setCustomNavigationMode(View view) { @@ -345,6 +358,10 @@ public class ActionBarImpl extends ActionBar { @Override public void setTabNavigationMode() { + if (mActivity == null) { + throw new IllegalStateException( + "Tab navigation mode cannot be used outside of an Activity"); + } mActionView.setNavigationMode(NAVIGATION_MODE_TABS); } @@ -380,6 +397,27 @@ public class ActionBarImpl extends ActionBar { trans.commit(); } + @Override + public int getHeight() { + return mActionView.getHeight(); + } + + @Override + public void show() { + // TODO animate! + mAnimatorView.setVisibility(View.VISIBLE); + } + + @Override + public void hide() { + // TODO animate! + mAnimatorView.setVisibility(View.GONE); + } + + public boolean isShowing() { + return mAnimatorView.getVisibility() == View.VISIBLE; + } + /** * @hide */ @@ -396,7 +434,7 @@ public class ActionBarImpl extends ActionBar { @Override public MenuInflater getMenuInflater() { - return new MenuInflater(mActivity); + return new MenuInflater(mContext); } @Override @@ -485,7 +523,7 @@ public class ActionBarImpl extends ActionBar { return true; } - new MenuPopupHelper(mActivity, subMenu).show(); + new MenuPopupHelper(mContext, subMenu).show(); return true; } @@ -493,6 +531,8 @@ public class ActionBarImpl extends ActionBar { } public void onMenuModeChange(MenuBuilder menu) { + invalidate(); + mUpperContextView.showOverflowMenu(); } } diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 89649a98efa8..5d1f632797a7 100755 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -19,6 +19,7 @@ package com.android.internal.app; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.content.pm.PackageInfoLite; +import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, @@ -28,4 +29,5 @@ interface IMediaContainerService { in ParcelFileDescriptor outStream); PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags); boolean checkFreeStorage(boolean external, in Uri fileUri); + ObbInfo getObbInfo(String filename); } diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java new file mode 100644 index 000000000000..ce5959d1ea91 --- /dev/null +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.ImageView; + +public class PlatLogoActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ImageView content = new ImageView(this); + content.setImageResource(com.android.internal.R.drawable.platlogo); + content.setScaleType(ImageView.ScaleType.FIT_CENTER); + + setContentView(content); + } +} diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 59600dcdc455..5767832b58d0 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -24,6 +24,7 @@ import android.os.IBinder; import android.os.Process; import android.os.SystemProperties; import android.util.Config; +import android.util.Finalizers; import android.util.Log; import android.util.Slog; @@ -141,6 +142,12 @@ public class RuntimeInit { Debug.enableEmulatorTraceOutput(); } + /** + * Initialize the thread used to reclaim resources without + * going through finalizers. + */ + Finalizers.init(); + initialized = true; } @@ -331,9 +338,6 @@ public class RuntimeInit { } } - /** Counter used to prevent reentrancy in {@link #reportException}. */ - private static final AtomicInteger sInReportException = new AtomicInteger(); - /** * Set the object identifying this application/process, for reporting VM * errors. diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java index e6d6ba04d807..ab80c588f8fd 100644 --- a/core/java/com/android/internal/view/StandaloneActionMode.java +++ b/core/java/com/android/internal/view/StandaloneActionMode.java @@ -135,5 +135,7 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call } public void onMenuModeChange(MenuBuilder menu) { + invalidate(); + mContextView.showOverflowMenu(); } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java index e2815368ecba..cb5f17980d99 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -20,10 +20,12 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.util.AttributeSet; +import android.view.Gravity; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.LinearLayout; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -39,6 +41,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo private int mMaxItems; private boolean mReserveOverflow; private OverflowMenuButton mOverflowButton; + private WeakReference<MenuPopupHelper> mOverflowPopup; public ActionMenuView(Context context) { this(context, null); @@ -80,6 +83,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo if (p instanceof LayoutParams) { LayoutParams lp = (LayoutParams) p; return lp.leftMargin == mItemMargin && lp.rightMargin == mItemMargin && + lp.gravity == Gravity.CENTER_VERTICAL && lp.width == LayoutParams.WRAP_CONTENT && lp.height == LayoutParams.WRAP_CONTENT; } return false; @@ -91,6 +95,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo LayoutParams.WRAP_CONTENT); params.leftMargin = mItemMargin; params.rightMargin = mItemMargin; + params.gravity = Gravity.CENTER_VERTICAL; return params; } @@ -143,8 +148,32 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo public boolean showOverflowMenu() { if (mOverflowButton != null) { - MenuPopupHelper popup = new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true); - popup.show(); + final MenuPopupHelper popup = + new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true); + // Post this for later; we might still need a layout for the anchor to be right. + post(new Runnable() { + public void run() { + popup.show(); + } + }); + mOverflowPopup = new WeakReference<MenuPopupHelper>(popup); + return true; + } + return false; + } + + public boolean isOverflowMenuShowing() { + MenuPopupHelper popup = mOverflowPopup != null ? mOverflowPopup.get() : null; + if (popup != null) { + return popup.isShowing(); + } + return false; + } + + public boolean hideOverflowMenu() { + MenuPopupHelper popup = mOverflowPopup != null ? mOverflowPopup.get() : null; + if (popup != null) { + popup.dismiss(); return true; } return false; @@ -162,7 +191,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo final Resources res = context.getResources(); setClickable(true); setFocusable(true); - // TODO setTitle() to a localized string for accessibility + setContentDescription(res.getString(com.android.internal.R.string.more_item_label)); setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more)); setVisibility(VISIBLE); setEnabled(true); @@ -174,7 +203,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return true; } - showOverflowMenu(); + // Change to overflow mode + mMenu.getCallback().onMenuModeChange(mMenu); return true; } } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index ce0ec045dc0b..f52c93cf3537 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -92,7 +92,9 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On } public void dismiss() { - mPopup.dismiss(); + if (isShowing()) { + mPopup.dismiss(); + } mPopup = null; } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 5518b3edc317..6a476d00eb09 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -163,6 +163,27 @@ public class ActionBarContextView extends ViewGroup { mMenuView = null; } + public boolean showOverflowMenu() { + if (mMenuView != null) { + return mMenuView.showOverflowMenu(); + } + return false; + } + + public boolean hideOverflowMenu() { + if (mMenuView != null) { + return mMenuView.hideOverflowMenu(); + } + return false; + } + + public boolean isOverflowMenuShowing() { + if (mMenuView != null) { + return mMenuView.isOverflowMenuShowing(); + } + return false; + } + @Override protected LayoutParams generateDefaultLayoutParams() { // Used by custom views if they don't supply layout params. Everything else diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index d703a2f7f730..73d3c9574a3c 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -118,7 +118,6 @@ public class ActionBarView extends ViewGroup { super(context, attrs); final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - mContentHeight = (int) (CONTENT_HEIGHT_DIP * metrics.density + 0.5f); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar); @@ -157,14 +156,18 @@ public class ActionBarView extends ViewGroup { LayoutInflater inflater = LayoutInflater.from(context); mCustomNavView = (View) inflater.inflate(customNavId, null); mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM; + addView(mCustomNavView); } + final int padding = a.getDimensionPixelSize(R.styleable.ActionBar_padding, + (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f)); + setPadding(padding, padding, padding, padding); + mContentHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, + (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f)) - padding * 2; + a.recycle(); // TODO: Set this in the theme - int padding = (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f); - setPadding(padding, padding, padding, padding); - mSpacing = (int) (CONTENT_SPACING_DIP * metrics.density + 0.5f); mActionSpacing = (int) (CONTENT_ACTION_SPACING_DIP * metrics.density + 0.5f); @@ -209,6 +212,20 @@ public class ActionBarView extends ViewGroup { return false; } + public boolean hideOverflowMenu() { + if (mMenuView != null) { + return mMenuView.hideOverflowMenu(); + } + return false; + } + + public boolean isOverflowMenuShowing() { + if (mMenuView != null) { + return mMenuView.isOverflowMenuShowing(); + } + return false; + } + public boolean isOverflowReserved() { return mMenuView != null && mMenuView.isOverflowReserved(); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 77c77f98eefa..860b5b743b7f 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -19,6 +19,10 @@ ifneq ($(USE_CUSTOM_RUNTIME_HEAP_MAX),) LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX) endif +ifeq ($(USE_OPENGL_RENDERER),true) + LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER +endif + LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_SRC_FILES:= \ @@ -47,7 +51,6 @@ LOCAL_SRC_FILES:= \ android_view_InputChannel.cpp \ android_view_InputQueue.cpp \ android_view_KeyEvent.cpp \ - android_view_HardwareRenderer.cpp \ android_view_GLES20Canvas.cpp \ android_view_MotionEvent.cpp \ android_text_AndroidCharacter.cpp \ @@ -136,7 +139,8 @@ LOCAL_SRC_FILES:= \ android_backup_BackupDataOutput.cpp \ android_backup_FileBackupHelperBase.cpp \ android_backup_BackupHelperDispatcher.cpp \ - android_content_res_ObbScanner.cpp + android_content_res_ObbScanner.cpp \ + android_content_res_Configuration.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ @@ -170,7 +174,6 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libnetutils \ libui \ - libhwui \ libgui \ libsurfaceflinger_client \ libcamera_client \ @@ -193,6 +196,10 @@ LOCAL_SHARED_LIBRARIES := \ libwpa_client \ libjpeg +ifeq ($(USE_OPENGL_RENDERER),true) + LOCAL_SHARED_LIBRARIES += libhwui +endif + ifeq ($(BOARD_HAVE_BLUETOOTH),true) LOCAL_C_INCLUDES += \ external/dbus \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1e6d219bcb10..2dd17bbf5954 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -115,7 +115,6 @@ extern int register_android_graphics_PixelFormat(JNIEnv* env); extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env); extern int register_android_view_Display(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); -extern int register_android_view_HardwareRenderer(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); extern int register_android_view_ViewRoot(JNIEnv* env); extern int register_android_database_CursorWindow(JNIEnv* env); @@ -168,6 +167,7 @@ extern int register_android_view_InputQueue(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_content_res_ObbScanner(JNIEnv* env); +extern int register_android_content_res_Configuration(JNIEnv* env); static AndroidRuntime* gCurRuntime = NULL; @@ -1244,7 +1244,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_PixelFormat), REG_JNI(register_android_graphics_Graphics), REG_JNI(register_android_view_GLES20Canvas), - REG_JNI(register_android_view_HardwareRenderer), REG_JNI(register_android_view_Surface), REG_JNI(register_android_view_ViewRoot), REG_JNI(register_com_google_android_gles_jni_EGLImpl), @@ -1333,6 +1332,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_MotionEvent), REG_JNI(register_android_content_res_ObbScanner), + REG_JNI(register_android_content_res_Configuration), }; /* diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 558f5ffa478e..bf150a90ad6e 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -20,7 +20,6 @@ #include "SkCanvas.h" #include "SkDevice.h" -#include "SkGLCanvas.h" #include "SkGraphics.h" #include "SkImageRef_GlobalPool.h" #include "SkPorterDuff.h" @@ -67,13 +66,8 @@ public: return bitmap ? new SkCanvas(*bitmap) : new SkCanvas; } - static SkCanvas* initGL(JNIEnv* env, jobject) { - return new SkGLCanvas; - } - static void freeCaches(JNIEnv* env, jobject) { // these are called in no particular order - SkGLCanvas::DeleteAllTextures(); SkImageRef_GlobalPool::SetRAMUsed(0); SkGraphics::SetFontCacheUsed(0); } @@ -110,11 +104,6 @@ public: return canvas->getDevice()->accessBitmap(false).height(); } - static void setViewport(JNIEnv* env, jobject, SkCanvas* canvas, - int width, int height) { - canvas->setViewport(width, height); - } - static void setBitmap(JNIEnv* env, jobject, SkCanvas* canvas, SkBitmap* bitmap) { canvas->setBitmapDevice(*bitmap); @@ -880,12 +869,10 @@ public: static JNINativeMethod gCanvasMethods[] = { {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer}, {"initRaster","(I)I", (void*) SkCanvasGlue::initRaster}, - {"initGL","()I", (void*) SkCanvasGlue::initGL}, {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque}, {"getWidth","()I", (void*) SkCanvasGlue::getWidth}, {"getHeight","()I", (void*) SkCanvasGlue::getHeight}, {"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap}, - {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport}, {"save","()I", (void*) SkCanvasGlue::saveAll}, {"save","(I)I", (void*) SkCanvasGlue::save}, {"native_saveLayer","(ILandroid/graphics/RectF;II)I", diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp index 848234f8129a..f3be8b0b9522 100644 --- a/core/jni/android/graphics/ColorFilter.cpp +++ b/core/jni/android/graphics/ColorFilter.cpp @@ -36,40 +36,25 @@ public: obj->safeUnref(); } - static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, - SkPorterDuff::Mode mode) { - return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode)); - } - static SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, SkPorterDuff::Mode mode) { +#ifdef USE_OPENGL_RENDERER return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode)); +#else + return NULL; +#endif } - static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) { - return SkColorFilter::CreateLightingFilter(mul, add); - } - static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) { +#ifdef USE_OPENGL_RENDERER return new SkiaLightingFilter(mul, add); - } - - static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { - AutoJavaFloatArray autoArray(env, jarray, 20); - const float* src = autoArray.ptr(); - -#ifdef SK_SCALAR_IS_FIXED - SkFixed array[20]; - for (int i = 0; i < 20; i++) { - array[i] = SkFloatToScalar(src[i]); - } - return new SkColorMatrixFilter(array); #else - return new SkColorMatrixFilter(src); + return NULL; #endif } static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { +#ifdef USE_OPENGL_RENDERER AutoJavaFloatArray autoArray(env, jarray, 20); const float* src = autoArray.ptr(); @@ -86,6 +71,33 @@ public: colorVector[3] = src[19]; return new SkiaColorMatrixFilter(colorMatrix, colorVector); +#else + return NULL; +#endif + } + + static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, + SkPorterDuff::Mode mode) { + return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode)); + } + + static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) { + return SkColorFilter::CreateLightingFilter(mul, add); + } + + static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { + AutoJavaFloatArray autoArray(env, jarray, 20); + const float* src = autoArray.ptr(); + +#ifdef SK_SCALAR_IS_FIXED + SkFixed array[20]; + for (int i = 0; i < 20; i++) { + array[i] = SkFloatToScalar(src[i]); + } + return new SkColorMatrixFilter(array); +#else + return new SkColorMatrixFilter(src); +#endif } }; diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp index 34b4ab50d124..cb1c333762d9 100644 --- a/core/jni/android/graphics/Shader.cpp +++ b/core/jni/android/graphics/Shader.cpp @@ -71,7 +71,9 @@ static void Shader_setLocalMatrix(JNIEnv* env, jobject o, SkShader* shader, Skia else { shader->setLocalMatrix(*matrix); } +#ifdef USE_OPENGL_RENDERER skiaShader->setMatrix(const_cast<SkMatrix*>(matrix)); +#endif } } @@ -90,10 +92,14 @@ static SkShader* BitmapShader_constructor(JNIEnv* env, jobject o, const SkBitmap static SkiaShader* BitmapShader_postConstructor(JNIEnv* env, jobject o, SkShader* shader, SkBitmap* bitmap, int tileModeX, int tileModeY) { +#ifdef USE_OPENGL_RENDERER SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader, static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY), NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0); return skiaShader; +#else + return NULL; +#endif } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -134,7 +140,7 @@ static SkShader* LinearGradient_create1(JNIEnv* env, jobject o, static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader, float x0, float y0, float x1, float y1, jintArray colorArray, jfloatArray posArray, int tileMode) { - +#ifdef USE_OPENGL_RENDERER size_t count = env->GetArrayLength(colorArray); const jint* colorValues = env->GetIntArrayElements(colorArray, NULL); @@ -143,7 +149,9 @@ static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* storedBounds[2] = x1; storedBounds[3] = y1; jfloat* storedPositions = new jfloat[count]; uint32_t* storedColors = new uint32_t[count]; - memcpy(storedColors, colorValues, count); + for (size_t i = 0; i < count; i++) { + storedColors[i] = static_cast<uint32_t>(colorValues[i]); + } if (posArray) { AutoJavaFloatArray autoPos(env, posArray, count); @@ -162,10 +170,14 @@ static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT); return skiaShader; +#else + return NULL; +#endif } static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader, float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) { +#ifdef USE_OPENGL_RENDERER float* storedBounds = new float[4]; storedBounds[0] = x0; storedBounds[1] = y0; storedBounds[2] = x1; storedBounds[3] = y1; @@ -175,14 +187,17 @@ static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* storedPositions[1] = 1.0f; uint32_t* storedColors = new uint32_t[2]; - storedColors[0] = color0; - storedColors[1] = color1; + storedColors[0] = static_cast<uint32_t>(color0); + storedColors[1] = static_cast<uint32_t>(color1); SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors, storedPositions, 2, shader, static_cast<SkShader::TileMode>(tileMode), NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0); return skiaShader; +#else + return NULL; +#endif } static SkShader* LinearGradient_create2(JNIEnv* env, jobject o, @@ -315,22 +330,26 @@ static SkShader* ComposeShader_create2(JNIEnv* env, jobject o, static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* shaderA, SkiaShader* shaderB, SkPorterDuff::Mode porterDuffMode) { - SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode)); - SkXfermode* mode = (SkXfermode*) au.get(); - SkXfermode::Mode skiaMode; - if (!SkXfermode::IsMode(mode, &skiaMode)) { - skiaMode = SkXfermode::kSrcOver_Mode; - } - return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader); +#ifdef USE_OPENGL_RENDERER + SkXfermode::Mode mode = SkPorterDuff::ToXfermodeMode(porterDuffMode); + return new SkiaComposeShader(shaderA, shaderB, mode, shader); +#else + return NULL; +#endif } static SkiaShader* ComposeShader_postCreate1(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* shaderA, SkiaShader* shaderB, SkXfermode* mode) { +#ifdef USE_OPENGL_RENDERER SkXfermode::Mode skiaMode; if (!SkXfermode::IsMode(mode, &skiaMode)) { + // TODO: Support other modes skiaMode = SkXfermode::kSrcOver_Mode; } return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader); +#else + return NULL; +#endif } /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp index 716d96044e3e..147e1fa2e3a0 100644 --- a/core/jni/android/graphics/TextLayout.cpp +++ b/core/jni/android/graphics/TextLayout.cpp @@ -221,6 +221,18 @@ void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len, } } +bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count, + jsize contextCount, jchar* shaped) { + UErrorCode status = U_ZERO_ERROR; + count = shapeRtlText(context, start, count, contextCount, shaped, status); + if (U_SUCCESS(status)) { + return true; + } else { + LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status); + } + return false; +} + void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars, jint start, jint count, jint contextCount, int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) { @@ -231,12 +243,8 @@ void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars, uint8_t rtl = dirFlags & 0x1; if (rtl) { SkAutoSTMalloc<80, jchar> buffer(contextCount); - UErrorCode status = U_ZERO_ERROR; - count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status); - if (U_SUCCESS(status)) { + if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) { canvas->drawText(buffer.get(), count << 1, x_, y_, *paint); - } else { - LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status); } } else { canvas->drawText(chars + start, count << 1, x_, y_, *paint); diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h index 3d05e18f5e1c..8f666c095ccf 100644 --- a/core/jni/android/graphics/TextLayout.h +++ b/core/jni/android/graphics/TextLayout.h @@ -66,6 +66,9 @@ public: static bool prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags, const jchar** outText, int32_t* outBytes, jchar** outBuffer); + static bool prepareRtlTextRun(const jchar* context, jsize start, jsize& count, + jsize contextCount, jchar* shaped); + private: static bool needsLayout(const jchar* text, jint len, jint bidiFlags); diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 1feb3b3ecda9..0932473aa859 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -600,7 +600,7 @@ static bool mainWorkCallback(int fd, int events, void* data) { static jint loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue, jstring internalDataDir, jstring externalDataDir, int sdkVersion, - jobject jAssetMgr) + jobject jAssetMgr, jbyteArray savedState) { LOG_TRACE("loadNativeCode_native"); @@ -666,7 +666,18 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQ code->assetManager = assetManagerForJavaObject(env, jAssetMgr); - code->createActivityFunc(code, NULL, 0); + jbyte* rawSavedState = NULL; + jsize rawSavedSize = 0; + if (savedState != NULL) { + rawSavedState = env->GetByteArrayElements(savedState, NULL); + rawSavedSize = env->GetArrayLength(savedState); + } + + code->createActivityFunc(code, rawSavedState, rawSavedSize); + + if (rawSavedState != NULL) { + env->ReleaseByteArrayElements(savedState, rawSavedState, 0); + } } return (jint)code; @@ -706,17 +717,31 @@ onResume_native(JNIEnv* env, jobject clazz, jint handle) } } -static void +static jbyteArray onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle) { LOG_TRACE("onSaveInstanceState_native"); + + jbyteArray array = NULL; + if (handle != 0) { NativeCode* code = (NativeCode*)handle; if (code->callbacks.onSaveInstanceState != NULL) { size_t len = 0; - code->callbacks.onSaveInstanceState(code, &len); + jbyte* state = (jbyte*)code->callbacks.onSaveInstanceState(code, &len); + if (len > 0) { + array = env->NewByteArray(len); + if (array != NULL) { + env->SetByteArrayRegion(array, 0, len, state); + } + } + if (state != NULL) { + free(state); + } } } + + return array; } static void @@ -744,6 +769,18 @@ onStop_native(JNIEnv* env, jobject clazz, jint handle) } static void +onConfigurationChanged_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onConfigurationChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onConfigurationChanged != NULL) { + code->callbacks.onConfigurationChanged(code); + } + } +} + +static void onLowMemory_native(JNIEnv* env, jobject clazz, jint handle) { LOG_TRACE("onLowMemory_native"); @@ -934,14 +971,15 @@ finishPreDispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, } static const JNINativeMethod g_methods[] = { - { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;)I", + { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I", (void*)loadNativeCode_native }, { "unloadNativeCode", "(I)V", (void*)unloadNativeCode_native }, { "onStartNative", "(I)V", (void*)onStart_native }, { "onResumeNative", "(I)V", (void*)onResume_native }, - { "onSaveInstanceStateNative", "(I)V", (void*)onSaveInstanceState_native }, + { "onSaveInstanceStateNative", "(I)[B", (void*)onSaveInstanceState_native }, { "onPauseNative", "(I)V", (void*)onPause_native }, { "onStopNative", "(I)V", (void*)onStop_native }, + { "onConfigurationChangedNative", "(I)V", (void*)onConfigurationChanged_native }, { "onLowMemoryNative", "(I)V", (void*)onLowMemory_native }, { "onWindowFocusChangedNative", "(IZ)V", (void*)onWindowFocusChanged_native }, { "onSurfaceCreatedNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceCreated_native }, diff --git a/core/jni/android_content_res_Configuration.cpp b/core/jni/android_content_res_Configuration.cpp new file mode 100644 index 000000000000..28a43ab09f76 --- /dev/null +++ b/core/jni/android_content_res_Configuration.cpp @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#define LOG_TAG "Configuration" + +#include <utils/Log.h> +#include "utils/misc.h" + +#include "jni.h" +#include <android_runtime/android_content_res_Configuration.h> +#include "android_runtime/AndroidRuntime.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID mcc; + jfieldID mnc; + jfieldID locale; + jfieldID screenLayout; + jfieldID touchscreen; + jfieldID keyboard; + jfieldID keyboardHidden; + jfieldID hardKeyboardHidden; + jfieldID navigation; + jfieldID navigationHidden; + jfieldID orientation; + jfieldID uiMode; +} gConfigurationClassInfo; + +void android_Configuration_getFromJava( + JNIEnv* env, jobject clazz, struct AConfiguration* out) { + out->mcc = env->GetIntField(clazz, gConfigurationClassInfo.mcc); + out->mnc = env->GetIntField(clazz, gConfigurationClassInfo.mnc); + out->screenLayout = env->GetIntField(clazz, gConfigurationClassInfo.screenLayout); + out->touchscreen = env->GetIntField(clazz, gConfigurationClassInfo.touchscreen); + out->keyboard = env->GetIntField(clazz, gConfigurationClassInfo.keyboard); + out->navigation = env->GetIntField(clazz, gConfigurationClassInfo.navigation); + + out->inputFlags = env->GetIntField(clazz, gConfigurationClassInfo.keyboardHidden); + int hardKeyboardHidden = env->GetIntField(clazz, gConfigurationClassInfo.hardKeyboardHidden); + if (out->inputFlags == ACONFIGURATION_KEYSHIDDEN_NO + && hardKeyboardHidden == 2) { + out->inputFlags = ACONFIGURATION_KEYSHIDDEN_SOFT; + } + out->inputFlags |= env->GetIntField(clazz, gConfigurationClassInfo.navigationHidden) + << ResTable_config::SHIFT_NAVHIDDEN; + + out->orientation = env->GetIntField(clazz, gConfigurationClassInfo.orientation); + out->uiMode = env->GetIntField(clazz, gConfigurationClassInfo.uiMode); +} + +/* + * JNI registration. + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + //{ "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z", + // (void*) android_content_res_ObbScanner_getObbInfo }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_content_res_Configuration(JNIEnv* env) +{ + FIND_CLASS(gConfigurationClassInfo.clazz, "android/content/res/Configuration"); + + GET_FIELD_ID(gConfigurationClassInfo.mcc, gConfigurationClassInfo.clazz, + "mcc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.mnc, gConfigurationClassInfo.clazz, + "mnc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.locale, gConfigurationClassInfo.clazz, + "locale", "Ljava/util/Locale;"); + GET_FIELD_ID(gConfigurationClassInfo.screenLayout, gConfigurationClassInfo.clazz, + "screenLayout", "I"); + GET_FIELD_ID(gConfigurationClassInfo.touchscreen, gConfigurationClassInfo.clazz, + "touchscreen", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboard, gConfigurationClassInfo.clazz, + "keyboard", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, gConfigurationClassInfo.clazz, + "keyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden, gConfigurationClassInfo.clazz, + "hardKeyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz, + "navigation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, gConfigurationClassInfo.clazz, + "navigationHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.orientation, gConfigurationClassInfo.clazz, + "orientation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.uiMode, gConfigurationClassInfo.clazz, + "uiMode", "I"); + + return AndroidRuntime::registerNativeMethods(env, "android/content/res/Configuration", gMethods, + NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp index 848a57adea4d..eceef1c54620 100644 --- a/core/jni/android_os_ParcelFileDescriptor.cpp +++ b/core/jni/android_os_ParcelFileDescriptor.cpp @@ -66,6 +66,26 @@ static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEn return fileDescriptorClone; } +static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env, + jobject clazz, jobjectArray outFds) +{ + int fds[2]; + if (pipe(fds) < 0) { + return -errno; + } + + for (int i=0; i<2; i++) { + jobject fdObj = env->NewObject(gFileDescriptorOffsets.mClass, + gFileDescriptorOffsets.mConstructor); + if (fdObj != NULL) { + env->SetIntField(fdObj, gFileDescriptorOffsets.mDescriptor, fds[i]); + } + env->SetObjectArrayElement(outFds, i, fdObj); + } + + return 0; +} + static jint getFd(JNIEnv* env, jobject clazz) { jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor); @@ -109,6 +129,8 @@ static jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env, static const JNINativeMethod gParcelFileDescriptorMethods[] = { {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;", (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket}, + {"createPipeNative", "([Ljava/io/FileDescriptor;)I", + (void*)android_os_ParcelFileDescriptor_createPipeNative}, {"getStatSize", "()J", (void*)android_os_ParcelFileDescriptor_getStatSize}, {"seekTo", "(J)J", diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 9fd2b8674f4a..9f94af9e30ce 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -27,6 +27,7 @@ #include <SkPaint.h> #include <SkRegion.h> #include <SkScalerContext.h> +#include <SkTemplates.h> #include <SkXfermode.h> #include <OpenGLRenderer.h> @@ -41,6 +42,13 @@ namespace android { using namespace uirenderer; +/** + * Note: OpenGLRenderer JNI layer is generated and compiled only on supported + * devices. This means all the logic must be compiled only when the + * preprocessor variable USE_OPENGL_RENDERER is defined. + */ +#ifdef USE_OPENGL_RENDERER + // ---------------------------------------------------------------------------- // Java APIs // ---------------------------------------------------------------------------- @@ -228,6 +236,16 @@ static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject canvas, renderer->drawRect(left, top, right, bottom, paint); } +static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject canvas, + OpenGLRenderer* renderer, SkRegion* region, SkPaint* paint) { + SkRegion::Iterator it(*region); + while (!it.done()) { + const SkIRect& r = it.rect(); + renderer->drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); + it.next(); + } +} + static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject canvas, OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) { renderer->drawPath(path, paint); @@ -268,6 +286,23 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, } } +static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, + jint start, jint count, jint contextCount, jfloat x, jfloat y, + int flags, SkPaint* paint) { + uint8_t rtl = flags & 0x1; + if (rtl) { + SkAutoSTMalloc<80, jchar> buffer(contextCount); + jchar* shaped = buffer.get(); + if (TextLayout::prepareRtlTextRun(text, start, count, contextCount, shaped)) { + renderer->drawText((const char*) shaped, count << 1, count, x, y, paint); + } else { + LOGW("drawTextRun error"); + } + } else { + renderer->drawText((const char*) (text + start), count << 1, count, x, y, paint); + } +} + static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject canvas, OpenGLRenderer* renderer, jcharArray text, int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) { @@ -284,6 +319,42 @@ static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject canvas, env->ReleaseStringChars(text, textArray); } +static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject canvas, + OpenGLRenderer* renderer, jcharArray text, int index, int count, + int contextIndex, int contextCount, jfloat x, jfloat y, int dirFlags, + SkPaint* paint) { + jchar* textArray = env->GetCharArrayElements(text, NULL); + renderTextRun(renderer, textArray + contextIndex, index - contextIndex, + count, contextCount, x, y, dirFlags, paint); + env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); + } + +static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject canvas, + OpenGLRenderer* renderer, jstring text, int start, int end, + int contextStart, int contextEnd, jfloat x, jfloat y, int dirFlags, + SkPaint* paint) { + const jchar* textArray = env->GetStringChars(text, NULL); + jint count = end - start; + jint contextCount = contextEnd - contextStart; + renderTextRun(renderer, textArray + contextStart, start - contextStart, + count, contextCount, x, y, dirFlags, paint); + env->ReleaseStringChars(text, textArray); +} + +#endif // USE_OPENGL_RENDERER + +// ---------------------------------------------------------------------------- +// Common +// ---------------------------------------------------------------------------- + +static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) { +#ifdef USE_OPENGL_RENDERER + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + // ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -291,59 +362,73 @@ static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject canvas, const char* const kClassPathName = "android/view/GLES20Canvas"; static JNINativeMethod gMethods[] = { - { "nCreateRenderer", "()I", (void*) android_view_GLES20Canvas_createRenderer }, - { "nDestroyRenderer", "(I)V", (void*) android_view_GLES20Canvas_destroyRenderer }, - { "nSetViewport", "(III)V", (void*) android_view_GLES20Canvas_setViewport }, - { "nPrepare", "(I)V", (void*) android_view_GLES20Canvas_prepare }, - - { "nSave", "(II)I", (void*) android_view_GLES20Canvas_save }, - { "nRestore", "(I)V", (void*) android_view_GLES20Canvas_restore }, - { "nRestoreToCount", "(II)V", (void*) android_view_GLES20Canvas_restoreToCount }, - { "nGetSaveCount", "(I)I", (void*) android_view_GLES20Canvas_getSaveCount }, - - { "nSaveLayer", "(IFFFFII)I", (void*) android_view_GLES20Canvas_saveLayer }, - { "nSaveLayerAlpha", "(IFFFFII)I", (void*) android_view_GLES20Canvas_saveLayerAlpha }, - - { "nQuickReject", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_quickReject }, - { "nClipRect", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_clipRectF }, - { "nClipRect", "(IIIIII)Z", (void*) android_view_GLES20Canvas_clipRect }, - - { "nTranslate", "(IFF)V", (void*) android_view_GLES20Canvas_translate }, - { "nRotate", "(IF)V", (void*) android_view_GLES20Canvas_rotate }, - { "nScale", "(IFF)V", (void*) android_view_GLES20Canvas_scale }, - - { "nSetMatrix", "(II)V", (void*) android_view_GLES20Canvas_setMatrix }, - { "nGetMatrix", "(II)V", (void*) android_view_GLES20Canvas_getMatrix }, - { "nConcatMatrix", "(II)V", (void*) android_view_GLES20Canvas_concatMatrix }, - - { "nDrawBitmap", "(IIFFI)V", (void*) android_view_GLES20Canvas_drawBitmap }, - { "nDrawBitmap", "(IIFFFFFFFFI)V", (void*) android_view_GLES20Canvas_drawBitmapRect }, - { "nDrawBitmap", "(IIII)V", (void*) android_view_GLES20Canvas_drawBitmapMatrix }, - { "nDrawPatch", "(II[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch }, - { "nDrawColor", "(III)V", (void*) android_view_GLES20Canvas_drawColor }, - { "nDrawRect", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawRect }, - { "nDrawPath", "(III)V", (void*) android_view_GLES20Canvas_drawPath }, - - { "nResetModifiers", "(I)V", (void*) android_view_GLES20Canvas_resetModifiers }, - { "nSetupShader", "(II)V", (void*) android_view_GLES20Canvas_setupShader }, - { "nSetupColorFilter", "(II)V", (void*) android_view_GLES20Canvas_setupColorFilter }, - - { "nDrawText", "(I[CIIFFII)V", (void*) android_view_GLES20Canvas_drawTextArray }, - { "nDrawText", "(ILjava/lang/String;IIFFII)V", + { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable }, + +#ifdef USE_OPENGL_RENDERER + { "nCreateRenderer", "()I", (void*) android_view_GLES20Canvas_createRenderer }, + { "nDestroyRenderer", "(I)V", (void*) android_view_GLES20Canvas_destroyRenderer }, + { "nSetViewport", "(III)V", (void*) android_view_GLES20Canvas_setViewport }, + { "nPrepare", "(I)V", (void*) android_view_GLES20Canvas_prepare }, + + { "nSave", "(II)I", (void*) android_view_GLES20Canvas_save }, + { "nRestore", "(I)V", (void*) android_view_GLES20Canvas_restore }, + { "nRestoreToCount", "(II)V", (void*) android_view_GLES20Canvas_restoreToCount }, + { "nGetSaveCount", "(I)I", (void*) android_view_GLES20Canvas_getSaveCount }, + + { "nSaveLayer", "(IFFFFII)I", (void*) android_view_GLES20Canvas_saveLayer }, + { "nSaveLayerAlpha", "(IFFFFII)I", (void*) android_view_GLES20Canvas_saveLayerAlpha }, + + { "nQuickReject", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_quickReject }, + { "nClipRect", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_clipRectF }, + { "nClipRect", "(IIIIII)Z", (void*) android_view_GLES20Canvas_clipRect }, + + { "nTranslate", "(IFF)V", (void*) android_view_GLES20Canvas_translate }, + { "nRotate", "(IF)V", (void*) android_view_GLES20Canvas_rotate }, + { "nScale", "(IFF)V", (void*) android_view_GLES20Canvas_scale }, + + { "nSetMatrix", "(II)V", (void*) android_view_GLES20Canvas_setMatrix }, + { "nGetMatrix", "(II)V", (void*) android_view_GLES20Canvas_getMatrix }, + { "nConcatMatrix", "(II)V", (void*) android_view_GLES20Canvas_concatMatrix }, + + { "nDrawBitmap", "(IIFFI)V", (void*) android_view_GLES20Canvas_drawBitmap }, + { "nDrawBitmap", "(IIFFFFFFFFI)V", (void*) android_view_GLES20Canvas_drawBitmapRect }, + { "nDrawBitmap", "(IIII)V", (void*) android_view_GLES20Canvas_drawBitmapMatrix }, + { "nDrawPatch", "(II[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch }, + { "nDrawColor", "(III)V", (void*) android_view_GLES20Canvas_drawColor }, + { "nDrawRect", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawRect }, + { "nDrawRects", "(III)V", (void*) android_view_GLES20Canvas_drawRects }, + { "nDrawPath", "(III)V", (void*) android_view_GLES20Canvas_drawPath }, + + { "nResetModifiers", "(I)V", (void*) android_view_GLES20Canvas_resetModifiers }, + { "nSetupShader", "(II)V", (void*) android_view_GLES20Canvas_setupShader }, + { "nSetupColorFilter", "(II)V", (void*) android_view_GLES20Canvas_setupColorFilter }, + + { "nDrawText", "(I[CIIFFII)V", (void*) android_view_GLES20Canvas_drawTextArray }, + { "nDrawText", "(ILjava/lang/String;IIFFII)V", (void*) android_view_GLES20Canvas_drawText }, + { "nDrawTextRun", "(I[CIIIIFFII)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, + { "nDrawTextRun", "(ILjava/lang/String;IIIIFFII)V", + (void*) android_view_GLES20Canvas_drawTextRun }, + { "nGetClipBounds", "(ILandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_getClipBounds }, +#endif }; -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); - -#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ - var = env->GetMethodID(clazz, methodName, methodDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find method " methodName); +#ifdef USE_OPENGL_RENDERER + #define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + + #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); +#else + #define FIND_CLASS(var, className) + #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) +#endif int register_android_view_GLES20Canvas(JNIEnv* env) { FIND_CLASS(gRectClassInfo.clazz, "android/graphics/Rect"); diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp deleted file mode 100644 index 6d20c9de85cf..000000000000 --- a/core/jni/android_view_HardwareRenderer.cpp +++ /dev/null @@ -1,44 +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. - */ - -#include <utils/SkGLCanvas.h> - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <android_runtime/AndroidRuntime.h> -#include <utils/misc.h> - -// ---------------------------------------------------------------------------- - -namespace android { - -static void android_view_HardwareRenderer_abandonGlCaches(JNIEnv* env, jobject) { - SkGLCanvas::AbandonAllTextures(); -} - -// ---------------------------------------------------------------------------- - -const char* const kClassPathName = "android/view/HardwareRenderer"; - -static JNINativeMethod gMethods[] = { - { "nativeAbandonGlCaches", "()V", (void*)android_view_HardwareRenderer_abandonGlCaches }, -}; - -int register_android_view_HardwareRenderer(JNIEnv* env) { - return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -}; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 01ded6805a36..1f66d05d5e30 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1284,6 +1284,9 @@ android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true"> </activity> + <activity android:name="com.android.internal.app.PlatLogoActivity" + android:theme="@style/Theme.NoTitleBar.Fullscreen"> + </activity> <activity android:name="com.android.internal.app.DisableCarModeActivity" android:theme="@style/Theme.NoDisplay" android:excludeFromRecents="true"> diff --git a/core/res/res/anim/animator_fade_in.xml b/core/res/res/anim/animator_fade_in.xml new file mode 100644 index 000000000000..cd5042fb9a2e --- /dev/null +++ b/core/res/res/anim/animator_fade_in.xml @@ -0,0 +1,26 @@ +<?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. +*/ +--> + +<property xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="0" + android:valueTo="1" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime" +/> diff --git a/core/res/res/anim/animator_fade_out.xml b/core/res/res/anim/animator_fade_out.xml new file mode 100644 index 000000000000..dfb5d9c6b555 --- /dev/null +++ b/core/res/res/anim/animator_fade_out.xml @@ -0,0 +1,26 @@ +<?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. +*/ +--> + +<property xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime" +/> diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml new file mode 100644 index 000000000000..d4091e851d6d --- /dev/null +++ b/core/res/res/anim/fragment_close_enter.xml @@ -0,0 +1,48 @@ +<?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. +*/ +--> +<sequencer xmlns:android="http://schemas.android.com/apk/res/android"> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="2" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="2" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="-400" + android:valueTo="0" + android:valueType="floatType" + android:propertyName="translationX" + android:duration="@android:integer/config_mediumAnimTime"/> +</sequencer>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml new file mode 100644 index 000000000000..3e2cd22cc56f --- /dev/null +++ b/core/res/res/anim/fragment_close_exit.xml @@ -0,0 +1,48 @@ +<?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. +*/ +--> +<sequencer xmlns:android="http://schemas.android.com/apk/res/android"> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1" + android:valueTo=".5" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1" + android:valueTo=".5" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="0" + android:valueTo="400" + android:valueType="floatType" + android:propertyName="translationX" + android:duration="@android:integer/config_mediumAnimTime"/> +</sequencer>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml new file mode 100644 index 000000000000..c89001c09cd6 --- /dev/null +++ b/core/res/res/anim/fragment_open_enter.xml @@ -0,0 +1,46 @@ +<?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. +*/ +--> +<sequencer xmlns:android="http://schemas.android.com/apk/res/android"> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="2" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/decelerate_interpolator" + android:valueFrom="2" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:valueFrom="400" + android:valueTo="0" + android:valueType="floatType" + android:propertyName="translationX" + android:duration="@android:integer/config_mediumAnimTime"/> +</sequencer>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml new file mode 100644 index 000000000000..427fe4f8f255 --- /dev/null +++ b/core/res/res/anim/fragment_open_exit.xml @@ -0,0 +1,46 @@ +<?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. +*/ +--> +<sequencer xmlns:android="http://schemas.android.com/apk/res/android"> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1" + android:valueTo="2" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:interpolator="@anim/accelerate_interpolator" + android:valueFrom="1" + android:valueTo="2" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_mediumAnimTime"/> + <property + android:valueFrom="0" + android:valueTo="-400" + android:valueType="floatType" + android:propertyName="translationX" + android:duration="@android:integer/config_mediumAnimTime"/> +</sequencer>
\ No newline at end of file diff --git a/core/res/res/drawable-hdpi/btn_check_label_background_light.9.png b/core/res/res/drawable-hdpi/btn_check_label_background_light.9.png Binary files differnew file mode 100644 index 000000000000..97e680664755 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_label_background_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_check_off.png b/core/res/res/drawable-hdpi/btn_check_off.png Binary files differindex aad9ef7800fd..911e1aa6598c 100644 --- a/core/res/res/drawable-hdpi/btn_check_off.png +++ b/core/res/res/drawable-hdpi/btn_check_off.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable.png b/core/res/res/drawable-hdpi/btn_check_off_disable.png Binary files differindex eaee9e0bec4d..d72e2b95b810 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png Binary files differindex 6d2c2938a5a5..d72e2b95b810 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_focused_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_light.png Binary files differnew file mode 100644 index 000000000000..240a0444e1eb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_focused_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_disable_light.png b/core/res/res/drawable-hdpi/btn_check_off_disable_light.png Binary files differnew file mode 100644 index 000000000000..240a0444e1eb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_off_disable_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_light.png b/core/res/res/drawable-hdpi/btn_check_off_light.png Binary files differnew file mode 100644 index 000000000000..4ca3c56d3f3c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_off_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed.png b/core/res/res/drawable-hdpi/btn_check_off_pressed.png Binary files differindex 1c442e9b45b9..08f41812ff45 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_pressed.png +++ b/core/res/res/drawable-hdpi/btn_check_off_pressed.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_pressed_light.png b/core/res/res/drawable-hdpi/btn_check_off_pressed_light.png Binary files differnew file mode 100644 index 000000000000..d3754ddb42ad --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_off_pressed_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected.png b/core/res/res/drawable-hdpi/btn_check_off_selected.png Binary files differindex b852b2cb2f06..264f102132ed 100644 --- a/core/res/res/drawable-hdpi/btn_check_off_selected.png +++ b/core/res/res/drawable-hdpi/btn_check_off_selected.png diff --git a/core/res/res/drawable-hdpi/btn_check_off_selected_light.png b/core/res/res/drawable-hdpi/btn_check_off_selected_light.png Binary files differnew file mode 100644 index 000000000000..48506bf721ba --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_off_selected_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on.png b/core/res/res/drawable-hdpi/btn_check_on.png Binary files differindex cd5c1814344a..5541c67ba042 100644 --- a/core/res/res/drawable-hdpi/btn_check_on.png +++ b/core/res/res/drawable-hdpi/btn_check_on.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable.png b/core/res/res/drawable-hdpi/btn_check_on_disable.png Binary files differindex b4fc51a1bf34..7805458d2cdc 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png Binary files differindex bf346471edd5..7805458d2cdc 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_light.png Binary files differnew file mode 100644 index 000000000000..4e268d5168cb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_light.png b/core/res/res/drawable-hdpi/btn_check_on_disable_light.png Binary files differnew file mode 100644 index 000000000000..4e268d5168cb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_on_disable_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_light.png b/core/res/res/drawable-hdpi/btn_check_on_light.png Binary files differnew file mode 100644 index 000000000000..768c4afaed20 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_on_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed.png b/core/res/res/drawable-hdpi/btn_check_on_pressed.png Binary files differindex fa5c7a23d932..37e3953c87b3 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_pressed.png +++ b/core/res/res/drawable-hdpi/btn_check_on_pressed.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_pressed_light.png b/core/res/res/drawable-hdpi/btn_check_on_pressed_light.png Binary files differnew file mode 100644 index 000000000000..fc29e46248ab --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_on_pressed_light.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected.png b/core/res/res/drawable-hdpi/btn_check_on_selected.png Binary files differindex a6a21adc1e02..a0beac44afc8 100644 --- a/core/res/res/drawable-hdpi/btn_check_on_selected.png +++ b/core/res/res/drawable-hdpi/btn_check_on_selected.png diff --git a/core/res/res/drawable-hdpi/btn_check_on_selected_light.png b/core/res/res/drawable-hdpi/btn_check_on_selected_light.png Binary files differnew file mode 100644 index 000000000000..5df45c74f140 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_on_selected_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_label_background_light.9.png b/core/res/res/drawable-hdpi/btn_radio_label_background_light.9.png Binary files differnew file mode 100644 index 000000000000..45c5c6a76e19 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_label_background_light.9.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off.png b/core/res/res/drawable-hdpi/btn_radio_off.png Binary files differindex c0b14aa4db86..301c97df691b 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off.png +++ b/core/res/res/drawable-hdpi/btn_radio_off.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_light.png b/core/res/res/drawable-hdpi/btn_radio_off_light.png Binary files differnew file mode 100644 index 000000000000..657c8e55b8e9 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_off_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed.png Binary files differindex 318958187c0e..5e6ef2b947df 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_pressed.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_light.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_light.png Binary files differnew file mode 100644 index 000000000000..342bf1168c3f --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_off_pressed_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected.png b/core/res/res/drawable-hdpi/btn_radio_off_selected.png Binary files differindex f337703e2180..d11ae858a507 100644 --- a/core/res/res/drawable-hdpi/btn_radio_off_selected.png +++ b/core/res/res/drawable-hdpi/btn_radio_off_selected.png diff --git a/core/res/res/drawable-hdpi/btn_radio_off_selected_light.png b/core/res/res/drawable-hdpi/btn_radio_off_selected_light.png Binary files differnew file mode 100644 index 000000000000..68bd1dfee468 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_off_selected_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on.png b/core/res/res/drawable-hdpi/btn_radio_on.png Binary files differindex c90d2eb05e93..5b0dbe842358 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on.png +++ b/core/res/res/drawable-hdpi/btn_radio_on.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_light.png b/core/res/res/drawable-hdpi/btn_radio_on_light.png Binary files differnew file mode 100644 index 000000000000..45ae36b5e040 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_on_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed.png Binary files differindex d79450b8e86a..c3a0d48f9fab 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_pressed.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_light.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_light.png Binary files differnew file mode 100644 index 000000000000..ca223587650a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_on_pressed_light.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected.png b/core/res/res/drawable-hdpi/btn_radio_on_selected.png Binary files differindex db50c437c5a2..6c05f472aca7 100644 --- a/core/res/res/drawable-hdpi/btn_radio_on_selected.png +++ b/core/res/res/drawable-hdpi/btn_radio_on_selected.png diff --git a/core/res/res/drawable-hdpi/btn_radio_on_selected_light.png b/core/res/res/drawable-hdpi/btn_radio_on_selected_light.png Binary files differnew file mode 100644 index 000000000000..a17fa1edd124 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_on_selected_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_label_background_light.9.png b/core/res/res/drawable-mdpi/btn_check_label_background_light.9.png Binary files differnew file mode 100644 index 000000000000..79367b8c5393 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_label_background_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_check_off.png b/core/res/res/drawable-mdpi/btn_check_off.png Binary files differindex 56d3861542ea..5e44c293ca33 100644 --- a/core/res/res/drawable-mdpi/btn_check_off.png +++ b/core/res/res/drawable-mdpi/btn_check_off.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable.png b/core/res/res/drawable-mdpi/btn_check_off_disable.png Binary files differindex e012afd28ddd..a603fb1aff9c 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_focused.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused.png Binary files differindex 0837bbdb89f7..a603fb1aff9c 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_disable_focused.png +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_focused_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_light.png Binary files differnew file mode 100644 index 000000000000..69e9ff9eb1a2 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_focused_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_disable_light.png b/core/res/res/drawable-mdpi/btn_check_off_disable_light.png Binary files differnew file mode 100644 index 000000000000..69e9ff9eb1a2 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_off_disable_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_light.png b/core/res/res/drawable-mdpi/btn_check_off_light.png Binary files differnew file mode 100644 index 000000000000..5b2ec928e324 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_off_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed.png b/core/res/res/drawable-mdpi/btn_check_off_pressed.png Binary files differindex 984dfd750d30..611bb1d7a05b 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_pressed.png +++ b/core/res/res/drawable-mdpi/btn_check_off_pressed.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_pressed_light.png b/core/res/res/drawable-mdpi/btn_check_off_pressed_light.png Binary files differnew file mode 100644 index 000000000000..5a0ea441f37a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_off_pressed_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected.png b/core/res/res/drawable-mdpi/btn_check_off_selected.png Binary files differindex 20842d41c8f1..aa28df22a4fb 100644 --- a/core/res/res/drawable-mdpi/btn_check_off_selected.png +++ b/core/res/res/drawable-mdpi/btn_check_off_selected.png diff --git a/core/res/res/drawable-mdpi/btn_check_off_selected_light.png b/core/res/res/drawable-mdpi/btn_check_off_selected_light.png Binary files differnew file mode 100644 index 000000000000..ade1136e0214 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_off_selected_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on.png b/core/res/res/drawable-mdpi/btn_check_on.png Binary files differindex 791ac1d92c32..130d5629237c 100644 --- a/core/res/res/drawable-mdpi/btn_check_on.png +++ b/core/res/res/drawable-mdpi/btn_check_on.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable.png b/core/res/res/drawable-mdpi/btn_check_on_disable.png Binary files differindex 6cb02f3e4674..f19972a447b0 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused.png Binary files differindex 8a73b33f0df5..f19972a447b0 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_disable_focused.png +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_light.png Binary files differnew file mode 100644 index 000000000000..13ef46e2c39d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_light.png b/core/res/res/drawable-mdpi/btn_check_on_disable_light.png Binary files differnew file mode 100644 index 000000000000..13ef46e2c39d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_on_disable_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_light.png b/core/res/res/drawable-mdpi/btn_check_on_light.png Binary files differnew file mode 100644 index 000000000000..6b7808bbb6a6 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_on_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed.png b/core/res/res/drawable-mdpi/btn_check_on_pressed.png Binary files differindex 300d64afecb9..df753f51eb49 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_pressed.png +++ b/core/res/res/drawable-mdpi/btn_check_on_pressed.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_pressed_light.png b/core/res/res/drawable-mdpi/btn_check_on_pressed_light.png Binary files differnew file mode 100644 index 000000000000..6a4dd2ce7c76 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_on_pressed_light.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected.png b/core/res/res/drawable-mdpi/btn_check_on_selected.png Binary files differindex 0b36adbe2712..7586881cc458 100644 --- a/core/res/res/drawable-mdpi/btn_check_on_selected.png +++ b/core/res/res/drawable-mdpi/btn_check_on_selected.png diff --git a/core/res/res/drawable-mdpi/btn_check_on_selected_light.png b/core/res/res/drawable-mdpi/btn_check_on_selected_light.png Binary files differnew file mode 100644 index 000000000000..24701ce9d59a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_on_selected_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_label_background_light.9.png b/core/res/res/drawable-mdpi/btn_radio_label_background_light.9.png Binary files differnew file mode 100644 index 000000000000..16e89397baec --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_label_background_light.9.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off.png b/core/res/res/drawable-mdpi/btn_radio_off.png Binary files differindex 407632b1c0d5..16c1c6bc7b51 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off.png +++ b/core/res/res/drawable-mdpi/btn_radio_off.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_light.png b/core/res/res/drawable-mdpi/btn_radio_off_light.png Binary files differnew file mode 100644 index 000000000000..e8287f30693e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_off_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed.png Binary files differindex d6d8a9d4b685..b25217b6c214 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_pressed.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_light.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_light.png Binary files differnew file mode 100644 index 000000000000..b63b9b0250e6 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_off_pressed_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected.png b/core/res/res/drawable-mdpi/btn_radio_off_selected.png Binary files differindex 53f3e870e77d..bef757299c1a 100644 --- a/core/res/res/drawable-mdpi/btn_radio_off_selected.png +++ b/core/res/res/drawable-mdpi/btn_radio_off_selected.png diff --git a/core/res/res/drawable-mdpi/btn_radio_off_selected_light.png b/core/res/res/drawable-mdpi/btn_radio_off_selected_light.png Binary files differnew file mode 100644 index 000000000000..af754e145cfa --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_off_selected_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on.png b/core/res/res/drawable-mdpi/btn_radio_on.png Binary files differindex 25a3ccc54e4a..4ed74712e28c 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on.png +++ b/core/res/res/drawable-mdpi/btn_radio_on.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_light.png b/core/res/res/drawable-mdpi/btn_radio_on_light.png Binary files differnew file mode 100644 index 000000000000..62aaa41318d1 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_on_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed.png Binary files differindex c904a35c742b..7cf91c6c7fa9 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_pressed.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_pressed_light.png b/core/res/res/drawable-mdpi/btn_radio_on_pressed_light.png Binary files differnew file mode 100644 index 000000000000..0d93507b66f4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_on_pressed_light.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected.png b/core/res/res/drawable-mdpi/btn_radio_on_selected.png Binary files differindex 78e1fc0227ca..56f6f5b43fa8 100644 --- a/core/res/res/drawable-mdpi/btn_radio_on_selected.png +++ b/core/res/res/drawable-mdpi/btn_radio_on_selected.png diff --git a/core/res/res/drawable-mdpi/btn_radio_on_selected_light.png b/core/res/res/drawable-mdpi/btn_radio_on_selected_light.png Binary files differnew file mode 100644 index 000000000000..48dd8e9d3c6c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_on_selected_light.png diff --git a/core/res/res/drawable-nodpi/platlogo.jpg b/core/res/res/drawable-nodpi/platlogo.jpg Binary files differnew file mode 100644 index 000000000000..0e7780c12b1a --- /dev/null +++ b/core/res/res/drawable-nodpi/platlogo.jpg diff --git a/core/res/res/drawable/btn_check_light.xml b/core/res/res/drawable/btn_check_light.xml new file mode 100644 index 000000000000..85f119a3151a --- /dev/null +++ b/core/res/res/drawable/btn_check_light.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- Enabled states --> + + <item android:state_checked="true" android:state_window_focused="false" + android:state_enabled="true" + android:drawable="@drawable/btn_check_on_light" /> + <item android:state_checked="false" android:state_window_focused="false" + android:state_enabled="true" + android:drawable="@drawable/btn_check_off_light" /> + + <item android:state_checked="true" android:state_pressed="true" + android:state_enabled="true" + android:drawable="@drawable/btn_check_on_pressed_light" /> + <item android:state_checked="false" android:state_pressed="true" + android:state_enabled="true" + android:drawable="@drawable/btn_check_off_pressed_light" /> + + <item android:state_checked="true" android:state_focused="true" + android:state_enabled="true" + android:drawable="@drawable/btn_check_on_selected_light" /> + <item android:state_checked="false" android:state_focused="true" + android:state_enabled="true" + android:drawable="@drawable/btn_check_off_selected_light" /> + + <item android:state_checked="false" + android:state_enabled="true" + android:drawable="@drawable/btn_check_off_light" /> + <item android:state_checked="true" + android:state_enabled="true" + android:drawable="@drawable/btn_check_on_light" /> + + + <!-- Disabled states --> + + <item android:state_checked="true" android:state_window_focused="false" + android:drawable="@drawable/btn_check_on_disable_light" /> + <item android:state_checked="false" android:state_window_focused="false" + android:drawable="@drawable/btn_check_off_disable_light" /> + + <item android:state_checked="true" android:state_focused="true" + android:drawable="@drawable/btn_check_on_disable_focused_light" /> + <item android:state_checked="false" android:state_focused="true" + android:drawable="@drawable/btn_check_off_disable_focused_light" /> + + <item android:state_checked="false" android:drawable="@drawable/btn_check_off_disable_light" /> + <item android:state_checked="true" android:drawable="@drawable/btn_check_on_disable_light" /> + +</selector> diff --git a/core/res/res/drawable/btn_radio_light.xml b/core/res/res/drawable/btn_radio_light.xml new file mode 100644 index 000000000000..51c930bae4c6 --- /dev/null +++ b/core/res/res/drawable/btn_radio_light.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:state_window_focused="false" + android:drawable="@drawable/btn_radio_on_light" /> + <item android:state_checked="false" android:state_window_focused="false" + android:drawable="@drawable/btn_radio_off_light" /> + + <item android:state_checked="true" android:state_pressed="true" + android:drawable="@drawable/btn_radio_on_pressed_light" /> + <item android:state_checked="false" android:state_pressed="true" + android:drawable="@drawable/btn_radio_off_pressed_light" /> + + <item android:state_checked="true" android:state_focused="true" + android:drawable="@drawable/btn_radio_on_selected_light" /> + <item android:state_checked="false" android:state_focused="true" + android:drawable="@drawable/btn_radio_off_selected_light" /> + + <item android:state_checked="false" android:drawable="@drawable/btn_radio_off_light" /> + <item android:state_checked="true" android:drawable="@drawable/btn_radio_on_light" /> +</selector> diff --git a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml new file mode 100644 index 000000000000..d0277f0687b2 --- /dev/null +++ b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- +This is an optimized layout for a screen with +the Action Bar enabled overlaying application content. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:fitsSystemWindows="true"> + <FrameLayout android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <ViewAnimator android:id="@+id/action_bar_animator" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="top" + android:inAnimation="@anim/push_down_in" + android:outAnimation="@anim/push_down_out"> + <com.android.internal.widget.ActionBarView + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?android:attr/windowActionBarStyle" /> + <com.android.internal.widget.ActionBarContextView + android:id="@+id/action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </ViewAnimator> + <ImageView android:src="?android:attr/windowContentOverlay" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/action_bar_animator" /> +</RelativeLayout> diff --git a/core/res/res/layout/action_mode_bar.xml b/core/res/res/layout/action_mode_bar.xml index 8e2e69dae673..acf327e808ec 100644 --- a/core/res/res/layout/action_mode_bar.xml +++ b/core/res/res/layout/action_mode_bar.xml @@ -19,4 +19,5 @@ <com.android.internal.widget.ActionBarContextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:visibility="gone" /> diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml index d530e96931cb..eeba18e743ca 100644 --- a/core/res/res/layout/preference_list_content.xml +++ b/core/res/res/layout/preference_list_content.xml @@ -59,15 +59,28 @@ android:drawablePadding="3dip" android:text="@string/back_button_label" /> - - <Button android:id="@+id/next_button" - android:layout_width="150dip" + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_margin="5dip" - android:layout_alignParentRight="true" - android:drawableRight="@drawable/ic_btn_next" - android:drawablePadding="3dip" - android:text="@string/next_button_label" - /> + android:layout_alignParentRight="true"> + + <Button android:id="@+id/skip_button" + android:layout_width="150dip" + android:layout_height="wrap_content" + android:layout_margin="5dip" + android:text="@string/skip_button_label" + android:visibility="gone" + /> + + <Button android:id="@+id/next_button" + android:layout_width="150dip" + android:layout_height="wrap_content" + android:layout_margin="5dip" + android:drawableRight="@drawable/ic_btn_next" + android:drawablePadding="3dip" + android:text="@string/next_button_label" + /> + </LinearLayout> </RelativeLayout> </LinearLayout> diff --git a/core/res/res/layout/screen_action_bar_overlay.xml b/core/res/res/layout/screen_action_bar_overlay.xml new file mode 100644 index 000000000000..cb1ffc57a05d --- /dev/null +++ b/core/res/res/layout/screen_action_bar_overlay.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- +This is an optimized layout for a screen with +the Action Bar enabled overlaying application content. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:fitsSystemWindows="true"> + <FrameLayout android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <ViewAnimator android:id="@+id/action_bar_animator" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="top" + android:inAnimation="@anim/push_down_in" + android:outAnimation="@anim/push_down_out"> + <com.android.internal.widget.ActionBarView + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?android:attr/windowActionBarStyle" /> + <com.android.internal.widget.ActionBarContextView + android:id="@+id/action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </ViewAnimator> + <ImageView android:src="?android:attr/windowContentOverlay" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/action_bar_animator" /> + <LinearLayout android:id="@+id/lower_action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + style="?android:attr/windowActionBarStyle" + android:visibility="gone" /> +</RelativeLayout> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index f7f4d6936bce..e768308e620e 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -746,8 +746,8 @@ <string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string> <string name="report" msgid="4060218260984795706">"Nahlásit"</string> <string name="wait" msgid="7147118217226317732">"Počkat"</string> - <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces<xliff:g id="PROCESS">%2$s</xliff:g>) porušila své samovynucované zásady StrictMode."</string> - <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své samovynucované zásady StrictMode."</string> + <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) porušila své vlastní vynucené zásady StrictMode."</string> + <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své vlastní vynucené zásady StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Tuto možnost vyberte, chcete-li přepnout na aplikaci."</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Přepnout mezi aplikacemi?"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index ac42fd1ec5d6..87376fec6a31 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -749,7 +749,7 @@ <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string> <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string> - <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswahl zum Wechseln in die Anwendung"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswählen zum Wechseln in die Anwendung"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Anwendung wechseln?"</string> <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Eine andere Anwendung wird bereits ausgeführt und muss vor dem Start einer neuen Anwendung beendet werden."</string> <string name="old_app_action" msgid="493129172238566282">"Zu <xliff:g id="OLD_APP">%1$s</xliff:g> zurückkehren"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index c2dd90b254ae..04b6addc8ef6 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -746,8 +746,8 @@ <string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string> <string name="report" msgid="4060218260984795706">"Rapport"</string> <string name="wait" msgid="7147118217226317732">"Attendre"</string> - <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles StrictMode."</string> - <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles StrictMode."</string> + <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles du mode strict."</string> + <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles du mode strict."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string> <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Sélectionner pour changer d\'application"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Passer d\'une application à l\'autre ?"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index fcb7eb7f752f..21e8ad8d4f89 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -173,7 +173,7 @@ <string name="permlab_statusBar" msgid="7417192629601890791">"отключать или изменять строку состояния"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Позволяет приложению отключать строку состояния или добавлять/удалять системные значки."</string> <string name="permlab_statusBarService" msgid="7247281911387931485">"строка состояния"</string> - <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению быть строкой состояния."</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению заменять строку состояния."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"разворачивать/сворачивать строку состояния"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Позволяет приложению разворачивать или сворачивать строку состояния."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"перехватывать исходящие вызовы"</string> @@ -315,7 +315,7 @@ <string name="permlab_recordAudio" msgid="3876049771427466323">"записывать аудио"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Позволяет приложению получать доступ к пути аудиозаписи."</string> <string name="permlab_camera" msgid="3616391919559751192">"снимать фото и видео"</string> - <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры. Это дает приложению возможность в любое время получать изображения с объектива камеры."</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры в любое время."</string> <string name="permlab_brick" msgid="8337817093326370537">"отключать телефон"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Позволяет данному приложению отключить телефон навсегда. Это очень опасно."</string> <string name="permlab_reboot" msgid="2898560872462638242">"принудительно перезагружать телефон"</string> @@ -749,7 +749,7 @@ <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string> <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string> - <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы переключиться в приложение"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы перейти к приложению"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Переключить приложения?"</string> <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Выполняется другое приложение, которое должно быть остановлено прежде, чем запускать новое."</string> <string name="old_app_action" msgid="493129172238566282">"Вернуться к приложению <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 8e623e8adb49..e8f5a201605d 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -563,7 +563,7 @@ <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet är låst."</string> <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser upp SIM-kort…"</string> <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string> - <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett ditt lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string> + <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string> <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har angett din PIN-kod fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string> <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till kommer du att uppmanas att låsa upp telefonen med din Google-inloggning."\n\n" Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string> <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index b207db9513de..a9dd8871f9c7 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -746,8 +746,8 @@ <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string> <string name="report" msgid="4060218260984795706">"Rapor"</string> <string name="wait" msgid="7147118217226317732">"Bekle"</string> - <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string> - <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string> + <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> + <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string> <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Uygulama değiştirmeyi seçin"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Uygulamaların arasında geçiş yapılsın mı?"</string> diff --git a/core/res/res/values-xlarge/styles.xml b/core/res/res/values-xlarge/styles.xml index 40e423e23be1..ff7df7c3ec49 100644 --- a/core/res/res/values-xlarge/styles.xml +++ b/core/res/res/values-xlarge/styles.xml @@ -19,7 +19,6 @@ <style name="TextAppearance.StatusBar"> <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> - <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> </style> <style name="TextAppearance.StatusBar.Ticker"> </style> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 4d78b86bc8a1..0949553f4747 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -315,7 +315,7 @@ <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"允许应用程序访问录音路径。"</string> <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string> - <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中看到的图片。"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中的图片。"</string> <string name="permlab_brick" msgid="8337817093326370537">"永久停用手机"</string> <string name="permdesc_brick" msgid="5569526552607599221">"允许应用程序永久停用整个手机,这非常危险。"</string> <string name="permlab_reboot" msgid="2898560872462638242">"强行重新启动手机"</string> @@ -747,7 +747,7 @@ <string name="report" msgid="4060218260984795706">"报告"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> - <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策"</string> + <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string> <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"选择以切换到该应用程序"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"要切换应用程序吗?"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 4ab9907f8c5c..2f0a49bba9de 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -34,7 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"連線發生問題或錯誤的 MMI 碼。"</string> - <string name="mmiFdnError" msgid="5224398216385316471">"僅允許在固定撥號時使用此操作。"</string> + <string name="mmiFdnError" msgid="5224398216385316471">"僅限對固定撥號號碼執行此作業。"</string> <string name="serviceEnabled" msgid="8147278346414714315">"服務已啟用。"</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"已啟用服務:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"服務已停用。"</string> @@ -173,7 +173,7 @@ <string name="permlab_statusBar" msgid="7417192629601890791">"停用或變更狀態列"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"允許應用程式停用狀態列或新增、移除系統圖示。"</string> <string name="permlab_statusBarService" msgid="7247281911387931485">"狀態列"</string> - <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式成為狀態列。"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式以狀態列顯示。"</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展開/收攏狀態列"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允許應用程式展開或收攏狀態列。"</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"攔截撥出電話"</string> @@ -314,8 +314,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"允許應用程式存取音訊錄製路徑。"</string> - <string name="permlab_camera" msgid="3616391919559751192">"拍照和錄影"</string> - <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影。此功能可讓應用程式隨時透過相機收集圖片。"</string> + <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影,此功能可讓應用程式隨時透過相機收集圖片。"</string> <string name="permlab_brick" msgid="8337817093326370537">"永久停用電話"</string> <string name="permdesc_brick" msgid="5569526552607599221">"允許應用程式永久停用手機。此項操作非常危險。"</string> <string name="permlab_reboot" msgid="2898560872462638242">"強制重開機"</string> @@ -535,7 +535,7 @@ <string name="orgTypeCustom" msgid="225523415372088322">"自訂"</string> <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string> <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"輸入密碼即可解鎖"</string> - <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 即可解鎖"</string> + <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 進行解鎖"</string> <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤!"</string> <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string> <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string> @@ -563,8 +563,8 @@ <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡已鎖定。"</string> <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"解鎖 SIM 卡中..."</string> <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。"\n\n" 請在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再嘗試。"</string> - <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string> - <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒內再試一次。"</string> + <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string> + <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。再錯誤 <xliff:g id="NUMBER_1">%d</xliff:g> 次後,系統會要求使用 Google 登入來解鎖。"\n\n" 請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string> <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘記解鎖圖形?"</string> @@ -746,12 +746,12 @@ <string name="force_close" msgid="3653416315450806396">"強制關閉"</string> <string name="report" msgid="4060218260984795706">"回報"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> - <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行實施的 StrictMode 政策。"</string> - <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行實施的 StrictMode 政策。"</string> + <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> + <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string> - <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取切換應用程式"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取以切換到該應用程式"</string> <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"切換應用程式?"</string> - <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行,才能啟動新的應用程式。"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行該應用程式,才能啟動新的應用程式。"</string> <string name="old_app_action" msgid="493129172238566282">"返回 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> <string name="old_app_description" msgid="942967900237208466">"請勿啟動新的應用程式。"</string> <string name="new_app_action" msgid="5472756926945440706">"啟動 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> @@ -862,15 +862,15 @@ <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string> <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string> <string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string> - <string name="no_file_chosen" msgid="6363648562170759465">"沒有選擇檔案"</string> + <string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string> <string name="reset" msgid="2448168080964209908">"重設"</string> <string name="submit" msgid="1602335572089911941">"提交"</string> <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"已啟用車用模式"</string> <string name="car_mode_disable_notification_message" msgid="668663626721675614">"選取結束車用模式。"</string> <string name="tethered_notification_title" msgid="3146694234398202601">"數據連線或無線基地台已啟用"</string> <string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string> - <string name="back_button_label" msgid="2300470004503343439">"上一個"</string> - <string name="next_button_label" msgid="1080555104677992408">"下一個"</string> + <string name="back_button_label" msgid="2300470004503343439">"上一頁"</string> + <string name="next_button_label" msgid="1080555104677992408">"下一頁"</string> <string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用量"</string> <string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string> <string name="throttled_notification_title" msgid="6269541897729781332">"已達行動資料上限"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 97c58226b16c..a75f1a60d23e 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -248,6 +248,11 @@ in place of the usual title bar. --> <attr name="windowActionBar" format="boolean" /> + <!-- Flag indicating whether this window's Action Bar should overlay + application content. Does nothing if the window would not + have an Action Bar. --> + <attr name="windowActionBarOverlay" format="boolean" /> + <!-- Reference to a style for the Action Bar --> <attr name="windowActionBarStyle" format="reference" /> @@ -999,6 +1004,7 @@ <attr name="windowActionBar" /> <attr name="windowActionBarStyle" /> <attr name="windowActionModeOverlay" /> + <attr name="windowActionBarOverlay" /> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> @@ -1015,6 +1021,14 @@ <attr name="centerMedium" format="reference|color" /> </declare-styleable> + <!-- Fragment animation class attributes. --> + <declare-styleable name="FragmentAnimation"> + <attr name="fragmentOpenEnterAnimation" format="reference" /> + <attr name="fragmentOpenExitAnimation" format="reference" /> + <attr name="fragmentCloseEnterAnimation" format="reference" /> + <attr name="fragmentCloseExitAnimation" format="reference" /> + </declare-styleable> + <!-- Window animation class attributes. --> <declare-styleable name="WindowAnimation"> <!-- The animation used when a window is being added. --> @@ -3987,6 +4001,10 @@ <attr name="background" /> <!-- Specifies a layout for custom navigation. Overrides navigationMode. --> <attr name="customNavigationLayout" format="reference" /> + <!-- Specifies a fixed height. --> + <attr name="height" /> + <!-- Specifies padding around all sides. --> + <attr name="padding" /> </declare-styleable> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 99263d72c5f9..539f1c010eee 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1315,6 +1315,13 @@ <public type="attr" name="propertyName" /> <public type="attr" name="ordering" /> <public type="attr" name="fragment" /> + <public type="attr" name="windowActionBarOverlay" /> + <public type="attr" name="fragmentOpenEnterAnimation" /> + <public type="attr" name="fragmentOpenExitAnimation" /> + <public type="attr" name="fragmentCloseEnterAnimation" /> + <public type="attr" name="fragmentCloseExitAnimation" /> + <public type="anim" name="animator_fade_in" /> + <public type="anim" name="animator_fade_out" /> <public type="id" name="home" /> <!-- Context menu ID for the "Select text..." menu item to switch to text diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 32a1f0dbed2e..2b279304500d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -807,8 +807,8 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_writeCalendar">add or modify calendar events and send email to guests</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_writeCalendar">Allows an application to add or change the - events on your calendar, which may send email to guests. Malicious applications can use this + <string name="permdesc_writeCalendar">Allows an application to add or change the + events on your calendar, which may send email to guests. Malicious applications can use this to erase or modify your calendar events or to send email to guests.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -1610,7 +1610,7 @@ <!-- Do not translate. WebView User Agent string --> <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>) - AppleWebKit/534.4 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.4</string> + AppleWebKit/534.5 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.5</string> <!-- Do not translate. WebView User Agent targeted content --> <string name="web_user_agent_target_content" translatable="false">"Mobile "</string> @@ -1918,7 +1918,7 @@ <!-- Text shown by list fragment when waiting for data to display. --> <string name="loading">Loading...</string> - + <!-- Default text for a button that can be toggled on and off. --> <string name="capital_on">ON</string> <!-- Default text for a button that can be toggled on and off. --> @@ -1969,23 +1969,23 @@ <!-- Notification text to tell the user that a heavy-weight application is running. --> <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string> - + <!-- Notification details to tell the user that a heavy-weight application is running. --> <string name="heavy_weight_notification_detail">Select to switch to application</string> - + <!-- Title of dialog prompting whether user wants to switch between heavy-weight apps. --> <string name="heavy_weight_switcher_title">Switch applications?</string> - + <!-- Descriptive text for switching to a new heavy-weight application. --> <string name="heavy_weight_switcher_text">Another application is already running that must be stopped before you can start a new one.</string> - + <string name="old_app_action">Return to <xliff:g id="old_app">%1$s</xliff:g></string> <string name="old_app_description">Don\'t start the new application.</string> - + <string name="new_app_action">Start <xliff:g id="old_app">%1$s</xliff:g></string> <string name="new_app_description">Stop the old application without saving.</string> - + <!-- Displayed in the title of the chooser for things to do with text that is to be sent to another application. For example, I can send text through SMS or IM. A dialog with those choices would be shown, @@ -2278,6 +2278,9 @@ <string name="back_button_label">Back</string> <string name="next_button_label">Next</string> + <!-- Optional button to Skip a PreferenceActivity [CHAR LIMIT=20] --> + <string name="skip_button_label">Skip</string> + <!-- Strings for throttling notification --> <!-- Shown when the user is in danger of being throttled --> <string name="throttle_warning_notification_title">High mobile data use</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 993048d95d89..7e52ebd774a2 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -74,6 +74,10 @@ <item name="wallpaperIntraOpenExitAnimation">@anim/wallpaper_intra_open_exit</item> <item name="wallpaperIntraCloseEnterAnimation">@anim/wallpaper_intra_close_enter</item> <item name="wallpaperIntraCloseExitAnimation">@anim/wallpaper_intra_close_exit</item> + <item name="fragmentOpenEnterAnimation">@anim/fragment_open_enter</item> + <item name="fragmentOpenExitAnimation">@anim/fragment_open_exit</item> + <item name="fragmentCloseEnterAnimation">@anim/fragment_close_enter</item> + <item name="fragmentCloseExitAnimation">@anim/fragment_close_exit</item> </style> <!-- Standard animations for a non-full-screen window or activity. --> @@ -279,11 +283,21 @@ <item name="android:button">@android:drawable/btn_check</item> </style> + <style name="Widget.CompoundButton.CheckBox.Inverse"> + <item name="android:background">@android:drawable/btn_check_label_background_light</item> + <item name="android:button">@android:drawable/btn_check_light</item> + </style> + <style name="Widget.CompoundButton.RadioButton"> <item name="android:background">@android:drawable/btn_radio_label_background</item> <item name="android:button">@android:drawable/btn_radio</item> </style> + <style name="Widget.CompoundButton.RadioButton.Inverse"> + <item name="android:background">@android:drawable/btn_radio_label_background_light</item> + <item name="android:button">@android:drawable/btn_radio_light</item> + </style> + <style name="Widget.CompoundButton.Star"> <item name="android:background">@android:drawable/btn_star_label_background</item> <item name="android:button">@android:drawable/btn_star</item> @@ -879,6 +893,8 @@ <item name="android:background">@android:drawable/action_bar_background</item> <item name="android:displayOptions">useLogo</item> <item name="android:divider">@android:drawable/action_bar_divider</item> + <item name="android:height">56dip</item> + <item name="android:padding">3dip</item> </style> <style name="Widget.ActionButton"> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 739912a1a00d..3348b4ee90b7 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -84,9 +84,9 @@ <item name="searchResultListItemHeight">58dip</item> <item name="listDivider">@drawable/divider_horizontal_dark</item> <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator</item> - - <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item> - <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item> + + <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item> + <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item> <item name="expandableListPreferredItemPaddingLeft">40dip</item> <item name="expandableListPreferredChildPaddingLeft"> @@ -160,8 +160,8 @@ <item name="progressBarStyleSmallTitle">@android:style/Widget.ProgressBar.Small.Title</item> <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large</item> <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar.Inverse</item> - <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small.Inverse</item> - <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large.Inverse</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large.Inverse</item> <item name="seekBarStyle">@android:style/Widget.SeekBar</item> <item name="ratingBarStyle">@android:style/Widget.RatingBar</item> <item name="ratingBarStyleIndicator">@android:style/Widget.RatingBar.Indicator</item> @@ -253,18 +253,24 @@ <item name="textCheckMark">@android:drawable/indicator_check_mark_light</item> <item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_dark</item> + <!-- List attributes --> + <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio_light</item> + <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check_light</item> + + <!-- Widget styles --> + <item name="checkboxStyle">@android:style/Widget.CompoundButton.CheckBox.Inverse</item> <item name="gestureOverlayViewStyle">@android:style/Widget.GestureOverlayView.White</item> <item name="expandableListViewStyle">@android:style/Widget.ExpandableListView.White</item> <item name="listViewStyle">@android:style/Widget.ListView.White</item> <item name="listDivider">@drawable/divider_horizontal_bright</item> <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator.White</item> - <item name="progressBarStyle">@android:style/Widget.ProgressBar.Inverse</item> - <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small.Inverse</item> - <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large.Inverse</item> - <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item> - <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item> - <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item> + <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small.Inverse</item> + <item name="progressBarStyleLarge">@android:style/Widget.ProgressBar.Large.Inverse</item> + <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item> + <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item> + <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item> + <item name="radioButtonStyle">@android:style/Widget.CompoundButton.RadioButton.Inverse</item> </style> <!-- Variant of the light theme with no title bar --> diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java index 0fe83e194378..cbd87140da33 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java @@ -17,13 +17,11 @@ package android.bluetooth; import android.app.Instrumentation; -import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.test.InstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; public class BluetoothStressTest extends InstrumentationTestCase { @@ -161,7 +159,6 @@ public class BluetoothStressTest extends InstrumentationTestCase { mContext.unregisterReceiver(mReceiver); } - @LargeTest public void testEnableDisable() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -172,7 +169,6 @@ public class BluetoothStressTest extends InstrumentationTestCase { } } - @LargeTest public void testDiscoverable() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); enable(adapter); @@ -186,7 +182,6 @@ public class BluetoothStressTest extends InstrumentationTestCase { disable(adapter); } - @LargeTest public void testScan() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); enable(adapter); @@ -336,7 +331,7 @@ public class BluetoothStressTest extends InstrumentationTestCase { mReceiver.resetFiredFlags(); if (!adapter.isEnabled()) { - fail("undiscoverable(): bluetooth not enabled"); + fail("undiscoverable() bluetooth not enabled"); } int scanMode = adapter.getScanMode(); @@ -374,7 +369,7 @@ public class BluetoothStressTest extends InstrumentationTestCase { mReceiver.resetFiredFlags(); if (!adapter.isEnabled()) { - fail("startScan(): bluetooth not enabled"); + fail("startScan() bluetooth not enabled"); } if (adapter.isDiscovering()) { @@ -404,7 +399,7 @@ public class BluetoothStressTest extends InstrumentationTestCase { mReceiver.resetFiredFlags(); if (!adapter.isEnabled()) { - fail("stopScan(): bluetooth not enabled"); + fail("stopScan() bluetooth not enabled"); } if (!adapter.isDiscovering()) { diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java index b0cfb7bf4364..ced9310d5d5d 100644 --- a/core/tests/coretests/src/android/util/JsonReaderTest.java +++ b/core/tests/coretests/src/android/util/JsonReaderTest.java @@ -32,6 +32,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } + public void testReadEmptyArray() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[]")); + reader.beginArray(); + assertFalse(reader.hasNext()); + reader.endArray(); + assertEquals(JsonToken.END_DOCUMENT, reader.peek()); + } + public void testReadObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": \"android\", \"b\": \"banana\"}")); @@ -44,6 +52,14 @@ public final class JsonReaderTest extends TestCase { assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } + public void testReadEmptyObject() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{}")); + reader.beginObject(); + assertFalse(reader.hasNext()); + reader.endObject(); + assertEquals(JsonToken.END_DOCUMENT, reader.peek()); + } + public void testSkipObject() throws IOException { JsonReader reader = new JsonReader(new StringReader( "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}")); @@ -412,4 +428,256 @@ public final class JsonReaderTest extends TestCase { } catch (IllegalStateException expected) { } } + + public void testStrictNameValueSeparator() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); + reader.beginObject(); + assertEquals("a", reader.nextName()); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("{\"a\"=>true}")); + reader.beginObject(); + assertEquals("a", reader.nextName()); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientNameValueSeparator() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}")); + reader.setLenient(true); + reader.beginObject(); + assertEquals("a", reader.nextName()); + assertEquals(true, reader.nextBoolean()); + + reader = new JsonReader(new StringReader("{\"a\"=>true}")); + reader.setLenient(true); + reader.beginObject(); + assertEquals("a", reader.nextName()); + assertEquals(true, reader.nextBoolean()); + } + + public void testStrictComments() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); + reader.beginArray(); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("[# comment \n true]")); + reader.beginArray(); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("[/* comment */ true]")); + reader.beginArray(); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientComments() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[// comment \n true]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + + reader = new JsonReader(new StringReader("[# comment \n true]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + + reader = new JsonReader(new StringReader("[/* comment */ true]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + } + + public void testStrictUnquotedNames() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{a:true}")); + reader.beginObject(); + try { + reader.nextName(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientUnquotedNames() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{a:true}")); + reader.setLenient(true); + reader.beginObject(); + assertEquals("a", reader.nextName()); + } + + public void testStrictSingleQuotedNames() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{'a':true}")); + reader.beginObject(); + try { + reader.nextName(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientSingleQuotedNames() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{'a':true}")); + reader.setLenient(true); + reader.beginObject(); + assertEquals("a", reader.nextName()); + } + + public void testStrictUnquotedStrings() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[a]")); + reader.beginArray(); + try { + reader.nextString(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientUnquotedStrings() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[a]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals("a", reader.nextString()); + } + + public void testStrictSingleQuotedStrings() throws IOException { + JsonReader reader = new JsonReader(new StringReader("['a']")); + reader.beginArray(); + try { + reader.nextString(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientSingleQuotedStrings() throws IOException { + JsonReader reader = new JsonReader(new StringReader("['a']")); + reader.setLenient(true); + reader.beginArray(); + assertEquals("a", reader.nextString()); + } + + public void testStrictSemicolonDelimitedArray() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[true;true]")); + reader.beginArray(); + try { + reader.nextBoolean(); + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientSemicolonDelimitedArray() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[true;true]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + assertEquals(true, reader.nextBoolean()); + } + + public void testStrictSemicolonDelimitedNameValuePair() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); + reader.beginObject(); + assertEquals("a", reader.nextName()); + try { + reader.nextBoolean(); + reader.nextName(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientSemicolonDelimitedNameValuePair() throws IOException { + JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}")); + reader.setLenient(true); + reader.beginObject(); + assertEquals("a", reader.nextName()); + assertEquals(true, reader.nextBoolean()); + assertEquals("b", reader.nextName()); + } + + public void testStrictUnnecessaryArraySeparators() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[true,,true]")); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + try { + reader.nextNull(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("[,true]")); + reader.beginArray(); + try { + reader.nextNull(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("[true,]")); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + try { + reader.nextNull(); + fail(); + } catch (IOException expected) { + } + + reader = new JsonReader(new StringReader("[,]")); + reader.beginArray(); + try { + reader.nextNull(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientUnnecessaryArraySeparators() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[true,,true]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + reader.nextNull(); + assertEquals(true, reader.nextBoolean()); + reader.endArray(); + + reader = new JsonReader(new StringReader("[,true]")); + reader.setLenient(true); + reader.beginArray(); + reader.nextNull(); + assertEquals(true, reader.nextBoolean()); + reader.endArray(); + + reader = new JsonReader(new StringReader("[true,]")); + reader.setLenient(true); + reader.beginArray(); + assertEquals(true, reader.nextBoolean()); + reader.nextNull(); + reader.endArray(); + + reader = new JsonReader(new StringReader("[,]")); + reader.setLenient(true); + reader.beginArray(); + reader.nextNull(); + reader.nextNull(); + reader.endArray(); + } } diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java index 0bf7e042a7ac..fa840239d7b8 100644 --- a/core/tests/coretests/src/android/util/JsonWriterTest.java +++ b/core/tests/coretests/src/android/util/JsonWriterTest.java @@ -346,7 +346,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintObject() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); - jsonWriter.setIndentSpaces(3); + jsonWriter.setIndent(" "); jsonWriter.beginObject(); jsonWriter.name("a").value(true); @@ -383,7 +383,7 @@ public final class JsonWriterTest extends TestCase { public void testPrettyPrintArray() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); - jsonWriter.setIndentSpaces(3); + jsonWriter.setIndent(" "); jsonWriter.beginArray(); jsonWriter.value(true); diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 537dd3aeb730..d9ee3ec700a8 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -19,12 +19,16 @@ package android.graphics; import android.os.Parcel; import android.os.Parcelable; import android.util.DisplayMetrics; +import android.util.Finalizers; import java.io.OutputStream; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public final class Bitmap implements Parcelable { /** @@ -55,7 +59,7 @@ public final class Bitmap implements Parcelable { private static volatile Matrix sScaleMatrix; private static volatile int sDefaultDensity = -1; - + /** * For backwards compatibility, allows the app layer to change the default * density when running old apps. @@ -81,8 +85,7 @@ public final class Bitmap implements Parcelable { This can be called from JNI code. */ - private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk, - int density) { + private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk, int density) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } @@ -94,6 +97,13 @@ public final class Bitmap implements Parcelable { if (density >= 0) { mDensity = density; } + + // If the finalizers queue is null, we are running in zygote and the + // bitmap will never be reclaimed, so we don't need to run our native + // destructor + if (Finalizers.getQueue() != null) { + new BitmapFinalizer(this); + } } /** @@ -1016,12 +1026,22 @@ public final class Bitmap implements Parcelable { nativePrepareToDraw(mNativeBitmap); } - @Override - protected void finalize() throws Throwable { - try { + private static class BitmapFinalizer extends Finalizers.ReclaimableReference<Bitmap> { + private static final Set<BitmapFinalizer> sFinalizers = Collections.synchronizedSet( + new HashSet<BitmapFinalizer>()); + + private int mNativeBitmap; + + BitmapFinalizer(Bitmap b) { + super(b, Finalizers.getQueue()); + mNativeBitmap = b.mNativeBitmap; + sFinalizers.add(this); + } + + @Override + public void reclaim() { nativeDestructor(mNativeBitmap); - } finally { - super.finalize(); + sFinalizers.remove(this); } } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 77a1930b5fc3..36a8e574a92c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -42,7 +42,6 @@ public class Canvas { for both to be null. */ private Bitmap mBitmap; // if not null, mGL must be null - private GL mGL; // if not null, mBitmap must be null // optional field set by the caller private DrawFilter mDrawFilter; @@ -106,31 +105,22 @@ public class Canvas { mDensity = bitmap.mDensity; } - /*package*/ Canvas(int nativeCanvas) { + Canvas(int nativeCanvas) { if (nativeCanvas == 0) { throw new IllegalStateException(); } mNativeCanvas = nativeCanvas; mDensity = Bitmap.getDefaultDensity(); } - + /** - * Construct a canvas with the specified gl context. All drawing through - * this canvas will be redirected to OpenGL. Note: some features may not - * be supported in this mode (e.g. some GL implementations may not support - * antialiasing or certain effects like ColorMatrix or certain Xfermodes). - * However, no exception will be thrown in those cases. + * Returns null. * - * <p>The initial target density of the canvas is the same as the initial - * density of bitmaps as per {@link Bitmap#getDensity() Bitmap.getDensity()}. - * - * @deprecated This constructor is not supported and should not be invoked. + * @deprecated This method is not supported and should not be invoked. */ @Deprecated - public Canvas(GL gl) { - mNativeCanvas = initGL(); - mGL = gl; - mDensity = Bitmap.getDefaultDensity(); + protected GL getGL() { + return null; } /** @@ -143,32 +133,9 @@ public class Canvas { * false otherwise. */ public boolean isHardwareAccelerated() { - return mGL != null; - } - - /** - * Return the GL object associated with this canvas, or null if it is not - * backed by GL. - * - * @deprecated This method is not supported and should not be invoked. - */ - @Deprecated - public GL getGL() { - return mGL; - } - - /** - * Call this to free up OpenGL resources that may be cached or allocated - * on behalf of the Canvas. Any subsequent drawing with a GL-backed Canvas - * will have to recreate those resources. - * - * @deprecated This method is not supported and should not be invoked. - */ - @Deprecated - public static void freeGlCaches() { - freeCaches(); + return false; } - + /** * Specify a bitmap for the canvas to draw into. As a side-effect, also * updates the canvas's target density to match that of the bitmap. @@ -182,7 +149,7 @@ public class Canvas { if (!bitmap.isMutable()) { throw new IllegalStateException(); } - if (mGL != null) { + if (isHardwareAccelerated()) { throw new RuntimeException("Can't set a bitmap device on a GL canvas"); } throwIfRecycled(bitmap); @@ -196,16 +163,12 @@ public class Canvas { * Set the viewport dimensions if this canvas is GL based. If it is not, * this method is ignored and no exception is thrown. * - * @param width The width of the viewport - * @param height The height of the viewport + * @param width The width of the viewport + * @param height The height of the viewport * - * @deprecated This method is not supported and should not be invoked. + * @hide */ - @Deprecated public void setViewport(int width, int height) { - if (mGL != null) { - nativeSetViewport(mNativeCanvas, width, height); - } } /** @@ -1591,26 +1554,26 @@ public class Canvas { @Override protected void finalize() throws Throwable { - super.finalize(); - // If the constructor threw an exception before setting mNativeCanvas, the native finalizer - // must not be invoked. - if (mNativeCanvas != 0) { - finalizer(mNativeCanvas); + try { + super.finalize(); + } finally { + // If the constructor threw an exception before setting mNativeCanvas, + // the native finalizer must not be invoked. + if (mNativeCanvas != 0) { + finalizer(mNativeCanvas); + } } } /** - * Free up as much memory as possible from private caches (e.g. fonts, - * images) + * Free up as much memory as possible from private caches (e.g. fonts, images) * - * @hide - for now + * @hide */ public static native void freeCaches(); private static native int initRaster(int nativeBitmapOrZero); - private static native int initGL(); private static native void native_setBitmap(int nativeCanvas, int bitmap); - private static native void nativeSetViewport(int nCanvas, int w, int h); private static native int native_saveLayer(int nativeCanvas, RectF bounds, int paint, int layerFlags); private static native int native_saveLayer(int nativeCanvas, float l, diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index 9b57ea44242c..8d5c913d1b69 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -20,6 +20,14 @@ package android.graphics; an {@link android.graphics.Xfermode} subclass. */ public class ComposeShader extends Shader { + /** + * Hold onto the shaders to avoid GC. + */ + @SuppressWarnings({"UnusedDeclaration"}) + private final Shader mShaderA; + @SuppressWarnings({"UnusedDeclaration"}) + private final Shader mShaderB; + /** Create a new compose shader, given shaders A, B, and a combining mode. When the mode is applied, it will be given the result from shader A as its "dst", and the result of from shader B as its "src". @@ -29,10 +37,18 @@ public class ComposeShader extends Shader { is null, then SRC_OVER is assumed. */ public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) { + mShaderA = shaderA; + mShaderB = shaderB; native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance, (mode != null) ? mode.native_instance : 0); - native_shader = nativePostCreate1(native_instance, shaderA.native_shader, - shaderB.native_shader, (mode != null) ? mode.native_instance : 0); + if (mode instanceof PorterDuffXfermode) { + PorterDuff.Mode pdMode = ((PorterDuffXfermode) mode).mode; + native_shader = nativePostCreate2(native_instance, shaderA.native_shader, + shaderB.native_shader, pdMode != null ? pdMode.nativeInt : 0); + } else { + native_shader = nativePostCreate1(native_instance, shaderA.native_shader, + shaderB.native_shader, mode != null ? mode.native_instance : 0); + } } /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. @@ -43,6 +59,8 @@ public class ComposeShader extends Shader { @param mode The PorterDuff mode that combines the colors from the two shaders. */ public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) { + mShaderA = shaderA; + mShaderB = shaderB; native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance, mode.nativeInt); native_shader = nativePostCreate2(native_instance, shaderA.native_shader, diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index cb2c6a294f38..c3416a01f571 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -16,6 +16,8 @@ package android.graphics; +import android.view.HardwareRenderer; + /** * The Path class encapsulates compound (multiple contour) geometric paths * consisting of straight line segments, quadratic curves, and cubic curves. @@ -24,12 +26,27 @@ package android.graphics; * text on a path. */ public class Path { + /** + * @hide + */ + public final int mNativePath; + + /** + * @hide + */ + public boolean isSimplePath = true; + /** + * @hide + */ + public Region rects; + private boolean mDetectSimplePaths; /** * Create an empty path */ public Path() { mNativePath = init1(); + mDetectSimplePaths = HardwareRenderer.isAvailable(); } /** @@ -43,6 +60,7 @@ public class Path { valNative = src.mNativePath; } mNativePath = init2(valNative); + mDetectSimplePaths = HardwareRenderer.isAvailable(); } /** @@ -50,6 +68,10 @@ public class Path { * This does NOT change the fill-type setting. */ public void reset() { + isSimplePath = true; + if (mDetectSimplePaths) { + if (rects != null) rects.setEmpty(); + } native_reset(mNativePath); } @@ -58,6 +80,10 @@ public class Path { * keeps the internal data structure for faster reuse. */ public void rewind() { + isSimplePath = true; + if (mDetectSimplePaths) { + if (rects != null) rects.setEmpty(); + } native_rewind(mNativePath); } @@ -65,6 +91,7 @@ public class Path { */ public void set(Path src) { if (this != src) { + isSimplePath = src.isSimplePath; native_set(mNativePath, src.mNativePath); } } @@ -160,6 +187,7 @@ public class Path { * @param bounds Returns the computed bounds of the path's control points. * @param exact This parameter is no longer used. */ + @SuppressWarnings({"UnusedDeclaration"}) public void computeBounds(RectF bounds, boolean exact) { native_computeBounds(mNativePath, bounds); } @@ -236,6 +264,7 @@ public class Path { * @param y2 The y-coordinate of the end point on a quadratic curve */ public void quadTo(float x1, float y1, float x2, float y2) { + isSimplePath = false; native_quadTo(mNativePath, x1, y1, x2, y2); } @@ -254,6 +283,7 @@ public class Path { * this contour, for the end point of a quadratic curve */ public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { + isSimplePath = false; native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2); } @@ -271,6 +301,7 @@ public class Path { */ public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { + isSimplePath = false; native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } @@ -281,6 +312,7 @@ public class Path { */ public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { + isSimplePath = false; native_rCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } @@ -299,6 +331,7 @@ public class Path { */ public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { + isSimplePath = false; native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo); } @@ -314,6 +347,7 @@ public class Path { * @param sweepAngle Sweep angle (in degrees) measured clockwise */ public void arcTo(RectF oval, float startAngle, float sweepAngle) { + isSimplePath = false; native_arcTo(mNativePath, oval, startAngle, sweepAngle, false); } @@ -322,6 +356,7 @@ public class Path { * first point of the contour, a line segment is automatically added. */ public void close() { + isSimplePath = false; native_close(mNativePath); } @@ -351,6 +386,11 @@ public class Path { if (rect == null) { throw new NullPointerException("need rect parameter"); } + if (mDetectSimplePaths) { + if (rects == null) rects = new Region(); + rects.op((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom, + Region.Op.UNION); + } native_addRect(mNativePath, rect, dir.nativeInt); } @@ -363,8 +403,11 @@ public class Path { * @param bottom The bottom of a rectangle to add to the path * @param dir The direction to wind the rectangle's contour */ - public void addRect(float left, float top, float right, float bottom, - Direction dir) { + public void addRect(float left, float top, float right, float bottom, Direction dir) { + if (mDetectSimplePaths) { + if (rects == null) rects = new Region(); + rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION); + } native_addRect(mNativePath, left, top, right, bottom, dir.nativeInt); } @@ -378,6 +421,7 @@ public class Path { if (oval == null) { throw new NullPointerException("need oval parameter"); } + isSimplePath = false; native_addOval(mNativePath, oval, dir.nativeInt); } @@ -390,6 +434,7 @@ public class Path { * @param dir The direction to wind the circle's contour */ public void addCircle(float x, float y, float radius, Direction dir) { + isSimplePath = false; native_addCircle(mNativePath, x, y, radius, dir.nativeInt); } @@ -404,6 +449,7 @@ public class Path { if (oval == null) { throw new NullPointerException("need oval parameter"); } + isSimplePath = false; native_addArc(mNativePath, oval, startAngle, sweepAngle); } @@ -419,6 +465,7 @@ public class Path { if (rect == null) { throw new NullPointerException("need rect parameter"); } + isSimplePath = false; native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt); } @@ -438,6 +485,7 @@ public class Path { if (radii.length < 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } + isSimplePath = false; native_addRoundRect(mNativePath, rect, radii, dir.nativeInt); } @@ -448,6 +496,7 @@ public class Path { * @param dx The amount to translate the path in X as it is added */ public void addPath(Path src, float dx, float dy) { + isSimplePath = false; native_addPath(mNativePath, src.mNativePath, dx, dy); } @@ -457,6 +506,7 @@ public class Path { * @param src The path that is appended to the current path */ public void addPath(Path src) { + isSimplePath = false; native_addPath(mNativePath, src.mNativePath); } @@ -466,6 +516,7 @@ public class Path { * @param src The path to add as a new contour */ public void addPath(Path src, Matrix matrix) { + if (!src.isSimplePath) isSimplePath = false; native_addPath(mNativePath, src.mNativePath, matrix.native_instance); } @@ -502,6 +553,7 @@ public class Path { * @param dy The new Y coordinate for the last point */ public void setLastPoint(float dx, float dy) { + isSimplePath = false; native_setLastPoint(mNativePath, dx, dy); } @@ -537,8 +589,8 @@ public class Path { super.finalize(); } } - - /*package*/ final int ni() { + + final int ni() { return mNativePath; } @@ -592,9 +644,4 @@ public class Path { int dst_path); private static native void native_transform(int nPath, int matrix); private static native void finalizer(int nPath); - - /** - * @hide - */ - public final int mNativePath; } diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java index cb127fdaa3fb..6ba064cc05ef 100644 --- a/graphics/java/android/graphics/PorterDuffXfermode.java +++ b/graphics/java/android/graphics/PorterDuffXfermode.java @@ -18,11 +18,17 @@ package android.graphics; public class PorterDuffXfermode extends Xfermode { /** + * @hide + */ + public final PorterDuff.Mode mode; + + /** * Create an xfermode that uses the specified porter-duff mode. * * @param mode The porter-duff mode that is applied */ public PorterDuffXfermode(PorterDuff.Mode mode) { + this.mode = mode; native_instance = nativeCreateXfermode(mode.nativeInt); } diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index 489ef83811f5..e5408068e64f 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -20,6 +20,10 @@ import android.os.Parcel; import android.os.Parcelable; public class Region implements Parcelable { + /** + * @hide + */ + public final int mNativeRegion; // the native values for these must match up with the enum in SkRegion.h public enum Op { @@ -329,10 +333,14 @@ public class Region implements Parcelable { } protected void finalize() throws Throwable { - nativeDestructor(mNativeRegion); + try { + nativeDestructor(mNativeRegion); + } finally { + super.finalize(); + } } - /*package*/ Region(int ni) { + Region(int ni) { if (ni == 0) { throw new RuntimeException(); } @@ -345,7 +353,7 @@ public class Region implements Parcelable { this(ni); } - /*package*/ final int ni() { + final int ni() { return mNativeRegion; } @@ -374,6 +382,4 @@ public class Region implements Parcelable { Parcel p); private static native boolean nativeEquals(int native_r1, int native_r2); - - private final int mNativeRegion; } diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index 42c410e58b3a..2467bdc29bcc 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -31,7 +31,11 @@ package android.graphics; public class Xfermode { protected void finalize() throws Throwable { - finalizer(native_instance); + try { + finalizer(native_instance); + } finally { + super.finalize(); + } } private static native void finalizer(int native_instance); diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 33ecbea910ed..88f6d43c834c 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -313,18 +313,16 @@ public class GradientDrawable extends Drawable { case RECTANGLE: if (st.mRadiusArray != null) { mPath.reset(); - mPath.addRoundRect(mRect, st.mRadiusArray, - Path.Direction.CW); + mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); canvas.drawPath(mPath, mFillPaint); if (haveStroke) { canvas.drawPath(mPath, mStrokePaint); } - } - else { + } else if (st.mRadius > 0.0f) { // since the caller is only giving us 1 value, we will force // it to be square if the rect is too small in one dimension // to show it. If we did nothing, Skia would clamp the rad - // independently along each axis, giving us a thin ellips + // independently along each axis, giving us a thin ellipse // if the rect were very wide but not very tall float rad = st.mRadius; float r = Math.min(mRect.width(), mRect.height()) * 0.5f; @@ -335,6 +333,11 @@ public class GradientDrawable extends Drawable { if (haveStroke) { canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); } + } else { + canvas.drawRect(mRect, mFillPaint); + if (haveStroke) { + canvas.drawRect(mRect, mStrokePaint); + } } break; case OVAL: diff --git a/graphics/java/android/renderscript/Matrix4f.java b/graphics/java/android/renderscript/Matrix4f.java index e854cd9dd6b7..5ffc21a58939 100644 --- a/graphics/java/android/renderscript/Matrix4f.java +++ b/graphics/java/android/renderscript/Matrix4f.java @@ -179,6 +179,76 @@ public class Matrix4f { tmp.loadTranslate(x, y, z); multiply(tmp); } + private float computeCofactor(int i, int j) { + int c0 = (i+1) % 4; + int c1 = (i+2) % 4; + int c2 = (i+3) % 4; + int r0 = (j+1) % 4; + int r1 = (j+2) % 4; + int r2 = (j+3) % 4; + + float minor = (mMat[c0 + 4*r0] * (mMat[c1 + 4*r1] * mMat[c2 + 4*r2] - + mMat[c1 + 4*r2] * mMat[c2 + 4*r1])) + - (mMat[c0 + 4*r1] * (mMat[c1 + 4*r0] * mMat[c2 + 4*r2] - + mMat[c1 + 4*r2] * mMat[c2 + 4*r0])) + + (mMat[c0 + 4*r2] * (mMat[c1 + 4*r0] * mMat[c2 + 4*r1] - + mMat[c1 + 4*r1] * mMat[c2 + 4*r0])); + + float cofactor = ((i+j) & 1) != 0 ? -minor : minor; + return cofactor; + } + + public boolean inverse() { + + Matrix4f result = new Matrix4f(); + + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + result.mMat[4*i + j] = computeCofactor(i, j); + } + } + + // Dot product of 0th column of source and 0th row of result + float det = mMat[0]*result.mMat[0] + mMat[4]*result.mMat[1] + + mMat[8]*result.mMat[2] + mMat[12]*result.mMat[3]; + + if (Math.abs(det) < 1e-6) { + return false; + } + + det = 1.0f / det; + for (int i = 0; i < 16; ++i) { + mMat[i] = result.mMat[i] * det; + } + + return true; + } + + public boolean inverseTranspose() { + + Matrix4f result = new Matrix4f(); + + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + result.mMat[4*j + i] = computeCofactor(i, j); + } + } + + float det = mMat[0]*result.mMat[0] + mMat[4]*result.mMat[4] + + mMat[8]*result.mMat[8] + mMat[12]*result.mMat[12]; + + if (Math.abs(det) < 1e-6) { + return false; + } + + det = 1.0f / det; + for (int i = 0; i < 16; ++i) { + mMat[i] = result.mMat[i] * det; + } + + return true; + } + public void transpose() { for(int i = 0; i < 3; ++i) { for(int j = i + 1; j < 4; ++j) { diff --git a/include/android_runtime/android_content_res_Configuration.h b/include/android_runtime/android_content_res_Configuration.h new file mode 100644 index 000000000000..2f5a98249cb7 --- /dev/null +++ b/include/android_runtime/android_content_res_Configuration.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_CONTENT_RES_CONFIGURATION_H +#define _ANDROID_CONTENT_RES_CONFIGURATION_H + +#include <utils/ResourceTypes.h> +#include <android/configuration.h> + +#include "jni.h" + +struct AConfiguration : android::ResTable_config { +}; + +namespace android { + +extern void android_Configuration_getFromJava( + JNIEnv* env, jobject clazz, struct AConfiguration* out); + +} // namespace android + + +#endif // _ANDROID_CONTENT_RES_CONFIGURATION_H diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h index 9cddab2b358d..92ce068a40d5 100644 --- a/include/media/stagefright/MediaDefs.h +++ b/include/media/stagefright/MediaDefs.h @@ -34,6 +34,8 @@ extern const char *MEDIA_MIMETYPE_AUDIO_MPEG; extern const char *MEDIA_MIMETYPE_AUDIO_AAC; extern const char *MEDIA_MIMETYPE_AUDIO_QCELP; extern const char *MEDIA_MIMETYPE_AUDIO_VORBIS; +extern const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW; +extern const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW; extern const char *MEDIA_MIMETYPE_AUDIO_RAW; extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4; diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h index f1622316fcc8..71c6c517692d 100644 --- a/include/ui/InputReader.h +++ b/include/ui/InputReader.h @@ -454,6 +454,8 @@ public: virtual void reset(); virtual void process(const RawEvent* rawEvent); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + private: // Amount that trackball needs to move in order to generate a key event. static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6; diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h index 97694ff0a997..9e2bf37e8fa4 100644 --- a/include/utils/AssetManager.h +++ b/include/utils/AssetManager.h @@ -129,6 +129,8 @@ public: */ void setConfiguration(const ResTable_config& config, const char* locale = NULL); + void getConfiguration(ResTable_config* outConfig) const; + typedef Asset::AccessMode AccessMode; // typing shortcut /* diff --git a/include/utils/ObbFile.h b/include/utils/ObbFile.h index 075927cdfa2e..d2ca82eb55c7 100644 --- a/include/utils/ObbFile.h +++ b/include/utils/ObbFile.h @@ -35,6 +35,8 @@ public: bool readFrom(int fd); bool writeTo(const char* filename); bool writeTo(int fd); + bool removeFrom(const char* filename); + bool removeFrom(int fd); const char* getFileName() const { return mFileName; @@ -78,6 +80,8 @@ private: size_t mFileSize; + size_t mFooterStart; + unsigned char* mReadBuf; bool parseObbFile(int fd); diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index c7d9ff1dd678..da86da41006d 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -31,6 +31,8 @@ #include <stdint.h> #include <sys/types.h> +#include <android/configuration.h> + namespace android { /** ******************************************************************** @@ -822,25 +824,25 @@ struct ResTable_config }; enum { - ORIENTATION_ANY = 0x0000, - ORIENTATION_PORT = 0x0001, - ORIENTATION_LAND = 0x0002, - ORIENTATION_SQUARE = 0x0003, + ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY, + ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT, + ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND, + ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE, }; enum { - TOUCHSCREEN_ANY = 0x0000, - TOUCHSCREEN_NOTOUCH = 0x0001, - TOUCHSCREEN_STYLUS = 0x0002, - TOUCHSCREEN_FINGER = 0x0003, + TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY, + TOUCHSCREEN_NOTOUCH = ACONFIGURATION_TOUCHSCREEN_NOTOUCH, + TOUCHSCREEN_STYLUS = ACONFIGURATION_TOUCHSCREEN_STYLUS, + TOUCHSCREEN_FINGER = ACONFIGURATION_TOUCHSCREEN_FINGER, }; enum { - DENSITY_DEFAULT = 0, - DENSITY_LOW = 120, - DENSITY_MEDIUM = 160, - DENSITY_HIGH = 240, - DENSITY_NONE = 0xffff + DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT, + DENSITY_LOW = ACONFIGURATION_DENSITY_LOW, + DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM, + DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH, + DENSITY_NONE = ACONFIGURATION_DENSITY_NONE }; union { @@ -853,33 +855,34 @@ struct ResTable_config }; enum { - KEYBOARD_ANY = 0x0000, - KEYBOARD_NOKEYS = 0x0001, - KEYBOARD_QWERTY = 0x0002, - KEYBOARD_12KEY = 0x0003, + KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY, + KEYBOARD_NOKEYS = ACONFIGURATION_KEYBOARD_NOKEYS, + KEYBOARD_QWERTY = ACONFIGURATION_KEYBOARD_QWERTY, + KEYBOARD_12KEY = ACONFIGURATION_KEYBOARD_12KEY, }; enum { - NAVIGATION_ANY = 0x0000, - NAVIGATION_NONAV = 0x0001, - NAVIGATION_DPAD = 0x0002, - NAVIGATION_TRACKBALL = 0x0003, - NAVIGATION_WHEEL = 0x0004, + NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY, + NAVIGATION_NONAV = ACONFIGURATION_NAVIGATION_NONAV, + NAVIGATION_DPAD = ACONFIGURATION_NAVIGATION_DPAD, + NAVIGATION_TRACKBALL = ACONFIGURATION_NAVIGATION_TRACKBALL, + NAVIGATION_WHEEL = ACONFIGURATION_NAVIGATION_WHEEL, }; enum { MASK_KEYSHIDDEN = 0x0003, - KEYSHIDDEN_ANY = 0x0000, - KEYSHIDDEN_NO = 0x0001, - KEYSHIDDEN_YES = 0x0002, - KEYSHIDDEN_SOFT = 0x0003, + KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY, + KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO, + KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES, + KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT, }; enum { MASK_NAVHIDDEN = 0x000c, - NAVHIDDEN_ANY = 0x0000, - NAVHIDDEN_NO = 0x0004, - NAVHIDDEN_YES = 0x0008, + SHIFT_NAVHIDDEN = 2, + NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN, + NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN, + NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN, }; union { @@ -929,32 +932,34 @@ struct ResTable_config enum { // screenLayout bits for screen size class. MASK_SCREENSIZE = 0x0f, - SCREENSIZE_ANY = 0x00, - SCREENSIZE_SMALL = 0x01, - SCREENSIZE_NORMAL = 0x02, - SCREENSIZE_LARGE = 0x03, - SCREENSIZE_XLARGE = 0x04, + SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY, + SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL, + SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL, + SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE, + SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE, // screenLayout bits for wide/long screen variation. MASK_SCREENLONG = 0x30, - SCREENLONG_ANY = 0x00, - SCREENLONG_NO = 0x10, - SCREENLONG_YES = 0x20, + SHIFT_SCREENLONG = 4, + SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG, + SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG, + SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG, }; enum { // uiMode bits for the mode type. MASK_UI_MODE_TYPE = 0x0f, - UI_MODE_TYPE_ANY = 0x00, - UI_MODE_TYPE_NORMAL = 0x01, - UI_MODE_TYPE_DESK = 0x02, - UI_MODE_TYPE_CAR = 0x03, + UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY, + UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL, + UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK, + UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR, // uiMode bits for the night switch. MASK_UI_MODE_NIGHT = 0x30, - UI_MODE_NIGHT_ANY = 0x00, - UI_MODE_NIGHT_NO = 0x10, - UI_MODE_NIGHT_YES = 0x20, + SHIFT_UI_MODE_NIGHT = 4, + UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT, + UI_MODE_NIGHT_NO = ACONFIGURATION_UI_MODE_NIGHT_NO << SHIFT_UI_MODE_NIGHT, + UI_MODE_NIGHT_YES = ACONFIGURATION_UI_MODE_NIGHT_YES << SHIFT_UI_MODE_NIGHT, }; union { @@ -1023,19 +1028,19 @@ struct ResTable_config // match the corresponding ones in android.content.pm.ActivityInfo and // attrs_manifest.xml. enum { - CONFIG_MCC = 0x0001, - CONFIG_MNC = 0x0002, - CONFIG_LOCALE = 0x0004, - CONFIG_TOUCHSCREEN = 0x0008, - CONFIG_KEYBOARD = 0x0010, - CONFIG_KEYBOARD_HIDDEN = 0x0020, - CONFIG_NAVIGATION = 0x0040, - CONFIG_ORIENTATION = 0x0080, - CONFIG_DENSITY = 0x0100, - CONFIG_SCREEN_SIZE = 0x0200, - CONFIG_VERSION = 0x0400, - CONFIG_SCREEN_LAYOUT = 0x0800, - CONFIG_UI_MODE = 0x1000 + CONFIG_MCC = ACONFIGURATION_MCC, + CONFIG_MNC = ACONFIGURATION_MCC, + CONFIG_LOCALE = ACONFIGURATION_LOCALE, + CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN, + CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD, + CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN, + CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION, + CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION, + CONFIG_DENSITY = ACONFIGURATION_DENSITY, + CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE, + CONFIG_VERSION = ACONFIGURATION_VERSION, + CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT, + CONFIG_UI_MODE = ACONFIGURATION_UI_MODE }; // Compare two configuration, returning CONFIG_* flags set for each value diff --git a/include/utils/String8.h b/include/utils/String8.h index 0b18fe3d22a8..ef0b51a443ec 100644 --- a/include/utils/String8.h +++ b/include/utils/String8.h @@ -171,7 +171,8 @@ public: status_t append(const char* other); status_t append(const char* other, size_t numChars); - status_t appendFormat(const char* fmt, ...); + status_t appendFormat(const char* fmt, ...) + __attribute__((format (printf, 2, 3))); // Note that this function takes O(N) time to calculate the value. // No cache value is stored. @@ -374,7 +375,7 @@ inline String8& String8::operator+=(const String8& other) inline String8 String8::operator+(const String8& other) const { - String8 tmp; + String8 tmp(*this); tmp += other; return tmp; } @@ -387,7 +388,7 @@ inline String8& String8::operator+=(const char* other) inline String8 String8::operator+(const char* other) const { - String8 tmp; + String8 tmp(*this); tmp += other; return tmp; } diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 04449645cbeb..1efe6b55384c 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -1,33 +1,41 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - FontRenderer.cpp \ - GradientCache.cpp \ - LayerCache.cpp \ - Matrix.cpp \ - OpenGLRenderer.cpp \ - Patch.cpp \ - PatchCache.cpp \ - PathCache.cpp \ - Program.cpp \ - ProgramCache.cpp \ - SkiaColorFilter.cpp \ - SkiaShader.cpp \ - TextureCache.cpp +# Only build libhwui when USE_OPENGL_RENDERER is +# defined in the current device/board configuration +ifeq ($(USE_OPENGL_RENDERER),true) + LOCAL_SRC_FILES:= \ + FontRenderer.cpp \ + GradientCache.cpp \ + LayerCache.cpp \ + Matrix.cpp \ + OpenGLRenderer.cpp \ + Patch.cpp \ + PatchCache.cpp \ + PathCache.cpp \ + Program.cpp \ + ProgramCache.cpp \ + SkiaColorFilter.cpp \ + SkiaShader.cpp \ + TextureCache.cpp + + LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + $(LOCAL_PATH)/../../include/utils \ + external/skia/include/core \ + external/skia/include/effects \ + external/skia/include/images \ + external/skia/src/ports \ + external/skia/include/utils -LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) \ - $(LOCAL_PATH)/../../include/utils \ - external/skia/include/core \ - external/skia/include/effects \ - external/skia/include/images \ - external/skia/src/ports \ - external/skia/include/utils + LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER + LOCAL_MODULE_CLASS := SHARED_LIBRARIES + LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia + LOCAL_MODULE := libhwui + LOCAL_MODULE_TAGS := optional + LOCAL_PRELINK_MODULE := false + + include $(BUILD_SHARED_LIBRARY) -LOCAL_MODULE_CLASS := SHARED_LIBRARIES -LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia -LOCAL_MODULE := libhwui -LOCAL_PRELINK_MODULE := false - -include $(BUILD_SHARED_LIBRARY) + include $(call all-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index dbea1147b87d..e807aba12a9c 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -69,16 +69,16 @@ void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds int width = (int) glyph->mBitmapWidth; int height = (int) glyph->mBitmapHeight; - if(bounds->bottom > nPenY) { + if (bounds->bottom > nPenY) { bounds->bottom = nPenY; } - if(bounds->left > nPenX) { + if (bounds->left > nPenX) { bounds->left = nPenX; } - if(bounds->right < nPenX + width) { + if (bounds->right < nPenX + width) { bounds->right = nPenX + width; } - if(bounds->top < nPenY + height) { + if (bounds->top < nPenY + height) { bounds->top = nPenY + height; } } @@ -102,7 +102,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) { } void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { + uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { int nPenX = x + glyph->mBitmapLeft; int nPenY = y + glyph->mBitmapTop; @@ -116,7 +116,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y, int32_t bX = 0, bY = 0; for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { - if(bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) { + if (bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) { LOGE("Skipping invalid index"); continue; } @@ -143,22 +143,19 @@ Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) { } void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { - if(bitmap != NULL && bitmapW > 0 && bitmapH > 0) { - renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, - bitmap, bitmapW, bitmapH, NULL); - } - else { - renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, - NULL, 0, 0, NULL); + int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { + if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) { + renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap, + bitmapW, bitmapH, NULL); + } else { + renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 0, 0, NULL); } } void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, Rect *bounds) { - if(bounds == NULL) { + int numGlyphs, Rect *bounds) { + if (bounds == NULL) { LOGE("No return rectangle provided to measure text"); return; } @@ -167,9 +164,8 @@ void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t } void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len, - int numGlyphs, int x, int y, RenderMode mode, - uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, - Rect *bounds) { + int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, + uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) { if (numGlyphs == 0 || text == NULL || len == 0) { return; } @@ -185,7 +181,7 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t while (glyphsLeft > 0) { int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text); - // Reached the end of the string or encountered + // Reached the end of the string if (utfChar < 0) { break; } @@ -292,6 +288,9 @@ FontRenderer::FontRenderer() { mCurrentQuadIndex = 0; mTextureId = 0; + mTextMeshPtr = NULL; + mTextTexture = NULL; + mIndexBufferID = 0; mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH; @@ -319,10 +318,12 @@ FontRenderer::~FontRenderer() { } mCacheLines.clear(); - delete[] mTextMeshPtr; - delete[] mTextTexture; + if (mInitialized) { + delete[] mTextMeshPtr; + delete[] mTextTexture; + } - if(mTextureId) { + if (mTextureId) { glDeleteTextures(1, &mTextureId); } @@ -417,7 +418,7 @@ void FontRenderer::initTextTexture() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Initialize texture dimentions glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, 0); + GL_ALPHA, GL_UNSIGNED_BYTE, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -522,7 +523,6 @@ void FontRenderer::checkTextureUpdate() { } void FontRenderer::issueDrawCommand() { - checkTextureUpdate(); float* vtx = mTextMeshPtr; diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index aeda416f24c2..59fa0a7badcd 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -80,8 +80,7 @@ void GradientCache::operator()(SkShader*& shader, Texture*& texture) { /////////////////////////////////////////////////////////////////////////////// Texture* GradientCache::get(SkShader* shader) { - Texture* texture = mCache.get(shader); - return texture; + return mCache.get(shader); } void GradientCache::remove(SkShader* shader) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index db8c8637aa67..3c1fe2a8090a 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -160,7 +160,9 @@ OpenGLRenderer::~OpenGLRenderer() { mTextureCache.clear(); mLayerCache.clear(); mGradientCache.clear(); + mPathCache.clear(); mPatchCache.clear(); + mProgramCache.clear(); } /////////////////////////////////////////////////////////////////////////////// @@ -410,8 +412,14 @@ const Rect& OpenGLRenderer::getClipBounds() { } bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { - Rect r(left, top, right, bottom); - mSnapshot->transform.mapRect(r); + SkRect sr; + sr.set(left, top, right, bottom); + + SkMatrix m; + mSnapshot->transform.copyTo(m); + m.mapRect(&sr); + + Rect r(sr.fLeft, sr.fTop, sr.fRight, sr.fBottom); return !mSnapshot->clipRect.intersects(r); } @@ -435,7 +443,9 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, const S return; } + glActiveTexture(GL_TEXTURE0); const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; const AutoTexture autoCleanup(texture); drawTextureRect(left, top, right, bottom, texture, paint); @@ -450,7 +460,9 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const return; } + glActiveTexture(GL_TEXTURE0); const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; const AutoTexture autoCleanup(texture); drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint); @@ -464,7 +476,9 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, return; } + glActiveTexture(GL_TEXTURE0); const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; const AutoTexture autoCleanup(texture); const float width = texture->width; @@ -488,7 +502,9 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, return; } + glActiveTexture(GL_TEXTURE0); const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; const AutoTexture autoCleanup(texture); int alpha; @@ -534,13 +550,17 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, drawColorRect(left, top, right, bottom, color, mode); } +#define kStdStrikeThru_Offset (-6.0f / 21.0f) +#define kStdUnderline_Offset (1.0f / 9.0f) +#define kStdUnderline_Thickness (1.0f / 18.0f) + void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint) { if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) { return; } - float length; + float length = -1.0f; switch (paint->getTextAlign()) { case SkPaint::kCenter_Align: length = paint->measureText(text, bytesCount); @@ -603,13 +623,56 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, mColorFilter->setupProgram(mCurrentProgram); } - // TODO: Implement scale properly const Rect& clip = mSnapshot->getLocalClip(); mFontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize()); mFontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableVertexAttribArray(texCoordsSlot); + + // Handle underline and strike-through + uint32_t flags = paint->getFlags(); + if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + float underlineWidth = length; + // If length is > 0.0f, we already measured the text for the text alignment + if (length <= 0.0f) { + underlineWidth = paint->measureText(text, bytesCount); + } + + float offsetX = 0; + switch (paint->getTextAlign()) { + case SkPaint::kCenter_Align: + offsetX = underlineWidth * 0.5f; + break; + case SkPaint::kRight_Align: + offsetX = underlineWidth; + break; + default: + break; + } + + if (underlineWidth > 0.0f) { + float textSize = paint->getTextSize(); + float height = textSize * kStdUnderline_Thickness; + + float left = x - offsetX; + float top = 0.0f; + float right = left + underlineWidth; + float bottom = 0.0f; + + if (flags & SkPaint::kUnderlineText_Flag) { + top = y + textSize * kStdUnderline_Offset; + bottom = top + height; + drawRect(left, top, right, bottom, paint); + } + + if (flags & SkPaint::kStrikeThruText_Flag) { + top = y + textSize * kStdStrikeThru_Offset; + bottom = top + height; + drawRect(left, top, right, bottom, paint); + } + } + } } void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { @@ -617,6 +680,7 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { glActiveTexture(gTextureUnits[textureUnit]); const PathTexture* texture = mPathCache.get(path, paint); + if (!texture) return; const AutoTexture autoCleanup(texture); int alpha; diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index fa6ea2591cc8..9a22dc0a41e0 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -34,6 +34,10 @@ PathCache::PathCache(uint32_t maxByteSize): mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity), mSize(0), mMaxSize(maxByteSize) { mCache.setOnEntryRemovedListener(this); + + GLint maxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + mMaxTextureSize = maxTextureSize; } PathCache::~PathCache() { @@ -94,9 +98,18 @@ PathTexture* PathCache::get(SkPath* path, SkPaint* paint) { PathTexture* PathCache::addTexture(const PathCacheEntry& entry, const SkPath *path, const SkPaint* paint) { const SkRect& bounds = path->getBounds(); + + const float pathWidth = bounds.width(); + const float pathHeight = bounds.height(); + + if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) { + LOGW("Path too large to be rendered into a texture"); + return NULL; + } + const float offset = entry.strokeWidth * 1.5f; - const uint32_t width = uint32_t(bounds.width() + offset * 2.0 + 0.5); - const uint32_t height = uint32_t(bounds.height() + offset * 2.0 + 0.5); + const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5); + const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5); const uint32_t size = width * height; // Don't even try to cache a bitmap that's bigger than the cache @@ -149,7 +162,8 @@ void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { glGenTextures(1, &texture->id); glBindTexture(GL_TEXTURE_2D, texture->id); - glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel()); + // Textures are Alpha8 + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); texture->blend = true; glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap.rowBytesAsPixels(), texture->height, 0, diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index 206fb49ca4e7..d09789f03763 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -139,6 +139,7 @@ private: uint32_t mSize; uint32_t mMaxSize; + GLuint mMaxTextureSize; }; // class PathCache }; // namespace uirenderer diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index dbae38ecb185..6528d9150110 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -51,6 +51,8 @@ Program::Program(const char* vertex, const char* fragment) { LOGE("Error while linking shaders: %s", log); delete log; } + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); glDeleteProgram(id); } diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 8a97b4cc3ae0..39fe85a740e1 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -137,7 +137,7 @@ const char* gFS_Footer = // PorterDuff snippets /////////////////////////////////////////////////////////////////////////////// -const char* gPorterDuff[12] = { +const char* gBlendOps[18] = { // Clear "return vec4(0.0, 0.0, 0.0, 0.0);\n", // Src @@ -161,8 +161,26 @@ const char* gPorterDuff[12] = { // DstAtop "return vec4(dst.rgb * src.a + (1.0 - dst.a) * src.rgb, src.a);\n", // Xor - "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, 1.0, " + "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, " "src.a + dst.a - 2.0 * src.a * dst.a);\n", + // Add + "return min(src + dst, 1.0);\n", + // Multiply + "return src * dst;\n", + // Screen + "return src + dst - src * dst;\n", + // Overlay + "return clamp(vec4(mix(" + "2.0 * src.rgb * dst.rgb + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), " + "src.a * dst.a - 2.0 * (dst.a - dst.rgb) * (src.a - src.rgb) + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), " + "step(dst.a, 2.0 * dst.rgb)), " + "src.a + dst.a - src.a * dst.a), 0.0, 1.0);\n", + // Darken + "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + " + "min(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n", + // Lighten + "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + " + "max(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n", }; /////////////////////////////////////////////////////////////////////////////// @@ -292,10 +310,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Generate required functions if (description.hasGradient && description.hasBitmap) { - generatePorterDuffBlend(shader, "blendShaders", description.shadersMode); + generateBlend(shader, "blendShaders", description.shadersMode); } if (description.colorOp == ProgramDescription::kColorBlend) { - generatePorterDuffBlend(shader, "blendColors", description.colorMode); + generateBlend(shader, "blendColors", description.colorMode); } if (description.isBitmapNpot) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); @@ -354,13 +372,12 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti return shader; } -void ProgramCache::generatePorterDuffBlend(String8& shader, const char* name, - SkXfermode::Mode mode) { +void ProgramCache::generateBlend(String8& shader, const char* name, SkXfermode::Mode mode) { shader.append("\nvec4 "); shader.append(name); shader.append("(vec4 src, vec4 dst) {\n"); shader.append(" "); - shader.append(gPorterDuff[mode]); + shader.append(gBlendOps[mode]); shader.append("}\n"); } @@ -376,6 +393,9 @@ void ProgramCache::generateTextureWrap(String8& shader, GLenum wrapS, GLenum wra } shader.append(" return vec2("); switch (wrapS) { + case GL_CLAMP_TO_EDGE: + shader.append("texCoords.x"); + break; case GL_REPEAT: shader.append("mod(texCoords.x, 1.0)"); break; @@ -385,6 +405,9 @@ void ProgramCache::generateTextureWrap(String8& shader, GLenum wrapS, GLenum wra } shader.append(", "); switch (wrapT) { + case GL_CLAMP_TO_EDGE: + shader.append("texCoords.y"); + break; case GL_REPEAT: shader.append("mod(texCoords.y, 1.0)"); break; diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index a1a4a0e1b879..fa4b8c4b3e47 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -57,9 +57,9 @@ namespace uirenderer { #define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600 #define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800 -// Support only the 12 Porter-Duff modes for now -#define PROGRAM_MAX_XFERMODE 0xC -#define PROGRAM_XFERMODE_SHADER_SHIFT 24 +// Encode the xfermodes on 6 bits +#define PROGRAM_MAX_XFERMODE 0x1f +#define PROGRAM_XFERMODE_SHADER_SHIFT 26 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20 #define PROGRAM_BITMAP_WRAPS_SHIFT 9 @@ -177,7 +177,7 @@ private: Program* generateProgram(const ProgramDescription& description, programid key); String8 generateVertexShader(const ProgramDescription& description); String8 generateFragmentShader(const ProgramDescription& description); - void generatePorterDuffBlend(String8& shader, const char* name, SkXfermode::Mode mode); + void generateBlend(String8& shader, const char* name, SkXfermode::Mode mode); void generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT); void printLongString(const String8& shader) const; diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index ffdb3485b3c0..3569d6a286ea 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -75,11 +75,13 @@ void SkiaShader::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX, SkShader::TileMode tileY, SkMatrix* matrix, bool blend): - SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap) { + SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap), mTexture(NULL) { } void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) { const Texture* texture = mTextureCache->get(mBitmap); + if (!texture) return; + mTexture = texture; const float width = texture->width; const float height = texture->height; @@ -87,7 +89,8 @@ void SkiaBitmapShader::describe(ProgramDescription& description, const Extension description.hasBitmap = true; // The driver does not support non-power of two mirrored/repeated // textures, so do it ourselves - if (!extensions.hasNPot() && !isPowerOfTwo(width) && !isPowerOfTwo(height)) { + if (!extensions.hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) && + (mTileX != SkShader::kClamp_TileMode || mTileY != SkShader::kClamp_TileMode)) { description.isBitmapNpot = true; description.bitmapWrapS = gTileModes[mTileX]; description.bitmapWrapT = gTileModes[mTileY]; @@ -98,7 +101,11 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, GLuint* textureUnit) { GLuint textureSlot = (*textureUnit)++; glActiveTexture(gTextureUnitsMap[textureSlot]); - const Texture* texture = mTextureCache->get(mBitmap); + + const Texture* texture = mTexture; + mTexture = NULL; + if (!texture) return; + const AutoTexture autoCleanup(texture); const float width = texture->width; const float height = texture->height; diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index 58f2870ac6c6..d95e3b04a42c 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -116,6 +116,7 @@ private: } SkBitmap* mBitmap; + const Texture* mTexture; }; // struct SkiaBitmapShader /** diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 399ae684d962..342e5b157f68 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -96,9 +96,14 @@ public: bool clip(float left, float top, float right, float bottom, SkRegion::Op op) { bool clipped = false; - Rect r(left, top, right, bottom); - transform.mapRect(r); + SkRect sr; + sr.set(left, top, right, bottom); + SkMatrix m; + transform.copyTo(m); + m.mapRect(&sr); + + Rect r(sr.fLeft, sr.fTop, sr.fRight, sr.fBottom); switch (op) { case SkRegion::kDifference_Op: break; @@ -137,8 +142,16 @@ public: if (flags & Snapshot::kFlagDirtyLocalClip) { mat4 inverse; inverse.loadInverse(transform); - localClip.set(clipRect); - inverse.mapRect(localClip); + + SkRect sr; + sr.set(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + + SkMatrix m; + inverse.copyTo(m); + m.mapRect(&sr); + + localClip.set(sr.fLeft, sr.fTop, sr.fRight, sr.fBottom); + flags &= ~Snapshot::kFlagDirtyLocalClip; } return localClip; diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 59903b70a6a1..3f9698db6032 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -31,6 +31,9 @@ TextureCache::TextureCache(uint32_t maxByteSize): mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity), mSize(0), mMaxSize(maxByteSize) { mCache.setOnEntryRemovedListener(this); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); + LOGD("Maximum texture dimension is %d pixels", mMaxTextureSize); } TextureCache::~TextureCache() { @@ -79,6 +82,11 @@ void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) { Texture* TextureCache::get(SkBitmap* bitmap) { Texture* texture = mCache.get(bitmap); if (!texture) { + if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) { + LOGW("Bitmap too large to be uploaded into a texture"); + return NULL; + } + const uint32_t size = bitmap->rowBytes() * bitmap->height(); // Don't even try to cache a bitmap that's bigger than the cache if (size < mMaxSize) { diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index bed11915c581..452716c947ff 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -82,6 +82,7 @@ private: uint32_t mSize; uint32_t mMaxSize; + GLint mMaxTextureSize; }; // class TextureCache }; // namespace uirenderer diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs index 008c8b58dba6..3c69289ea780 100644 --- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs +++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs @@ -8,52 +8,22 @@ void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32 const uchar4 *input = (const uchar4 *)rsGetElementAt(fs->ain, x, 0); float3 blurredPixel = 0; - float3 currentPixel = 0; - const float *gPtr = fs->gaussian; if ((y > fs->radius) && (y < (fs->height - fs->radius))) { const uchar4 *i = input + ((y - fs->radius) * fs->width); for(int r = -fs->radius; r <= fs->radius; r ++) { - currentPixel.x = (float)(i->x); - currentPixel.y = (float)(i->y); - currentPixel.z = (float)(i->z); - blurredPixel += currentPixel * gPtr[0]; + blurredPixel += convert_float3(i->xyz) * gPtr[0]; gPtr++; i += fs->width; } } else { for(int r = -fs->radius; r <= fs->radius; r ++) { - #if 1 - int validH = y + r; - // Clamp to zero and width - if(validH < 0) { - validH = 0; - } - if(validH > fs->height - 1) { - validH = fs->height - 1; - } - + int validH = rsClamp(y + r, (uint)0, (uint)(fs->height - 1)); const uchar4 *i = input + validH * fs->width; - //const uchar4 *i = (const uchar4 *)rsGetElementAt(fs->ain, x, validH); - - currentPixel.x = (float)(i->x); - currentPixel.y = (float)(i->y); - currentPixel.z = (float)(i->z); - blurredPixel += currentPixel * gPtr[0]; + blurredPixel += convert_float3(i->xyz) * gPtr[0]; gPtr++; - #else - int validH = rsClamp(y + r, 0, height - 1); - validH -= y; - uchar4 *i = input + validH * width + x; - blurredPixel.xyz += convert_float3(i->xyz) * gPtr[0]; - gPtr++; - #endif } } - - //output->xyz = convert_uchar3(blurredPixel.xyz); - output->x = (uint8_t)blurredPixel.x; - output->y = (uint8_t)blurredPixel.y; - output->z = (uint8_t)blurredPixel.z; + output->xyz = convert_uchar3(blurredPixel); } diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp index 6560101e6a89..7d31bd612633 100644 --- a/libs/rs/rsAllocation.cpp +++ b/libs/rs/rsAllocation.cpp @@ -234,6 +234,12 @@ void Allocation::data(const void *data, uint32_t sizeBytes) LOGE("Allocation::data called with mismatched size expected %i, got %i", size, sizeBytes); return; } + + if (mType->getElement()->getHasReferences()) { + incRefs(data, sizeBytes / mType->getElement()->getSizeBytes()); + decRefs(mPtr, sizeBytes / mType->getElement()->getSizeBytes()); + } + memcpy(mPtr, data, size); sendDirty(); mUploadDefered = true; @@ -256,6 +262,12 @@ void Allocation::subData(uint32_t xoff, uint32_t count, const void *data, uint32 mType->dumpLOGV("type info"); return; } + + if (mType->getElement()->getHasReferences()) { + incRefs(data, count); + decRefs(ptr, count); + } + memcpy(ptr, data, size); sendDirty(); mUploadDefered = true; @@ -279,6 +291,10 @@ void Allocation::subData(uint32_t xoff, uint32_t yoff, 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); + } memcpy(dst, src, lineSize); src += lineSize; dst += destW * eSize; @@ -375,7 +391,8 @@ Allocation *Allocation::createFromStream(Context *rsc, IStream *stream) alloc->setName(name.string(), name.size()); // Read in all of our allocation data - stream->loadByteArray(alloc->getPtr(), dataSize); + alloc->data(stream->getPtr() + stream->getPos(), dataSize); + stream->reset(stream->getPos() + dataSize); return alloc; } @@ -387,6 +404,32 @@ void Allocation::sendDirty() const } } +void Allocation::incRefs(const void *ptr, size_t ct) const +{ + const uint8_t *p = static_cast<const uint8_t *>(ptr); + const Element *e = mType->getElement(); + uint32_t stride = e->getSizeBytes(); + + while (ct > 0) { + e->incRefs(p); + ct --; + p += stride; + } +} + +void Allocation::decRefs(const void *ptr, size_t ct) const +{ + const uint8_t *p = static_cast<const uint8_t *>(ptr); + const Element *e = mType->getElement(); + uint32_t stride = e->getSizeBytes(); + + while (ct > 0) { + e->decRefs(p); + ct --; + p += stride; + } +} + ///////////////// // diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h index 8273165c4d86..177d5a411cfc 100644 --- a/libs/rs/rsAllocation.h +++ b/libs/rs/rsAllocation.h @@ -81,6 +81,9 @@ public: bool getIsTexture() const {return mIsTexture;} bool getIsBufferObject() const {return mIsVertexBuffer;} + void incRefs(const void *ptr, size_t ct) const; + void decRefs(const void *ptr, size_t ct) const; + protected: void sendDirty() const; diff --git a/libs/rs/rsComponent.cpp b/libs/rs/rsComponent.cpp index 8e509ad715d7..fbaa75f68c31 100644 --- a/libs/rs/rsComponent.cpp +++ b/libs/rs/rsComponent.cpp @@ -161,6 +161,10 @@ void Component::set(RsDataType dt, RsDataKind dk, bool norm, uint32_t vecSize) mBits = mTypeBits * mVectorSize; } +bool Component::isReference() const +{ + return (mType >= RS_TYPE_ELEMENT); +} diff --git a/libs/rs/rsComponent.h b/libs/rs/rsComponent.h index 15fd5ddc1523..a7750511454f 100644 --- a/libs/rs/rsComponent.h +++ b/libs/rs/rsComponent.h @@ -51,6 +51,8 @@ public: void serialize(OStream *stream) const; void loadFromStream(IStream *stream); + bool isReference() const; + protected: RsDataType mType; RsDataKind mKind; diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp index 37b8bd6bd1a6..5dee1fb0ff29 100644 --- a/libs/rs/rsElement.cpp +++ b/libs/rs/rsElement.cpp @@ -34,6 +34,7 @@ Element::Element(Context *rsc) : ObjectBase(rsc) mAllocLine = __LINE__; mFields = NULL; mFieldCount = 0; + mHasReference = false; } @@ -53,6 +54,7 @@ void Element::clear() delete [] mFields; mFields = NULL; mFieldCount = 0; + mHasReference = false; } size_t Element::getSizeBits() const @@ -68,15 +70,6 @@ size_t Element::getSizeBits() const return total; } -size_t Element::getFieldOffsetBits(uint32_t componentNumber) const -{ - size_t offset = 0; - for (uint32_t ct = 0; ct < componentNumber; ct++) { - offset += mFields[ct].e->mBits; - } - return offset; -} - void Element::dumpLOGV(const char *prefix) const { ObjectBase::dumpLOGV(prefix); @@ -121,14 +114,22 @@ Element *Element::createFromStream(Context *rsc, IStream *stream) Element *elem = new Element(rsc); elem->mComponent.loadFromStream(stream); elem->mBits = elem->mComponent.getBits(); + elem->mHasReference = elem->mComponent.isReference(); elem->mFieldCount = stream->loadU32(); if(elem->mFieldCount) { + uint32_t offset = 0; elem->mFields = new ElementField_t [elem->mFieldCount]; for(uint32_t ct = 0; ct < elem->mFieldCount; ct ++) { stream->loadString(&elem->mFields[ct].name); Element *fieldElem = Element::createFromStream(rsc, stream); elem->mFields[ct].e.set(fieldElem); + elem->mFields[ct].offsetBits = offset; + offset += fieldElem->getSizeBits(); + // Check if our sub-elements have references + if(fieldElem->mHasReference) { + elem->mHasReference = true; + } } } @@ -193,6 +194,7 @@ const Element * Element::create(Context *rsc, RsDataType dt, RsDataKind dk, Element *e = new Element(rsc); e->mComponent.set(dt, dk, isNorm, vecSize); e->mBits = e->mComponent.getBits(); + e->mHasReference = e->mComponent.isReference(); rsc->mStateElement.mElements.push(e); return e; } @@ -223,9 +225,16 @@ const Element * Element::create(Context *rsc, size_t count, const Element **ein, Element *e = new Element(rsc); e->mFields = new ElementField_t [count]; e->mFieldCount = count; + size_t bits = 0; for (size_t ct=0; ct < count; ct++) { e->mFields[ct].e.set(ein[ct]); e->mFields[ct].name.setTo(nin[ct], lengths[ct]); + e->mFields[ct].offsetBits = bits; + bits += ein[ct]->getSizeBits(); + + if (ein[ct]->mHasReference) { + e->mHasReference = true; + } } rsc->mStateElement.mElements.push(e); @@ -251,6 +260,43 @@ String8 Element::getGLSLType(uint32_t indent) const return s; } +void Element::incRefs(const void *ptr) const +{ + if (!mFieldCount) { + if (mComponent.isReference()) { + ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr); + ObjectBase *ob = obp[0]; + ob->incSysRef(); + } + return; + } + + const uint8_t *p = static_cast<const uint8_t *>(ptr); + for (uint32_t i=0; i < mFieldCount; i++) { + if (mFields[i].e->mHasReference) { + mFields[i].e->incRefs(&p[mFields[i].offsetBits >> 3]); + } + } +} + +void Element::decRefs(const void *ptr) const +{ + if (!mFieldCount) { + if (mComponent.isReference()) { + ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr); + ObjectBase *ob = obp[0]; + ob->decSysRef(); + } + return; + } + + const uint8_t *p = static_cast<const uint8_t *>(ptr); + for (uint32_t i=0; i < mFieldCount; i++) { + if (mFields[i].e->mHasReference) { + mFields[i].e->decRefs(&p[mFields[i].offsetBits >> 3]); + } + } +} ElementState::ElementState() diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h index 90e7cc8ff053..b5dad7a23c4a 100644 --- a/libs/rs/rsElement.h +++ b/libs/rs/rsElement.h @@ -40,9 +40,11 @@ public: return (getSizeBits() + 7) >> 3; } - size_t getFieldOffsetBits(uint32_t componentNumber) const; + size_t getFieldOffsetBits(uint32_t componentNumber) const { + return mFields[componentNumber].offsetBits; + } size_t getFieldOffsetBytes(uint32_t componentNumber) const { - return (getFieldOffsetBits(componentNumber) + 7) >> 3; + return mFields[componentNumber].offsetBits >> 3; } uint32_t getFieldCount() const {return mFieldCount;} @@ -66,6 +68,10 @@ public: static const Element * create(Context *rsc, size_t count, const Element **, const char **, const size_t * lengths); + void incRefs(const void *) const; + void decRefs(const void *) const; + bool getHasReferences() const {return mHasReference;} + protected: // deallocate any components that are part of this element. void clear(); @@ -73,9 +79,11 @@ protected: typedef struct { String8 name; ObjectBaseRef<const Element> e; + uint32_t offsetBits; } ElementField_t; ElementField_t *mFields; size_t mFieldCount; + bool mHasReference; Element(Context *); diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh index 85f3b2529ede..aa9aebcad6de 100644 --- a/libs/rs/scriptc/rs_core.rsh +++ b/libs/rs/scriptc/rs_core.rsh @@ -602,6 +602,171 @@ rsMatrixTranspose(rs_matrix2x2 *m) { m->m[2] = temp; } +///////////////////////////////////////////////////// +// quaternion ops +///////////////////////////////////////////////////// + +static void __attribute__((overloadable)) +rsQuaternionSet(rs_quaternion *q, float w, float x, float y, float z) { + q->w = w; + q->x = x; + q->y = y; + q->z = z; +} + +static void __attribute__((overloadable)) +rsQuaternionSet(rs_quaternion *q, const rs_quaternion *rhs) { + q->w = rhs->w; + q->x = rhs->x; + q->y = rhs->y; + q->z = rhs->z; +} + +static void __attribute__((overloadable)) +rsQuaternionMultiply(rs_quaternion *q, float s) { + q->w *= s; + q->x *= s; + q->y *= s; + q->z *= s; +} + +static void __attribute__((overloadable)) +rsQuaternionMultiply(rs_quaternion *q, const rs_quaternion *rhs) { + q->w = -q->x*rhs->x - q->y*rhs->y - q->z*rhs->z + q->w*rhs->w; + q->x = q->x*rhs->w + q->y*rhs->z - q->z*rhs->y + q->w*rhs->x; + q->y = -q->x*rhs->z + q->y*rhs->w + q->z*rhs->z + q->w*rhs->y; + q->z = q->x*rhs->y - q->y*rhs->x + q->z*rhs->w + q->w*rhs->z; +} + +static void +rsQuaternionAdd(rs_quaternion *q, const rs_quaternion *rhs) { + q->w *= rhs->w; + q->x *= rhs->x; + q->y *= rhs->y; + q->z *= rhs->z; +} + +static void +rsQuaternionLoadRotateUnit(rs_quaternion *q, float rot, float x, float y, float z) { + rot *= (float)(M_PI / 180.0f) * 0.5f; + float c = cos(rot); + float s = sin(rot); + + q->w = c; + q->x = x * s; + q->y = y * s; + q->z = z * s; +} + +static void +rsQuaternionLoadRotate(rs_quaternion *q, float rot, float x, float y, float z) { + const float len = x*x + y*y + z*z; + if (len != 1) { + const float recipLen = 1.f / sqrt(len); + x *= recipLen; + y *= recipLen; + z *= recipLen; + } + rsQuaternionLoadRotateUnit(q, rot, x, y, z); +} + +static void +rsQuaternionConjugate(rs_quaternion *q) { + q->x = -q->x; + q->y = -q->y; + q->z = -q->z; +} + +static float +rsQuaternionDot(const rs_quaternion *q0, const rs_quaternion *q1) { + return q0->w*q1->w + q0->x*q1->x + q0->y*q1->y + q0->z*q1->z; +} + +static void +rsQuaternionNormalize(rs_quaternion *q) { + const float len = rsQuaternionDot(q, q); + if (len != 1) { + const float recipLen = 1.f / sqrt(len); + rsQuaternionMultiply(q, recipLen); + } +} + +static void +rsQuaternionSlerp(rs_quaternion *q, const rs_quaternion *q0, const rs_quaternion *q1, float t) { + if(t <= 0.0f) { + rsQuaternionSet(q, q0); + return; + } + if(t >= 1.0f) { + rsQuaternionSet(q, q1); + return; + } + + rs_quaternion tempq0, tempq1; + rsQuaternionSet(&tempq0, q0); + rsQuaternionSet(&tempq1, q1); + + float angle = rsQuaternionDot(q0, q1); + if(angle < 0) { + rsQuaternionMultiply(&tempq0, -1.0f); + angle *= -1.0f; + } + + float scale, invScale; + if (angle + 1.0f > 0.05f) { + if (1.0f - angle >= 0.05f) { + float theta = acos(angle); + float invSinTheta = 1.0f / sin(theta); + scale = sin(theta * (1.0f - t)) * invSinTheta; + invScale = sin(theta * t) * invSinTheta; + } + else { + scale = 1.0f - t; + invScale = t; + } + } + else { + rsQuaternionSet(&tempq1, tempq0.z, -tempq0.y, tempq0.x, -tempq0.w); + scale = sin(M_PI * (0.5f - t)); + invScale = sin(M_PI * t); + } + + rsQuaternionSet(q, tempq0.w*scale + tempq1.w*invScale, tempq0.x*scale + tempq1.x*invScale, + tempq0.y*scale + tempq1.y*invScale, tempq0.z*scale + tempq1.z*invScale); +} + +static void rsQuaternionGetMatrixUnit(rs_matrix4x4 *m, const rs_quaternion *q) { + float x2 = 2.0f * q->x * q->x; + float y2 = 2.0f * q->y * q->y; + float z2 = 2.0f * q->z * q->z; + float xy = 2.0f * q->x * q->y; + float wz = 2.0f * q->w * q->z; + float xz = 2.0f * q->x * q->z; + float wy = 2.0f * q->w * q->y; + float wx = 2.0f * q->w * q->x; + float yz = 2.0f * q->y * q->z; + + m->m[0] = 1.0f - y2 - z2; + m->m[1] = xy - wz; + m->m[2] = xz + wy; + m->m[3] = 0.0f; + + m->m[4] = xy + wz; + m->m[5] = 1.0f - x2 - z2; + m->m[6] = yz - wx; + m->m[7] = 0.0f; + + m->m[8] = xz - wy; + m->m[9] = yz - wx; + m->m[10] = 1.0f - x2 - y2; + m->m[11] = 0.0f; + + m->m[12] = 0.0f; + m->m[13] = 0.0f; + m->m[14] = 0.0f; + m->m[15] = 1.0f; +} + ///////////////////////////////////////////////////// // int ops diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh index 69e1aedef2f7..ddae7eb1b219 100644 --- a/libs/rs/scriptc/rs_types.rsh +++ b/libs/rs/scriptc/rs_types.rsh @@ -67,6 +67,10 @@ typedef struct { float m[4]; } rs_matrix2x2; +typedef struct { + float w, x, y, z; +} rs_quaternion; + #define RS_PACKED __attribute__((packed, aligned(4))) diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 661870212ffd..5f5a4ac4e644 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -1080,6 +1080,14 @@ void TrackballInputMapper::applyPolicyAndDispatch(nsecs_t when, int32_t motionEv 1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime); } +int32_t TrackballInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); + } else { + return AKEY_STATE_UNKNOWN; + } +} + // --- TouchInputMapper --- diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp index 60a0d82e255d..e09e755861dc 100644 --- a/libs/utils/AssetManager.cpp +++ b/libs/utils/AssetManager.cpp @@ -232,6 +232,12 @@ void AssetManager::setConfiguration(const ResTable_config& config, const char* l } } +void AssetManager::getConfiguration(ResTable_config* outConfig) const +{ + AutoMutex _l(mLock); + *outConfig = *mConfig; +} + /* * Open an asset. * diff --git a/libs/utils/ObbFile.cpp b/libs/utils/ObbFile.cpp index fe49300eb25d..adedf0c5d0dc 100644 --- a/libs/utils/ObbFile.cpp +++ b/libs/utils/ObbFile.cpp @@ -156,9 +156,9 @@ bool ObbFile::parseObbFile(int fd) return false; } - if (footerSize < kFooterMinSize) { - LOGW("claimed footer size is too small (%08zx; minimum size is 0x%x)\n", - footerSize, kFooterMinSize); + if (footerSize < (kFooterMinSize - kFooterTagSize)) { + LOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n", + footerSize, kFooterMinSize - kFooterTagSize); return false; } } @@ -169,6 +169,8 @@ bool ObbFile::parseObbFile(int fd) return false; } + mFooterStart = fileOffset; + char* scanBuf = (char*)malloc(footerSize); if (scanBuf == NULL) { LOGW("couldn't allocate scanBuf: %s\n", strerror(errno)); @@ -293,4 +295,38 @@ bool ObbFile::writeTo(int fd) return true; } +bool ObbFile::removeFrom(const char* filename) +{ + int fd; + bool success = false; + + fd = ::open(filename, O_RDWR); + if (fd < 0) { + goto out; + } + success = removeFrom(fd); + close(fd); + +out: + if (!success) { + LOGW("failed to remove signature from %s: %s\n", filename, strerror(errno)); + } + return success; +} + +bool ObbFile::removeFrom(int fd) +{ + if (fd < 0) { + return false; + } + + if (!readFrom(fd)) { + return false; + } + + ftruncate(fd, mFooterStart); + + return true; +} + } diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk index b9f206a9d89f..725de9c5bb8b 100644 --- a/libs/utils/tests/Android.mk +++ b/libs/utils/tests/Android.mk @@ -7,7 +7,8 @@ ifneq ($(TARGET_SIMULATOR),true) # Build the unit tests. test_src_files := \ ObbFile_test.cpp \ - PollLoop_test.cpp + PollLoop_test.cpp \ + String8_test.cpp shared_libraries := \ libz \ @@ -41,4 +42,4 @@ $(foreach file,$(test_src_files), \ $(eval include $(BUILD_EXECUTABLE)) \ ) -endif
\ No newline at end of file +endif diff --git a/libs/utils/tests/String8_test.cpp b/libs/utils/tests/String8_test.cpp new file mode 100644 index 000000000000..c42c68dcefad --- /dev/null +++ b/libs/utils/tests/String8_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "String8_test" +#include <utils/Log.h> +#include <utils/String8.h> + +#include <gtest/gtest.h> + +namespace android { + +class String8Test : public testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } +}; + +TEST_F(String8Test, Cstr) { + String8 tmp("Hello, world!"); + + EXPECT_STREQ(tmp.string(), "Hello, world!"); +} + +TEST_F(String8Test, OperatorPlus) { + String8 src1("Hello, "); + + // Test adding String8 + const char* + const char* ccsrc2 = "world!"; + String8 dst1 = src1 + ccsrc2; + EXPECT_STREQ(dst1.string(), "Hello, world!"); + EXPECT_STREQ(src1.string(), "Hello, "); + EXPECT_STREQ(ccsrc2, "world!"); + + // Test adding String8 + String8 + String8 ssrc2("world!"); + String8 dst2 = src1 + ssrc2; + EXPECT_STREQ(dst2.string(), "Hello, world!"); + EXPECT_STREQ(src1.string(), "Hello, "); + EXPECT_STREQ(ssrc2.string(), "world!"); +} + +TEST_F(String8Test, OperatorPlusEquals) { + String8 src1("My voice"); + + // Testing String8 += String8 + String8 src2(" is my passport."); + src1 += src2; + EXPECT_STREQ(src1.string(), "My voice is my passport."); + EXPECT_STREQ(src2.string(), " is my passport."); + + // Adding const char* to the previous string. + const char* src3 = " Verify me."; + src1 += src3; + EXPECT_STREQ(src1.string(), "My voice is my passport. Verify me."); + EXPECT_STREQ(src2.string(), " is my passport."); + EXPECT_STREQ(src3, " Verify me."); +} + +} diff --git a/media/java/android/media/AudioEffect.java b/media/java/android/media/AudioEffect.java index aed29c3379d2..35038fa5ae92 100644 --- a/media/java/android/media/AudioEffect.java +++ b/media/java/android/media/AudioEffect.java @@ -101,15 +101,15 @@ public class AudioEffect { public static final int STATE_INITIALIZED = 1; // to keep in sync with - // frameworks/base/media/jni/audioeffect/android_media_AudioEffect.cpp + // frameworks/base/include/media/AudioEffect.h /** - * Event id for engine state change notification. + * Event id for engine control ownership change notification. */ - public static final int NATIVE_EVENT_ENABLED_STATUS = 0; + public static final int NATIVE_EVENT_CONTROL_STATUS = 0; /** - * Event id for engine control ownership change notification. + * Event id for engine state change notification. */ - public static final int NATIVE_EVENT_CONTROL_STATUS = 1; + public static final int NATIVE_EVENT_ENABLED_STATUS = 1; /** * Event id for engine parameter change notification. */ @@ -795,7 +795,7 @@ public class AudioEffect { // Interface definitions // -------------------- /** - * The OnParameterChangeListener interface defines a method called by the AudioEffect + * The OnEnableStatusChangeListener interface defines a method called by the AudioEffect * when a the enabled state of the effect engine was changed by the controlling application. */ public interface OnEnableStatusChangeListener { @@ -922,7 +922,6 @@ public class AudioEffect { if (effect == null) { return; } - if (effect.mNativeEventHandler != null) { Message m = effect.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 9212708e6c13..41d2cc5697d4 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -239,6 +239,9 @@ public class AudioService extends IAudioService.Stub { // independently change its priority) private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver(); + // Used to alter media button redirection when the phone is ringing. + private boolean mIsRinging = false; + // Devices currently connected private HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); @@ -1956,11 +1959,16 @@ public class AudioService extends IAudioService.Stub { private final static Object mAudioFocusLock = new Object(); + private final static Object mRingingLock = new Object(); + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { if (state == TelephonyManager.CALL_STATE_RINGING) { //Log.v(TAG, " CALL_STATE_RINGING"); + synchronized(mRingingLock) { + mIsRinging = true; + } int ringVolume = AudioService.this.getStreamVolume(AudioManager.STREAM_RING); if (ringVolume > 0) { requestAudioFocus(AudioManager.STREAM_RING, @@ -1970,12 +1978,18 @@ public class AudioService extends IAudioService.Stub { } } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) { //Log.v(TAG, " CALL_STATE_OFFHOOK"); + synchronized(mRingingLock) { + mIsRinging = false; + } requestAudioFocus(AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, null, null /* both allowed to be null only for this clientId */, IN_VOICE_COMM_FOCUS_ID /*clientId*/); } else if (state == TelephonyManager.CALL_STATE_IDLE) { //Log.v(TAG, " CALL_STATE_IDLE"); + synchronized(mRingingLock) { + mIsRinging = false; + } abandonAudioFocus(null, IN_VOICE_COMM_FOCUS_ID); } } @@ -2243,9 +2257,11 @@ public class AudioService extends IAudioService.Stub { // if in a call or ringing, do not break the current phone app behavior // TODO modify this to let the phone app specifically get the RC focus // add modify the phone app to take advantage of the new API - if ((getMode() == AudioSystem.MODE_IN_CALL) || - (getMode() == AudioSystem.MODE_RINGTONE)) { - return; + synchronized(mRingingLock) { + if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) || + (getMode() == AudioSystem.MODE_RINGTONE) ) { + return; + } } synchronized(mRCStack) { if (!mRCStack.empty()) { diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index 37f9f2c7dd6f..88cce467bce3 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -220,6 +220,50 @@ public class MtpDatabase { return -1; } + private int[] getSupportedPlaybackFormats() { + return new int[] { + Mtp.Object.FORMAT_ASSOCIATION, + Mtp.Object.FORMAT_MP3, + Mtp.Object.FORMAT_MPEG, + Mtp.Object.FORMAT_EXIF_JPEG, + Mtp.Object.FORMAT_TIFF_EP, + Mtp.Object.FORMAT_GIF, + Mtp.Object.FORMAT_JFIF, + Mtp.Object.FORMAT_PNG, + Mtp.Object.FORMAT_TIFF, + Mtp.Object.FORMAT_WMA, + Mtp.Object.FORMAT_OGG, + Mtp.Object.FORMAT_AAC, + Mtp.Object.FORMAT_MP4_CONTAINER, + Mtp.Object.FORMAT_MP2, + Mtp.Object.FORMAT_3GP_CONTAINER, + Mtp.Object.FORMAT_ABSTRACT_AV_PLAYLIST, + Mtp.Object.FORMAT_WPL_PLAYLIST, + Mtp.Object.FORMAT_M3U_PLAYLIST, + Mtp.Object.FORMAT_PLS_PLAYLIST, + }; + } + + private int[] getSupportedCaptureFormats() { + // no capture formats yet + return null; + } + + private int[] getSupportedObjectProperties(int handle) { + return new int[] { + Mtp.Object.PROPERTY_STORAGE_ID, + Mtp.Object.PROPERTY_OBJECT_FORMAT, + Mtp.Object.PROPERTY_OBJECT_SIZE, + Mtp.Object.PROPERTY_OBJECT_FILE_NAME, + Mtp.Object.PROPERTY_PARENT_OBJECT, + }; + } + + private int[] getSupportedDeviceProperties() { + // no device properties yet + return null; + } + private int getObjectProperty(int handle, int property, long[] outIntValue, char[] outStringValue) { Log.d(TAG, "getObjectProperty: " + property); diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp index d4539fe0f15e..abbea30ff575 100644 --- a/media/jni/android_media_MtpDatabase.cpp +++ b/media/jni/android_media_MtpDatabase.cpp @@ -40,6 +40,10 @@ static jmethodID method_beginSendObject; static jmethodID method_endSendObject; static jmethodID method_getObjectList; static jmethodID method_getNumObjects; +static jmethodID method_getSupportedPlaybackFormats; +static jmethodID method_getSupportedCaptureFormats; +static jmethodID method_getSupportedObjectProperties; +static jmethodID method_getSupportedDeviceProperties; static jmethodID method_getObjectProperty; static jmethodID method_getObjectInfo; static jmethodID method_getObjectFilePath; @@ -87,6 +91,13 @@ public: MtpObjectFormat format, MtpObjectHandle parent); + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats(); + virtual MtpObjectFormatList* getSupportedCaptureFormats(); + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format); + virtual MtpDevicePropertyList* getSupportedDeviceProperties(); + virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle, MtpObjectProperty property, MtpDataPacket& packet); @@ -190,6 +201,66 @@ int MyMtpDatabase::getNumObjects(MtpStorageID storageID, (jint)storageID, (jint)format, (jint)parent); } +MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedPlaybackFormats); + if (!array) + return NULL; + MtpObjectFormatList* list = new MtpObjectFormatList(); + jint* formats = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(formats[i]); + env->ReleaseIntArrayElements(array, formats, 0); + return list; +} + +MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedCaptureFormats); + if (!array) + return NULL; + MtpObjectFormatList* list = new MtpObjectFormatList(); + jint* formats = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(formats[i]); + env->ReleaseIntArrayElements(array, formats, 0); + return list; +} + +MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedObjectProperties, (jint)format); + if (!array) + return NULL; + MtpObjectPropertyList* list = new MtpObjectPropertyList(); + jint* properties = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(properties[i]); + env->ReleaseIntArrayElements(array, properties, 0); + return list; +} + +MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jintArray array = (jintArray)env->CallObjectMethod(mDatabase, + method_getSupportedDeviceProperties); + if (!array) + return NULL; + MtpDevicePropertyList* list = new MtpDevicePropertyList(); + jint* properties = env->GetIntArrayElements(array, 0); + jsize length = env->GetArrayLength(array); + for (int i = 0; i < length; i++) + list->push(properties[i]); + env->ReleaseIntArrayElements(array, properties, 0); + return list; +} + MtpResponseCode MyMtpDatabase::getObjectProperty(MtpObjectHandle handle, MtpObjectProperty property, MtpDataPacket& packet) { @@ -333,7 +404,7 @@ struct PropertyTableEntry { static const PropertyTableEntry kPropertyTable[] = { { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 }, { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 }, - { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 }, { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR }, { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 }, { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR }, @@ -460,6 +531,26 @@ int register_android_media_MtpDatabase(JNIEnv *env) LOGE("Can't find getNumObjects"); return -1; } + method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I"); + if (method_getSupportedPlaybackFormats == NULL) { + LOGE("Can't find getSupportedPlaybackFormats"); + return -1; + } + method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I"); + if (method_getSupportedCaptureFormats == NULL) { + LOGE("Can't find getSupportedCaptureFormats"); + return -1; + } + method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I"); + if (method_getSupportedObjectProperties == NULL) { + LOGE("Can't find getSupportedObjectProperties"); + return -1; + } + method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I"); + if (method_getSupportedDeviceProperties == NULL) { + LOGE("Can't find getSupportedDeviceProperties"); + return -1; + } method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I"); if (method_getObjectProperty == NULL) { LOGE("Can't find getObjectProperty"); diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index 3cdf48a4d51b..0f3e24532631 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -218,7 +218,7 @@ status_t AudioEffect::setEnabled(bool enabled) return mIEffect->disable(); } } - return INVALID_OPERATION; + return NO_ERROR; } status_t AudioEffect::command(uint32_t cmdCode, @@ -231,7 +231,22 @@ status_t AudioEffect::command(uint32_t cmdCode, return INVALID_OPERATION; } - return mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData); + status_t status = mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData); + if (status != NO_ERROR) { + return status; + } + status = *(status_t *)replyData; + if (status != NO_ERROR) { + return status; + } + + if (cmdCode == EFFECT_CMD_ENABLE) { + android_atomic_or(1, &mEnabled); + } + if (cmdCode == EFFECT_CMD_DISABLE) { + android_atomic_and(~1, &mEnabled); + } + return status; } @@ -347,7 +362,11 @@ void AudioEffect::enableStatusChanged(bool enabled) { LOGV("enableStatusChanged %p enabled %d mCbf %p", this, enabled, mCbf); if (mStatus == ALREADY_EXISTS) { - mEnabled = enabled; + if (enabled) { + android_atomic_or(1, &mEnabled); + } else { + android_atomic_and(~1, &mEnabled); + } if (mCbf) { mCbf(EVENT_ENABLE_STATUS_CHANGED, mUserData, &enabled); } diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp index 9fc1d27938d1..70af2da4850e 100644 --- a/media/libstagefright/AMRExtractor.cpp +++ b/media/libstagefright/AMRExtractor.cpp @@ -263,6 +263,7 @@ status_t AMRSource::read( buffer->set_range(0, frameSize); buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs); + buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); mOffset += frameSize; mCurrentTimeUs += 20000; // Each frame is 20ms diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 77a14766bd06..404762fd1886 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -81,6 +81,7 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_httplive \ libstagefright_rtsp \ libstagefright_id3 \ + libstagefright_g711dec \ LOCAL_SHARED_LIBRARIES += \ libstagefright_amrnb_common \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index e426fca16314..efdad4362a2e 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -854,18 +854,6 @@ void AwesomePlayer::setVideoSource(sp<MediaSource> source) { status_t AwesomePlayer::initVideoDecoder() { uint32_t flags = 0; -#if 0 - if (mRTPSession != NULL) { - // XXX hack. - - const char *mime; - CHECK(mVideoTrack->getFormat()->findCString(kKeyMIMEType, &mime)); - if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { - flags |= OMXCodec::kPreferSoftwareCodecs; - } - } -#endif - mVideoSource = OMXCodec::Create( mClient.interface(), mVideoTrack->getFormat(), false, // createEncoder @@ -1019,6 +1007,12 @@ void AwesomePlayer::onVideoEvent() { int64_t latenessUs = nowUs - timeUs; + if (mRTPSession != NULL) { + // We'll completely ignore timestamps for gtalk videochat + // and we'll play incoming video as fast as we get it. + latenessUs = 0; + } + if (latenessUs > 40000) { // We're more than 40ms late. LOGV("we're late by %lld us (%.2f secs)", latenessUs, latenessUs / 1E6); diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 5e7dd5c1a38f..9ccd140a19a0 100644 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -260,6 +260,7 @@ void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) { void CameraSource::signalBufferReturned(MediaBuffer *buffer) { LOGV("signalBufferReturned: %p", buffer->data()); + Mutex::Autolock autoLock(mLock); for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin(); it != mFramesBeingEncoded.end(); ++it) { if ((*it)->pointer() == buffer->data()) { @@ -327,6 +328,7 @@ status_t CameraSource::read( (*buffer)->setObserver(this); (*buffer)->add_ref(); (*buffer)->meta_data()->setInt64(kKeyTime, frameTime); + return OK; } } diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp index 23d8f5629821..ba99501c6e0e 100644 --- a/media/libstagefright/CameraSourceTimeLapse.cpp +++ b/media/libstagefright/CameraSourceTimeLapse.cpp @@ -149,10 +149,10 @@ void CameraSourceTimeLapse::threadTimeLapseEntry() { LOGV("threadTimeLapseEntry: taking picture"); CHECK_EQ(OK, mCamera->takePicture()); mCameraIdle = false; - sleep(mTimeBetweenTimeLapseFrameCaptureUs/1E6); + usleep(mTimeBetweenTimeLapseFrameCaptureUs); } else { LOGV("threadTimeLapseEntry: camera busy with old takePicture. Sleeping a little."); - sleep(.01); + usleep(1E4); } } } @@ -187,6 +187,7 @@ void CameraSourceTimeLapse::stopCameraRecording() { if (mUseStillCameraForTimeLapse) { void *dummy; pthread_join(mThreadTimeLapse, &dummy); + CHECK_EQ(OK, mCamera->startPreview()); } else { mCamera->stopRecording(); } diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index 9c99866f9e02..ccc6a340fca6 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -68,7 +68,7 @@ status_t HTTPStream::connect(const char *server, int port) { return UNKNOWN_ERROR; } - setReceiveTimeout(5); // Time out reads after 5 secs by default + setReceiveTimeout(30); // Time out reads after 30 secs by default mState = CONNECTING; @@ -158,7 +158,7 @@ status_t HTTPStream::send(const char *data) { // The workaround accepts both behaviours but could potentially break // legitimate responses that use a single newline to "fold" headers, which is // why it's not yet on by default. -#define WORKAROUND_FOR_MISSING_CR 0 +#define WORKAROUND_FOR_MISSING_CR 1 status_t HTTPStream::receive_line(char *line, size_t size) { if (mState != CONNECTED) { diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index 2248e237d447..4058fbc2c9e0 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -683,6 +683,7 @@ status_t MP3Source::read( buffer->set_range(0, frame_size); buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs); + buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); mCurrentPos += frame_size; mCurrentTimeUs += frame_size * 8000ll / bitrate; diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 7cea6298c29a..6af3a7fd369e 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -492,7 +492,6 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { case FOURCC('m', 'o', 'o', 'f'): case FOURCC('t', 'r', 'a', 'f'): case FOURCC('m', 'f', 'r', 'a'): - case FOURCC('s', 'k', 'i' ,'p'): case FOURCC('u', 'd', 't', 'a'): case FOURCC('i', 'l', 's', 't'): { @@ -1552,13 +1551,14 @@ status_t MPEG4Source::read( off_t offset; size_t size; uint32_t dts; + bool isSyncSample; bool newBuffer = false; if (mBuffer == NULL) { newBuffer = true; status_t err = mSampleTable->getMetaDataForSample( - mCurrentSampleIndex, &offset, &size, &dts); + mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample); if (err != OK) { return err; @@ -1595,6 +1595,10 @@ status_t MPEG4Source::read( kKeyTargetTime, targetSampleTimeUs); } + if (isSyncSample) { + mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); + } + ++mCurrentSampleIndex; } @@ -1697,6 +1701,10 @@ status_t MPEG4Source::read( kKeyTargetTime, targetSampleTimeUs); } + if (isSyncSample) { + mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); + } + ++mCurrentSampleIndex; *out = mBuffer; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index 0d8c3c6afa16..c860c5c50eb3 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -38,6 +38,9 @@ namespace android { +static const uint8_t kNalUnitTypeSeqParamSet = 0x07; +static const uint8_t kNalUnitTypePicParamSet = 0x08; + class MPEG4Writer::Track { public: Track(MPEG4Writer *owner, const sp<MediaSource> &source); @@ -111,6 +114,20 @@ private: }; List<SttsTableEntry> mSttsTableEntries; + // Sequence parameter set or picture parameter set + struct AVCParamSet { + AVCParamSet(uint16_t length, const uint8_t *data) + : mLength(length), mData(data) {} + + uint16_t mLength; + const uint8_t *mData; + }; + List<AVCParamSet> mSeqParamSets; + List<AVCParamSet> mPicParamSets; + uint8_t mProfileIdc; + uint8_t mProfileCompatible; + uint8_t mLevelIdc; + void *mCodecSpecificData; size_t mCodecSpecificDataSize; bool mGotAllCodecSpecificData; @@ -124,8 +141,15 @@ private: static void *ThreadWrapper(void *me); void threadEntry(); + const uint8_t *parseParamSet( + const uint8_t *data, size_t length, int type, size_t *paramSetLen); + status_t makeAVCCodecSpecificData( const uint8_t *data, size_t size); + status_t copyAVCCodecSpecificData( + const uint8_t *data, size_t size); + status_t parseAVCCodecSpecificData( + const uint8_t *data, size_t size); // Track authoring progress status void trackProgressStatus(int64_t timeUs, status_t err = OK); @@ -1038,6 +1062,174 @@ static void hexdump(const void *_data, size_t size) { } } +static void getNalUnitType(uint8_t byte, uint8_t* type) { + LOGV("getNalUnitType: %d", byte); + + // nal_unit_type: 5-bit unsigned integer + *type = (byte & 0x1F); +} + +static const uint8_t *findNextStartCode( + const uint8_t *data, size_t length) { + + LOGV("findNextStartCode: %p %d", data, length); + + size_t bytesLeft = length; + while (bytesLeft > 4 && + memcmp("\x00\x00\x00\x01", &data[length - bytesLeft], 4)) { + --bytesLeft; + } + if (bytesLeft <= 4) { + bytesLeft = 0; // Last parameter set + } + return &data[length - bytesLeft]; +} + +const uint8_t *MPEG4Writer::Track::parseParamSet( + const uint8_t *data, size_t length, int type, size_t *paramSetLen) { + + LOGV("parseParamSet"); + CHECK(type == kNalUnitTypeSeqParamSet || + type == kNalUnitTypePicParamSet); + + const uint8_t *nextStartCode = findNextStartCode(data, length); + *paramSetLen = nextStartCode - data; + if (*paramSetLen == 0) { + LOGE("Param set is malformed, since its length is 0"); + return NULL; + } + + AVCParamSet paramSet(*paramSetLen, data); + if (type == kNalUnitTypeSeqParamSet) { + if (*paramSetLen < 4) { + LOGE("Seq parameter set malformed"); + return NULL; + } + if (mSeqParamSets.empty()) { + mProfileIdc = data[1]; + mProfileCompatible = data[2]; + mLevelIdc = data[3]; + } else { + if (mProfileIdc != data[1] || + mProfileCompatible != data[2] || + mLevelIdc != data[3]) { + LOGE("Inconsistent profile/level found in seq parameter sets"); + return NULL; + } + } + mSeqParamSets.push_back(paramSet); + } else { + mPicParamSets.push_back(paramSet); + } + return nextStartCode; +} + +status_t MPEG4Writer::Track::copyAVCCodecSpecificData( + const uint8_t *data, size_t size) { + LOGV("copyAVCCodecSpecificData"); + + // 2 bytes for each of the parameter set length field + // plus the 7 bytes for the header + if (size < 4 + 7) { + LOGE("Codec specific data length too short: %d", size); + return ERROR_MALFORMED; + } + + mCodecSpecificDataSize = size; + mCodecSpecificData = malloc(size); + memcpy(mCodecSpecificData, data, size); + return OK; +} + +status_t MPEG4Writer::Track::parseAVCCodecSpecificData( + const uint8_t *data, size_t size) { + + LOGV("parseAVCCodecSpecificData"); + // Data starts with a start code. + // SPS and PPS are separated with start codes. + // Also, SPS must come before PPS + uint8_t type = kNalUnitTypeSeqParamSet; + bool gotSps = false; + bool gotPps = false; + const uint8_t *tmp = data; + const uint8_t *nextStartCode = data; + size_t bytesLeft = size; + size_t paramSetLen = 0; + mCodecSpecificDataSize = 0; + while (bytesLeft > 4 && !memcmp("\x00\x00\x00\x01", tmp, 4)) { + getNalUnitType(*(tmp + 4), &type); + if (type == kNalUnitTypeSeqParamSet) { + if (gotPps) { + LOGE("SPS must come before PPS"); + return ERROR_MALFORMED; + } + if (!gotSps) { + gotSps = true; + } + nextStartCode = parseParamSet(tmp + 4, bytesLeft - 4, type, ¶mSetLen); + } else if (type == kNalUnitTypePicParamSet) { + if (!gotSps) { + LOGE("SPS must come before PPS"); + return ERROR_MALFORMED; + } + if (!gotPps) { + gotPps = true; + } + nextStartCode = parseParamSet(tmp + 4, bytesLeft - 4, type, ¶mSetLen); + } else { + LOGE("Only SPS and PPS Nal units are expected"); + return ERROR_MALFORMED; + } + + if (nextStartCode == NULL) { + return ERROR_MALFORMED; + } + + // Move on to find the next parameter set + bytesLeft -= nextStartCode - tmp; + tmp = nextStartCode; + mCodecSpecificDataSize += (2 + paramSetLen); + } + + { + // Check on the number of seq parameter sets + size_t nSeqParamSets = mSeqParamSets.size(); + if (nSeqParamSets == 0) { + LOGE("Cound not find sequence parameter set"); + return ERROR_MALFORMED; + } + + if (nSeqParamSets > 0x1F) { + LOGE("Too many seq parameter sets (%d) found", nSeqParamSets); + return ERROR_MALFORMED; + } + } + + { + // Check on the number of pic parameter sets + size_t nPicParamSets = mPicParamSets.size(); + if (nPicParamSets == 0) { + LOGE("Cound not find picture parameter set"); + return ERROR_MALFORMED; + } + if (nPicParamSets > 0xFF) { + LOGE("Too many pic parameter sets (%d) found", nPicParamSets); + return ERROR_MALFORMED; + } + } + + { + // Check on the profiles + // These profiles requires additional parameter set extensions + if (mProfileIdc == 100 || mProfileIdc == 110 || + mProfileIdc == 122 || mProfileIdc == 144) { + LOGE("Sorry, no support for profile_idc: %d!", mProfileIdc); + return BAD_VALUE; + } + } + + return OK; +} status_t MPEG4Writer::Track::makeAVCCodecSpecificData( const uint8_t *data, size_t size) { @@ -1048,50 +1240,67 @@ status_t MPEG4Writer::Track::makeAVCCodecSpecificData( return ERROR_MALFORMED; } - if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) { - LOGE("Must start with a start code"); + if (size < 4) { + LOGE("Codec specific data length too short: %d", size); return ERROR_MALFORMED; } - size_t picParamOffset = 4; - while (picParamOffset + 3 < size - && memcmp("\x00\x00\x00\x01", &data[picParamOffset], 4)) { - ++picParamOffset; + // Data is in the form of AVCCodecSpecificData + if (memcmp("\x00\x00\x00\x01", data, 4)) { + return copyAVCCodecSpecificData(data, size); } - if (picParamOffset + 3 >= size) { - LOGE("Could not find start-code for pictureParameterSet"); + if (parseAVCCodecSpecificData(data, size) != OK) { return ERROR_MALFORMED; } - size_t seqParamSetLength = picParamOffset - 4; - size_t picParamSetLength = size - picParamOffset - 4; - - mCodecSpecificDataSize = - 6 + 1 + seqParamSetLength + 2 + picParamSetLength + 2; - + // ISO 14496-15: AVC file format + mCodecSpecificDataSize += 7; // 7 more bytes in the header mCodecSpecificData = malloc(mCodecSpecificDataSize); uint8_t *header = (uint8_t *)mCodecSpecificData; - header[0] = 1; - header[1] = 0x42; // profile - header[2] = 0x80; - header[3] = 0x1e; // level + header[0] = 1; // version + header[1] = mProfileIdc; // profile indication + header[2] = mProfileCompatible; // profile compatibility + header[3] = mLevelIdc; + // 6-bit '111111' followed by 2-bit to lengthSizeMinuusOne #if USE_NALLEN_FOUR header[4] = 0xfc | 3; // length size == 4 bytes #else header[4] = 0xfc | 1; // length size == 2 bytes #endif - header[5] = 0xe0 | 1; - header[6] = seqParamSetLength >> 8; - header[7] = seqParamSetLength & 0xff; - memcpy(&header[8], &data[4], seqParamSetLength); - header += 8 + seqParamSetLength; - header[0] = 1; - header[1] = picParamSetLength >> 8; - header[2] = picParamSetLength & 0xff; - memcpy(&header[3], &data[picParamOffset + 4], picParamSetLength); + // 3-bit '111' followed by 5-bit numSequenceParameterSets + int nSequenceParamSets = mSeqParamSets.size(); + header[5] = 0xe0 | nSequenceParamSets; + header += 6; + for (List<AVCParamSet>::iterator it = mSeqParamSets.begin(); + it != mSeqParamSets.end(); ++it) { + // 16-bit sequence parameter set length + uint16_t seqParamSetLength = it->mLength; + header[0] = seqParamSetLength >> 8; + header[1] = seqParamSetLength & 0xff; + + // SPS NAL unit (sequence parameter length bytes) + memcpy(&header[2], it->mData, seqParamSetLength); + header += (2 + seqParamSetLength); + } + + // 8-bit nPictureParameterSets + int nPictureParamSets = mPicParamSets.size(); + header[0] = nPictureParamSets; + header += 1; + for (List<AVCParamSet>::iterator it = mPicParamSets.begin(); + it != mPicParamSets.end(); ++it) { + // 16-bit picture parameter set length + uint16_t picParamSetLength = it->mLength; + header[0] = picParamSetLength >> 8; + header[1] = picParamSetLength & 0xff; + + // PPS Nal unit (picture parameter set length bytes) + memcpy(&header[2], it->mData, picParamSetLength); + header += (2 + picParamSetLength); + } return OK; } @@ -1168,91 +1377,6 @@ void MPEG4Writer::Track::threadEntry() { mGotAllCodecSpecificData = true; continue; - } else if (!mGotAllCodecSpecificData && - count == 1 && mIsMPEG4 && mCodecSpecificData == NULL) { - // The TI mpeg4 encoder does not properly set the - // codec-specific-data flag. - - const uint8_t *data = - (const uint8_t *)buffer->data() + buffer->range_offset(); - - const size_t size = buffer->range_length(); - - size_t offset = 0; - while (offset + 3 < size) { - if (data[offset] == 0x00 && data[offset + 1] == 0x00 - && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) { - break; - } - - ++offset; - } - - // CHECK(offset + 3 < size); - if (offset + 3 >= size) { - // XXX assume the entire first chunk of data is the codec specific - // data. - offset = size; - } - - mCodecSpecificDataSize = offset; - mCodecSpecificData = malloc(offset); - memcpy(mCodecSpecificData, data, offset); - - buffer->set_range(buffer->range_offset() + offset, size - offset); - - if (size == offset) { - buffer->release(); - buffer = NULL; - - continue; - } - - mGotAllCodecSpecificData = true; - } else if (!mGotAllCodecSpecificData && mIsAvc && count < 3) { - // The TI video encoder does not flag codec specific data - // as such and also splits up SPS and PPS across two buffers. - - const uint8_t *data = - (const uint8_t *)buffer->data() + buffer->range_offset(); - - size_t size = buffer->range_length(); - - CHECK(count == 2 || mCodecSpecificData == NULL); - - size_t offset = mCodecSpecificDataSize; - mCodecSpecificDataSize += size + 4; - mCodecSpecificData = - realloc(mCodecSpecificData, mCodecSpecificDataSize); - - memcpy((uint8_t *)mCodecSpecificData + offset, - "\x00\x00\x00\x01", 4); - - memcpy((uint8_t *)mCodecSpecificData + offset + 4, data, size); - - buffer->release(); - buffer = NULL; - - if (count == 2) { - void *tmp = mCodecSpecificData; - size = mCodecSpecificDataSize; - mCodecSpecificData = NULL; - mCodecSpecificDataSize = 0; - - status_t err = makeAVCCodecSpecificData( - (const uint8_t *)tmp, size); - free(tmp); - tmp = NULL; - CHECK_EQ(OK, err); - - mGotAllCodecSpecificData = true; - } - - continue; - } - - if (!mGotAllCodecSpecificData) { - mGotAllCodecSpecificData = true; } // Make a deep copy of the MediaBuffer and Metadata and release @@ -1753,6 +1877,8 @@ void MPEG4Writer::Track::writeTrackHeader( mOwner->writeInt32(samplerate << 16); if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) { mOwner->beginBox("esds"); + CHECK(mCodecSpecificData); + CHECK(mCodecSpecificDataSize > 0); mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt8(0x03); // ES_DescrTag @@ -1830,6 +1956,8 @@ void MPEG4Writer::Track::writeTrackHeader( mOwner->writeInt16(0x18); // depth mOwner->writeInt16(-1); // predefined + CHECK(mCodecSpecificData); + CHECK(mCodecSpecificDataSize > 0); CHECK(23 + mCodecSpecificDataSize < 128); if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { @@ -1877,6 +2005,8 @@ void MPEG4Writer::Track::writeTrackHeader( mOwner->endBox(); // d263 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { + CHECK(mCodecSpecificData); + CHECK(mCodecSpecificDataSize > 0); mOwner->beginBox("avcC"); mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); mOwner->endBox(); // avcC diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 39d264c9c0db..7648d427341d 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -32,6 +32,8 @@ const char *MEDIA_MIMETYPE_AUDIO_MPEG = "audio/mpeg"; const char *MEDIA_MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; const char *MEDIA_MIMETYPE_AUDIO_QCELP = "audio/qcelp"; const char *MEDIA_MIMETYPE_AUDIO_VORBIS = "audio/vorbis"; +const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw"; +const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw"; const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4"; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 4f3bffd94c67..4741b1d84e80 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -26,6 +26,7 @@ #include "include/AMRWBEncoder.h" #include "include/AVCDecoder.h" #include "include/AVCEncoder.h" +#include "include/G711Decoder.h" #include "include/M4vH263Decoder.h" #include "include/M4vH263Encoder.h" #include "include/MP3Decoder.h" @@ -77,6 +78,7 @@ FACTORY_CREATE(AMRNBDecoder) FACTORY_CREATE(AMRWBDecoder) FACTORY_CREATE(AACDecoder) FACTORY_CREATE(AVCDecoder) +FACTORY_CREATE(G711Decoder) FACTORY_CREATE(M4vH263Decoder) FACTORY_CREATE(VorbisDecoder) FACTORY_CREATE(VPXDecoder) @@ -124,6 +126,7 @@ static sp<MediaSource> InstantiateSoftwareCodec( FACTORY_REF(AMRWBDecoder) FACTORY_REF(AACDecoder) FACTORY_REF(AVCDecoder) + FACTORY_REF(G711Decoder) FACTORY_REF(M4vH263Decoder) FACTORY_REF(VorbisDecoder) FACTORY_REF(VPXDecoder) @@ -155,6 +158,8 @@ static const CodecInfo kDecoderInfo[] = { { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" }, { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" }, // { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" }, + { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" }, + { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" }, { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" }, @@ -1680,6 +1685,14 @@ void OMXCodec::on_message(const omx_message &msg) { MediaBuffer *buffer = info->mMediaBuffer; + if (msg.u.extended_buffer_data.range_offset + + msg.u.extended_buffer_data.range_length + > buffer->size()) { + CODEC_LOGE( + "Codec lied about its buffer size requirements, " + "sending a buffer larger than the originally " + "advertised size in FILL_BUFFER_DONE!"); + } buffer->set_range( msg.u.extended_buffer_data.range_offset, msg.u.extended_buffer_data.range_length); diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp index 641a876288ea..b699d8f51e6b 100644 --- a/media/libstagefright/OggExtractor.cpp +++ b/media/libstagefright/OggExtractor.cpp @@ -181,6 +181,8 @@ status_t OggSource::read( } #endif + packet->meta_data()->setInt32(kKeyIsSyncFrame, 1); + *out = packet; return OK; diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index 2e62f9ff136b..27faf4fbf5d2 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -55,6 +55,8 @@ SampleTable::SampleTable(const sp<DataSource> &source) mTimeToSample(NULL), mSyncSampleOffset(-1), mNumSyncSamples(0), + mSyncSamples(NULL), + mLastSyncSampleIndex(0), mSampleToChunkEntries(NULL) { mSampleIterator = new SampleIterator(this); } @@ -63,6 +65,9 @@ SampleTable::~SampleTable() { delete[] mSampleToChunkEntries; mSampleToChunkEntries = NULL; + delete[] mSyncSamples; + mSyncSamples = NULL; + delete[] mTimeToSample; mTimeToSample = NULL; @@ -278,6 +283,18 @@ status_t SampleTable::setSyncSampleParams(off_t data_offset, size_t data_size) { if (mNumSyncSamples < 2) { LOGW("Table of sync samples is empty or has only a single entry!"); } + + mSyncSamples = new uint32_t[mNumSyncSamples]; + size_t size = mNumSyncSamples * sizeof(uint32_t); + if (mDataSource->readAt(mSyncSampleOffset + 8, mSyncSamples, size) + != (ssize_t)size) { + return ERROR_IO; + } + + for (size_t i = 0; i < mNumSyncSamples; ++i) { + mSyncSamples[i] = ntohl(mSyncSamples[i]) - 1; + } + return OK; } @@ -394,14 +411,7 @@ status_t SampleTable::findSyncSampleNear( uint32_t left = 0; while (left < mNumSyncSamples) { - uint32_t x; - if (mDataSource->readAt( - mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) { - return ERROR_IO; - } - - x = ntohl(x); - --x; + uint32_t x = mSyncSamples[left]; if (x >= start_sample_index) { break; @@ -421,14 +431,7 @@ status_t SampleTable::findSyncSampleNear( --x; if (left + 1 < mNumSyncSamples) { - uint32_t y; - if (mDataSource->readAt( - mSyncSampleOffset + 8 + (left + 1) * 4, &y, 4) != 4) { - return ERROR_IO; - } - - y = ntohl(y); - --y; + uint32_t y = mSyncSamples[left + 1]; // our sample lies between sync samples x and y. @@ -486,13 +489,7 @@ status_t SampleTable::findSyncSampleNear( return ERROR_OUT_OF_RANGE; } - if (mDataSource->readAt( - mSyncSampleOffset + 8 + (left + 1) * 4, &x, 4) != 4) { - return ERROR_IO; - } - - x = ntohl(x); - --x; + x = mSyncSamples[left + 1]; CHECK(x >= start_sample_index); } @@ -532,13 +529,7 @@ status_t SampleTable::findThumbnailSample(uint32_t *sample_index) { } for (size_t i = 0; i < numSamplesToScan; ++i) { - uint32_t x; - if (mDataSource->readAt( - mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) { - return ERROR_IO; - } - x = ntohl(x); - --x; + uint32_t x = mSyncSamples[i]; // Now x is a sample index. size_t sampleSize; @@ -568,7 +559,8 @@ status_t SampleTable::getMetaDataForSample( uint32_t sampleIndex, off_t *offset, size_t *size, - uint32_t *decodingTime) { + uint32_t *decodingTime, + bool *isSyncSample) { Mutex::Autolock autoLock(mLock); status_t err; @@ -588,6 +580,28 @@ status_t SampleTable::getMetaDataForSample( *decodingTime = mSampleIterator->getSampleTime(); } + if (isSyncSample) { + *isSyncSample = false; + if (mSyncSampleOffset < 0) { + // Every sample is a sync sample. + *isSyncSample = true; + } else { + size_t i = (mLastSyncSampleIndex < mNumSyncSamples) + && (mSyncSamples[mLastSyncSampleIndex] <= sampleIndex) + ? mLastSyncSampleIndex : 0; + + while (i < mNumSyncSamples && mSyncSamples[i] < sampleIndex) { + ++i; + } + + if (i < mNumSyncSamples && mSyncSamples[i] == sampleIndex) { + *isSyncSample = true; + } + + mLastSyncSampleIndex = i; + } + } + return OK; } diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp index 7c2b07e4d30d..8d820c0dd69d 100644 --- a/media/libstagefright/WAVExtractor.cpp +++ b/media/libstagefright/WAVExtractor.cpp @@ -31,7 +31,11 @@ namespace android { -static uint16_t WAVE_FORMAT_PCM = 1; +enum { + WAVE_FORMAT_PCM = 1, + WAVE_FORMAT_ALAW = 6, + WAVE_FORMAT_MULAW = 7, +}; static uint32_t U32_LE_AT(const uint8_t *ptr) { return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0]; @@ -45,6 +49,7 @@ struct WAVSource : public MediaSource { WAVSource( const sp<DataSource> &dataSource, const sp<MetaData> &meta, + uint16_t waveFormat, int32_t bitsPerSample, off_t offset, size_t size); @@ -63,6 +68,7 @@ private: sp<DataSource> mDataSource; sp<MetaData> mMeta; + uint16_t mWaveFormat; int32_t mSampleRate; int32_t mNumChannels; int32_t mBitsPerSample; @@ -108,7 +114,7 @@ sp<MediaSource> WAVExtractor::getTrack(size_t index) { return new WAVSource( mDataSource, mTrackMeta, - mBitsPerSample, mDataOffset, mDataSize); + mWaveFormat, mBitsPerSample, mDataOffset, mDataSize); } sp<MetaData> WAVExtractor::getTrackMetaData( @@ -160,8 +166,10 @@ status_t WAVExtractor::init() { return NO_INIT; } - uint16_t format = U16_LE_AT(formatSpec); - if (format != WAVE_FORMAT_PCM) { + mWaveFormat = U16_LE_AT(formatSpec); + if (mWaveFormat != WAVE_FORMAT_PCM + && mWaveFormat != WAVE_FORMAT_ALAW + && mWaveFormat != WAVE_FORMAT_MULAW) { return ERROR_UNSUPPORTED; } @@ -178,9 +186,17 @@ status_t WAVExtractor::init() { mBitsPerSample = U16_LE_AT(&formatSpec[14]); - if (mBitsPerSample != 8 && mBitsPerSample != 16 - && mBitsPerSample != 24) { - return ERROR_UNSUPPORTED; + if (mWaveFormat == WAVE_FORMAT_PCM) { + if (mBitsPerSample != 8 && mBitsPerSample != 16 + && mBitsPerSample != 24) { + return ERROR_UNSUPPORTED; + } + } else { + CHECK(mWaveFormat == WAVE_FORMAT_MULAW + || mWaveFormat == WAVE_FORMAT_ALAW); + if (mBitsPerSample != 8) { + return ERROR_UNSUPPORTED; + } } mValidFormat = true; @@ -190,7 +206,23 @@ status_t WAVExtractor::init() { mDataSize = chunkSize; mTrackMeta = new MetaData; - mTrackMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + + switch (mWaveFormat) { + case WAVE_FORMAT_PCM: + mTrackMeta->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + break; + case WAVE_FORMAT_ALAW: + mTrackMeta->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_ALAW); + break; + default: + CHECK_EQ(mWaveFormat, WAVE_FORMAT_MULAW); + mTrackMeta->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_MLAW); + break; + } + mTrackMeta->setInt32(kKeyChannelCount, mNumChannels); mTrackMeta->setInt32(kKeySampleRate, mSampleRate); @@ -217,10 +249,12 @@ const size_t WAVSource::kMaxFrameSize = 32768; WAVSource::WAVSource( const sp<DataSource> &dataSource, const sp<MetaData> &meta, + uint16_t waveFormat, int32_t bitsPerSample, off_t offset, size_t size) : mDataSource(dataSource), mMeta(meta), + mWaveFormat(waveFormat), mSampleRate(0), mNumChannels(0), mBitsPerSample(bitsPerSample), @@ -312,43 +346,45 @@ status_t WAVSource::read( buffer->set_range(0, n); - if (mBitsPerSample == 8) { - // Convert 8-bit unsigned samples to 16-bit signed. + if (mWaveFormat == WAVE_FORMAT_PCM) { + if (mBitsPerSample == 8) { + // Convert 8-bit unsigned samples to 16-bit signed. - MediaBuffer *tmp; - CHECK_EQ(mGroup->acquire_buffer(&tmp), OK); + MediaBuffer *tmp; + CHECK_EQ(mGroup->acquire_buffer(&tmp), OK); - // The new buffer holds the sample number of samples, but each - // one is 2 bytes wide. - tmp->set_range(0, 2 * n); + // The new buffer holds the sample number of samples, but each + // one is 2 bytes wide. + tmp->set_range(0, 2 * n); - int16_t *dst = (int16_t *)tmp->data(); - const uint8_t *src = (const uint8_t *)buffer->data(); - while (n-- > 0) { - *dst++ = ((int16_t)(*src) - 128) * 256; - ++src; - } + int16_t *dst = (int16_t *)tmp->data(); + const uint8_t *src = (const uint8_t *)buffer->data(); + while (n-- > 0) { + *dst++ = ((int16_t)(*src) - 128) * 256; + ++src; + } - buffer->release(); - buffer = tmp; - } else if (mBitsPerSample == 24) { - // Convert 24-bit signed samples to 16-bit signed. - - const uint8_t *src = - (const uint8_t *)buffer->data() + buffer->range_offset(); - int16_t *dst = (int16_t *)src; - - size_t numSamples = buffer->range_length() / 3; - for (size_t i = 0; i < numSamples; ++i) { - int32_t x = (int32_t)(src[0] | src[1] << 8 | src[2] << 16); - x = (x << 8) >> 8; // sign extension - - x = x >> 8; - *dst++ = (int16_t)x; - src += 3; - } + buffer->release(); + buffer = tmp; + } else if (mBitsPerSample == 24) { + // Convert 24-bit signed samples to 16-bit signed. + + const uint8_t *src = + (const uint8_t *)buffer->data() + buffer->range_offset(); + int16_t *dst = (int16_t *)src; + + size_t numSamples = buffer->range_length() / 3; + for (size_t i = 0; i < numSamples; ++i) { + int32_t x = (int32_t)(src[0] | src[1] << 8 | src[2] << 16); + x = (x << 8) >> 8; // sign extension - buffer->set_range(buffer->range_offset(), 2 * numSamples); + x = x >> 8; + *dst++ = (int16_t)x; + src += 3; + } + + buffer->set_range(buffer->range_offset(), 2 * numSamples); + } } size_t bytesPerSample = mBitsPerSample >> 3; @@ -358,6 +394,7 @@ status_t WAVSource::read( 1000000LL * (mCurrentPos - mOffset) / (mNumChannels * bytesPerSample) / mSampleRate); + buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); *out = buffer; diff --git a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp index d5eb15628789..6e7427918630 100644 --- a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp +++ b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp @@ -337,14 +337,18 @@ status_t AVCEncoder::read( MediaBuffer *outputBuffer; CHECK_EQ(OK, mGroup->acquire_buffer(&outputBuffer)); - uint8_t *outPtr = (uint8_t *) outputBuffer->data(); - uint32_t dataLength = outputBuffer->size(); + + // Add 4 bytes for the start code 0x00000001 + uint8_t *outPtr = (uint8_t *) outputBuffer->data() + 4; + uint32_t dataLength = outputBuffer->size() - 4; int32_t type; AVCEnc_Status encoderStatus = AVCENC_SUCCESS; - // Return SPS and PPS for the first two buffers - if (!mSpsPpsHeaderReceived) { + // Combine SPS and PPS and place them in the very first output buffer + // SPS and PPS are separated by start code 0x00000001 + // Assume that we have exactly one SPS and exactly one PPS. + while (!mSpsPpsHeaderReceived && mNumInputFrames <= 0) { encoderStatus = PVAVCEncodeNAL(mHandle, outPtr, &dataLength, &type); if (encoderStatus == AVCENC_WRONG_STATE) { mSpsPpsHeaderReceived = true; @@ -352,11 +356,22 @@ status_t AVCEncoder::read( } else { switch (type) { case AVC_NALTYPE_SPS: + ++mNumInputFrames; + memcpy(outputBuffer->data(), "\x00\x00\x00\x01", 4); + outputBuffer->set_range(0, dataLength + 4); + outPtr += (dataLength + 4); // 4 bytes for next start code + dataLength = outputBuffer->size() - + (outputBuffer->range_length() + 4); + break; case AVC_NALTYPE_PPS: - LOGV("%s received", - (type == AVC_NALTYPE_SPS)? "SPS": "PPS"); ++mNumInputFrames; - outputBuffer->set_range(0, dataLength); + memcpy(((uint8_t *) outputBuffer->data()) + + outputBuffer->range_length(), + "\x00\x00\x00\x01", 4); + outputBuffer->set_range(0, + dataLength + outputBuffer->range_length() + 4); + outputBuffer->meta_data()->setInt32(kKeyIsCodecConfig, 1); + outputBuffer->meta_data()->setInt64(kKeyTime, 0); *out = outputBuffer; return OK; default: @@ -376,8 +391,18 @@ status_t AVCEncoder::read( if (err != OK) { LOGE("Failed to read input video frame: %d", err); outputBuffer->release(); + mInputBuffer->release(); + mInputBuffer = NULL; return err; } + + if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) { + outputBuffer->release(); + mInputBuffer->release(); + mInputBuffer = NULL; + return UNKNOWN_ERROR; + } + int64_t timeUs; CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); outputBuffer->meta_data()->setInt64(kKeyTime, timeUs); diff --git a/media/libstagefright/codecs/g711/Android.mk b/media/libstagefright/codecs/g711/Android.mk new file mode 100644 index 000000000000..2e431205aa6d --- /dev/null +++ b/media/libstagefright/codecs/g711/Android.mk @@ -0,0 +1,4 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/media/libstagefright/codecs/g711/dec/Android.mk b/media/libstagefright/codecs/g711/dec/Android.mk new file mode 100644 index 000000000000..cfb9fe465868 --- /dev/null +++ b/media/libstagefright/codecs/g711/dec/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + G711Decoder.cpp + +LOCAL_C_INCLUDES := \ + frameworks/base/media/libstagefright/include \ + +LOCAL_MODULE := libstagefright_g711dec + +include $(BUILD_STATIC_LIBRARY) diff --git a/media/libstagefright/codecs/g711/dec/G711Decoder.cpp b/media/libstagefright/codecs/g711/dec/G711Decoder.cpp new file mode 100644 index 000000000000..4414e4ed4b48 --- /dev/null +++ b/media/libstagefright/codecs/g711/dec/G711Decoder.cpp @@ -0,0 +1,213 @@ +/* + * 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 "G711Decoder" +#include <utils/Log.h> + +#include "G711Decoder.h" + +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> + +static const size_t kMaxNumSamplesPerFrame = 16384; + +namespace android { + +G711Decoder::G711Decoder(const sp<MediaSource> &source) + : mSource(source), + mStarted(false), + mBufferGroup(NULL) { +} + +G711Decoder::~G711Decoder() { + if (mStarted) { + stop(); + } +} + +status_t G711Decoder::start(MetaData *params) { + CHECK(!mStarted); + + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + mIsMLaw = false; + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) { + mIsMLaw = true; + } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW)) { + return ERROR_UNSUPPORTED; + } + + mBufferGroup = new MediaBufferGroup; + mBufferGroup->add_buffer( + new MediaBuffer(kMaxNumSamplesPerFrame * sizeof(int16_t))); + + mSource->start(); + + mStarted = true; + + return OK; +} + +status_t G711Decoder::stop() { + CHECK(mStarted); + + delete mBufferGroup; + mBufferGroup = NULL; + + mSource->stop(); + + mStarted = false; + + return OK; +} + +sp<MetaData> G711Decoder::getFormat() { + sp<MetaData> srcFormat = mSource->getFormat(); + + int32_t numChannels; + int32_t sampleRate; + + CHECK(srcFormat->findInt32(kKeyChannelCount, &numChannels)); + CHECK(srcFormat->findInt32(kKeySampleRate, &sampleRate)); + + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + meta->setInt32(kKeyChannelCount, numChannels); + meta->setInt32(kKeySampleRate, sampleRate); + + int64_t durationUs; + if (srcFormat->findInt64(kKeyDuration, &durationUs)) { + meta->setInt64(kKeyDuration, durationUs); + } + + meta->setCString(kKeyDecoderComponent, "G711Decoder"); + + return meta; +} + +status_t G711Decoder::read( + MediaBuffer **out, const ReadOptions *options) { + status_t err; + + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + CHECK(seekTimeUs >= 0); + } else { + seekTimeUs = -1; + } + + MediaBuffer *inBuffer; + err = mSource->read(&inBuffer, options); + + if (err != OK) { + return err; + } + + if (inBuffer->range_length() > kMaxNumSamplesPerFrame) { + LOGE("input buffer too large (%d).", inBuffer->range_length()); + + inBuffer->release(); + inBuffer = NULL; + + return ERROR_UNSUPPORTED; + } + + int64_t timeUs; + CHECK(inBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); + + const uint8_t *inputPtr = + (const uint8_t *)inBuffer->data() + inBuffer->range_offset(); + + MediaBuffer *outBuffer; + CHECK_EQ(mBufferGroup->acquire_buffer(&outBuffer), OK); + + if (mIsMLaw) { + DecodeMLaw( + static_cast<int16_t *>(outBuffer->data()), + inputPtr, inBuffer->range_length()); + } else { + DecodeALaw( + static_cast<int16_t *>(outBuffer->data()), + inputPtr, inBuffer->range_length()); + } + + // Each 8-bit byte is converted into a 16-bit sample. + outBuffer->set_range(0, inBuffer->range_length() * 2); + + outBuffer->meta_data()->setInt64(kKeyTime, timeUs); + + inBuffer->release(); + inBuffer = NULL; + + *out = outBuffer; + + return OK; +} + +// static +void G711Decoder::DecodeALaw( + int16_t *out, const uint8_t *in, size_t inSize) { + while (inSize-- > 0) { + int32_t x = *in++; + + int32_t ix = x ^ 0x55; + ix &= 0x7f; + + int32_t iexp = ix >> 4; + int32_t mant = ix & 0x0f; + + if (iexp > 0) { + mant += 16; + } + + mant = (mant << 4) + 8; + + if (iexp > 1) { + mant = mant << (iexp - 1); + } + + *out++ = (x > 127) ? mant : -mant; + } +} + +// static +void G711Decoder::DecodeMLaw( + int16_t *out, const uint8_t *in, size_t inSize) { + while (inSize-- > 0) { + int32_t x = *in++; + + int32_t mantissa = ~x; + int32_t exponent = (mantissa >> 4) & 7; + int32_t segment = exponent + 1; + mantissa &= 0x0f; + + int32_t step = 4 << segment; + + int32_t abs = (0x80l << exponent) + step * mantissa + step / 2 - 4 * 33; + + *out++ = (x < 0x80) ? -abs : abs; + } +} + +} // namespace android diff --git a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp index 5002442e991b..1bef0e91d09f 100644 --- a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp +++ b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp @@ -292,8 +292,18 @@ status_t M4vH263Encoder::read( if (OK != mSource->read(&mInputBuffer, options)) { LOGE("Failed to read from data source"); outputBuffer->release(); + mInputBuffer->release(); + mInputBuffer = NULL; return UNKNOWN_ERROR; } + + if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) { + outputBuffer->release(); + mInputBuffer->release(); + mInputBuffer = NULL; + return UNKNOWN_ERROR; + } + int64_t timeUs; CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); if (mNextModTimeUs > timeUs) { diff --git a/media/libstagefright/include/G711Decoder.h b/media/libstagefright/include/G711Decoder.h new file mode 100644 index 000000000000..8b5143ac896b --- /dev/null +++ b/media/libstagefright/include/G711Decoder.h @@ -0,0 +1,57 @@ +/* + * 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 G711_DECODER_H_ + +#define G711_DECODER_H_ + +#include <media/stagefright/MediaSource.h> + +namespace android { + +struct MediaBufferGroup; + +struct G711Decoder : public MediaSource { + G711Decoder(const sp<MediaSource> &source); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + +protected: + virtual ~G711Decoder(); + +private: + sp<MediaSource> mSource; + bool mStarted; + bool mIsMLaw; + + MediaBufferGroup *mBufferGroup; + + static void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize); + static void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize); + + G711Decoder(const G711Decoder &); + G711Decoder &operator=(const G711Decoder &); +}; + +} // namespace android + +#endif // G711_DECODER_H_ diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h index a2b2c998a0b5..f83069032425 100644 --- a/media/libstagefright/include/SampleTable.h +++ b/media/libstagefright/include/SampleTable.h @@ -60,7 +60,8 @@ public: uint32_t sampleIndex, off_t *offset, size_t *size, - uint32_t *decodingTime); + uint32_t *decodingTime, + bool *isSyncSample = NULL); enum { kFlagBefore, @@ -105,6 +106,8 @@ private: off_t mSyncSampleOffset; uint32_t mNumSyncSamples; + uint32_t *mSyncSamples; + size_t mLastSyncSampleIndex; SampleIterator *mSampleIterator; diff --git a/media/libstagefright/include/WAVExtractor.h b/media/libstagefright/include/WAVExtractor.h index 93849429ff19..3e847b9e5e94 100644 --- a/media/libstagefright/include/WAVExtractor.h +++ b/media/libstagefright/include/WAVExtractor.h @@ -43,6 +43,7 @@ private: sp<DataSource> mDataSource; status_t mInitCheck; bool mValidFormat; + uint16_t mWaveFormat; uint16_t mNumChannels; uint32_t mSampleRate; uint16_t mBitsPerSample; diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp index 3739be1f3bbd..71f6587637b3 100644 --- a/media/libstagefright/matroska/MatroskaExtractor.cpp +++ b/media/libstagefright/matroska/MatroskaExtractor.cpp @@ -296,6 +296,7 @@ status_t MatroskaSource::read( MediaBuffer *buffer = new MediaBuffer(size + 2); buffer->meta_data()->setInt64(kKeyTime, timeUs); + buffer->meta_data()->setInt32(kKeyIsSyncFrame, block->IsKey()); long res = block->Read( mExtractor->mReader, (unsigned char *)buffer->data() + 2); diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp index a5777045f4db..395cd28c8828 100644 --- a/media/libstagefright/rtsp/APacketSource.cpp +++ b/media/libstagefright/rtsp/APacketSource.cpp @@ -356,24 +356,10 @@ status_t APacketSource::read( if (!mBuffers.empty()) { const sp<ABuffer> buffer = *mBuffers.begin(); - uint64_t ntpTime; - CHECK(buffer->meta()->findInt64( - "ntp-time", (int64_t *)&ntpTime)); - MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size()); - mediaBuffer->meta_data()->setInt64(kKeyNTPTime, ntpTime); - - if (mFirstAccessUnit) { - mFirstAccessUnit = false; - mFirstAccessUnitNTP = ntpTime; - } - if (ntpTime > mFirstAccessUnitNTP) { - ntpTime -= mFirstAccessUnitNTP; - } else { - ntpTime = 0; - } - int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32)); + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs); @@ -390,10 +376,29 @@ status_t APacketSource::read( void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) { int32_t damaged; if (buffer->meta()->findInt32("damaged", &damaged) && damaged) { - // LOG(VERBOSE) << "discarding damaged AU"; + LOG(INFO) << "discarding damaged AU"; return; } + uint64_t ntpTime; + CHECK(buffer->meta()->findInt64( + "ntp-time", (int64_t *)&ntpTime)); + + if (mFirstAccessUnit) { + mFirstAccessUnit = false; + mFirstAccessUnitNTP = ntpTime; + } + + if (ntpTime > mFirstAccessUnitNTP) { + ntpTime -= mFirstAccessUnitNTP; + } else { + ntpTime = 0; + } + + int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32)); + + buffer->meta()->setInt64("timeUs", timeUs); + Mutex::Autolock autoLock(mLock); mBuffers.push_back(buffer); mCondition.signal(); diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp index 5bd306b41a08..469af3e256c3 100644 --- a/media/libstagefright/rtsp/ARTPConnection.cpp +++ b/media/libstagefright/rtsp/ARTPConnection.cpp @@ -28,8 +28,6 @@ #include <arpa/inet.h> #include <sys/socket.h> -#define IGNORE_RTCP_TIME 0 - namespace android { static const size_t kMaxUDPSize = 1500; @@ -61,8 +59,9 @@ struct ARTPConnection::StreamInfo { struct sockaddr_in mRemoteRTCPAddr; }; -ARTPConnection::ARTPConnection() - : mPollEventPending(false), +ARTPConnection::ARTPConnection(uint32_t flags) + : mFlags(flags), + mPollEventPending(false), mLastReceiverReportTimeUs(-1) { } @@ -280,7 +279,10 @@ void ARTPConnection::onPollStreams() { sp<ARTPSource> source = s->mSources.valueAt(i); source->addReceiverReport(buffer); - source->addFIR(buffer); + + if (mFlags & kRegularlyRequestFIR) { + source->addFIR(buffer); + } } if (buffer->size() > 0) { @@ -405,13 +407,11 @@ status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) { buffer->setInt32Data(u16at(&data[2])); buffer->setRange(payloadOffset, size - payloadOffset); -#if IGNORE_RTCP_TIME - if (!source->timeEstablished()) { + if ((mFlags & kFakeTimestamps) && !source->timeEstablished()) { source->timeUpdate(rtpTime, 0); - source->timeUpdate(rtpTime + 20, 0x100000000ll); + source->timeUpdate(rtpTime + 90000, 0x100000000ll); CHECK(source->timeEstablished()); } -#endif source->processRTPPacket(buffer); @@ -533,9 +533,9 @@ status_t ARTPConnection::parseSR( sp<ARTPSource> source = findSource(s, id); -#if !IGNORE_RTCP_TIME - source->timeUpdate(rtpTime, ntpTime); -#endif + if ((mFlags & kFakeTimestamps) == 0) { + source->timeUpdate(rtpTime, ntpTime); + } return 0; } diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h index 49839ad66b03..c53519977f0a 100644 --- a/media/libstagefright/rtsp/ARTPConnection.h +++ b/media/libstagefright/rtsp/ARTPConnection.h @@ -28,7 +28,12 @@ struct ARTPSource; struct ASessionDescription; struct ARTPConnection : public AHandler { - ARTPConnection(); + enum Flags { + kFakeTimestamps = 1, + kRegularlyRequestFIR = 2, + }; + + ARTPConnection(uint32_t flags = 0); void addStream( int rtpSocket, int rtcpSocket, @@ -56,6 +61,8 @@ private: static const int64_t kSelectTimeoutUs; + uint32_t mFlags; + struct StreamInfo; List<StreamInfo> mStreams; diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp index 0e0f45ad1a17..e0820782a9c9 100644 --- a/media/libstagefright/rtsp/ARTPSession.cpp +++ b/media/libstagefright/rtsp/ARTPSession.cpp @@ -40,7 +40,10 @@ status_t ARTPSession::setup(const sp<ASessionDescription> &desc) { mDesc = desc; - mRTPConn = new ARTPConnection; + mRTPConn = new ARTPConnection( + ARTPConnection::kFakeTimestamps + | ARTPConnection::kRegularlyRequestFIR); + looper()->registerHandler(mRTPConn); for (size_t i = 1; i < mDesc->countTracks(); ++i) { diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp index e08183e50259..225f6e8d1f78 100644 --- a/media/libstagefright/rtsp/ARTPSource.cpp +++ b/media/libstagefright/rtsp/ARTPSource.cpp @@ -98,7 +98,7 @@ void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) { mNTPTime[mNumTimes] = ntpTime; mRTPTime[mNumTimes++] = rtpTime; - if (mNumTimes == 2) { + if (timeEstablished()) { for (List<sp<ABuffer> >::iterator it = mQueue.begin(); it != mQueue.end(); ++it) { sp<AMessage> meta = (*it)->meta(); @@ -112,13 +112,6 @@ void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) { } bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) { -#if 1 - if (mNumTimes != 2) { - // Drop incoming packets until we've established a time base. - return false; - } -#endif - uint32_t seqNum = (uint32_t)buffer->int32Data(); if (mNumTimes == 2) { diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp index c159e2095d2c..9bfd00f44be8 100644 --- a/media/mtp/MtpDataPacket.cpp +++ b/media/mtp/MtpDataPacket.cpp @@ -266,6 +266,13 @@ void MtpDataPacket::putAUInt16(const uint16_t* values, int count) { putUInt16(*values++); } +void MtpDataPacket::putAUInt16(const UInt16List* values) { + size_t count = (values ? values->size() : 0); + putUInt32(count); + for (size_t i = 0; i < count; i++) + putUInt16((*values)[i]); +} + void MtpDataPacket::putAInt32(const int32_t* values, int count) { putUInt32(count); for (int i = 0; i < count; i++) diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h index e8314d7237a8..b458286ab89e 100644 --- a/media/mtp/MtpDataPacket.h +++ b/media/mtp/MtpDataPacket.h @@ -74,6 +74,7 @@ public: void putAUInt8(const uint8_t* values, int count); void putAInt16(const int16_t* values, int count); void putAUInt16(const uint16_t* values, int count); + void putAUInt16(const UInt16List* values); void putAInt32(const int32_t* values, int count); void putAUInt32(const uint32_t* values, int count); void putAUInt32(const UInt32List* list); diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index 02bb0d9f88c0..17823dfac156 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -51,6 +51,13 @@ public: MtpObjectFormat format, MtpObjectHandle parent) = 0; + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats() = 0; + virtual MtpObjectFormatList* getSupportedCaptureFormats() = 0; + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format) = 0; + virtual MtpDevicePropertyList* getSupportedDeviceProperties() = 0; + virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle, MtpObjectProperty property, MtpDataPacket& packet) = 0; diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp index 9ded6e2e4085..d6b107d7434c 100644 --- a/media/mtp/MtpDebug.cpp +++ b/media/mtp/MtpDebug.cpp @@ -18,12 +18,12 @@ namespace android { -struct OperationCodeEntry { +struct CodeEntry { const char* name; - MtpOperationCode code; + uint16_t code; }; -static const OperationCodeEntry sOperationCodes[] = { +static const CodeEntry sOperationCodes[] = { { "MTP_OPERATION_GET_DEVICE_INFO", 0x1001 }, { "MTP_OPERATION_OPEN_SESSION", 0x1002 }, { "MTP_OPERATION_CLOSE_SESSION", 0x1003 }, @@ -62,15 +62,326 @@ static const OperationCodeEntry sOperationCodes[] = { { 0, 0 }, }; +static const CodeEntry sFormatCodes[] = { + { "MTP_OPERATION_GET_DEVICE_INFO", 0x1001 }, + { "MTP_FORMAT_UNDEFINED", 0x3000 }, + { "MTP_FORMAT_ASSOCIATION", 0x3001 }, + { "MTP_FORMAT_SCRIPT", 0x3002 }, + { "MTP_FORMAT_EXECUTABLE", 0x3003 }, + { "MTP_FORMAT_TEXT", 0x3004 }, + { "MTP_FORMAT_HTML", 0x3005 }, + { "MTP_FORMAT_DPOF", 0x3006 }, + { "MTP_FORMAT_AIFF", 0x3007 }, + { "MTP_FORMAT_WAV", 0x3008 }, + { "MTP_FORMAT_MP3", 0x3009 }, + { "MTP_FORMAT_AVI", 0x300A }, + { "MTP_FORMAT_MPEG", 0x300B }, + { "MTP_FORMAT_ASF", 0x300C }, + { "MTP_FORMAT_DEFINED", 0x3800 }, + { "MTP_FORMAT_EXIF_JPEG", 0x3801 }, + { "MTP_FORMAT_TIFF_EP", 0x3802 }, + { "MTP_FORMAT_FLASHPIX", 0x3803 }, + { "MTP_FORMAT_BMP", 0x3804 }, + { "MTP_FORMAT_CIFF", 0x3805 }, + { "MTP_FORMAT_GIF", 0x3807 }, + { "MTP_FORMAT_JFIF", 0x3808 }, + { "MTP_FORMAT_CD", 0x3809 }, + { "MTP_FORMAT_PICT", 0x380A }, + { "MTP_FORMAT_PNG", 0x380B }, + { "MTP_FORMAT_TIFF", 0x380D }, + { "MTP_FORMAT_TIFF_IT", 0x380E }, + { "MTP_FORMAT_JP2", 0x380F }, + { "MTP_FORMAT_JPX", 0x3810 }, + { "MTP_FORMAT_UNDEFINED_FIRMWARE", 0xB802 }, + { "MTP_FORMAT_WINDOWS_IMAGE_FORMAT", 0xB881 }, + { "MTP_FORMAT_UNDEFINED_AUDIO", 0xB900 }, + { "MTP_FORMAT_WMA", 0xB901 }, + { "MTP_FORMAT_OGG", 0xB902 }, + { "MTP_FORMAT_AAC", 0xB903 }, + { "MTP_FORMAT_AUDIBLE", 0xB904 }, + { "MTP_FORMAT_FLAC", 0xB906 }, + { "MTP_FORMAT_UNDEFINED_VIDEO", 0xB980 }, + { "MTP_FORMAT_WMV", 0xB981 }, + { "MTP_FORMAT_MP4_CONTAINER", 0xB982 }, + { "MTP_FORMAT_MP2", 0xB983 }, + { "MTP_FORMAT_3GP_CONTAINER", 0xB984 }, + { "MTP_FORMAT_UNDEFINED_COLLECTION", 0xBA00 }, + { "MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM", 0xBA01 }, + { "MTP_FORMAT_ABSTRACT_IMAGE_ALBUM", 0xBA02 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_ALBUM", 0xBA03 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_ALBUM", 0xBA04 }, + { "MTP_FORMAT_ABSTRACT_AV_PLAYLIST", 0xBA05 }, + { "MTP_FORMAT_ABSTRACT_CONTACT_GROUP", 0xBA06 }, + { "MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER", 0xBA07 }, + { "MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION", 0xBA08 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST", 0xBA09 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST", 0xBA0A }, + { "MTP_FORMAT_ABSTRACT_MEDIACAST", 0xBA0B }, + { "MTP_FORMAT_WPL_PLAYLIST", 0xBA10 }, + { "MTP_FORMAT_M3U_PLAYLIST", 0xBA11 }, + { "MTP_FORMAT_MPL_PLAYLIST", 0xBA12 }, + { "MTP_FORMAT_ASX_PLAYLIST", 0xBA13 }, + { "MTP_FORMAT_PLS_PLAYLIST", 0xBA14 }, + { "MTP_FORMAT_UNDEFINED_DOCUMENT", 0xBA80 }, + { "MTP_FORMAT_ABSTRACT_DOCUMENT", 0xBA81 }, + { "MTP_FORMAT_XML_DOCUMENT", 0xBA82 }, + { "MTP_FORMAT_MS_WORD_DOCUMENT", 0xBA83 }, + { "MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT", 0xBA84 }, + { "MTP_FORMAT_MS_EXCEL_SPREADSHEET", 0xBA85 }, + { "MTP_FORMAT_MS_POWERPOINT_PRESENTATION", 0xBA86 }, + { "MTP_FORMAT_UNDEFINED_MESSAGE", 0xBB00 }, + { "MTP_FORMAT_ABSTRACT_MESSSAGE", 0xBB01 }, + { "MTP_FORMAT_UNDEFINED_CONTACT", 0xBB80 }, + { "MTP_FORMAT_ABSTRACT_CONTACT", 0xBB81 }, + { "MTP_FORMAT_VCARD_2", 0xBB82 }, + { 0, 0 }, +}; -const char* MtpDebug::getOperationCodeName(MtpOperationCode code) { - const OperationCodeEntry* entry = sOperationCodes; +static const CodeEntry sObjectPropCodes[] = { + { "MTP_PROPERTY_STORAGE_ID", 0xDC01 }, + { "MTP_PROPERTY_OBJECT_FORMAT", 0xDC02 }, + { "MTP_PROPERTY_PROTECTION_STATUS", 0xDC03 }, + { "MTP_PROPERTY_OBJECT_SIZE", 0xDC04 }, + { "MTP_PROPERTY_ASSOCIATION_TYPE", 0xDC05 }, + { "MTP_PROPERTY_ASSOCIATION_DESC", 0xDC06 }, + { "MTP_PROPERTY_OBJECT_FILE_NAME", 0xDC07 }, + { "MTP_PROPERTY_DATE_CREATED", 0xDC08 }, + { "MTP_PROPERTY_DATE_MODIFIED", 0xDC09 }, + { "MTP_PROPERTY_KEYWORDS", 0xDC0A }, + { "MTP_PROPERTY_PARENT_OBJECT", 0xDC0B }, + { "MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS", 0xDC0C }, + { "MTP_PROPERTY_HIDDEN", 0xDC0D }, + { "MTP_PROPERTY_SYSTEM_OBJECT", 0xDC0E }, + { "MTP_PROPERTY_PERSISTENT_UID", 0xDC41 }, + { "MTP_PROPERTY_SYNC_ID", 0xDC42 }, + { "MTP_PROPERTY_PROPERTY_BAG", 0xDC43 }, + { "MTP_PROPERTY_NAME", 0xDC44 }, + { "MTP_PROPERTY_CREATED_BY", 0xDC45 }, + { "MTP_PROPERTY_ARTIST", 0xDC46 }, + { "MTP_PROPERTY_DATE_AUTHORED", 0xDC47 }, + { "MTP_PROPERTY_DESCRIPTION", 0xDC48 }, + { "MTP_PROPERTY_URL_REFERENCE", 0xDC49 }, + { "MTP_PROPERTY_LANGUAGE_LOCALE", 0xDC4A }, + { "MTP_PROPERTY_COPYRIGHT_INFORMATION", 0xDC4B }, + { "MTP_PROPERTY_SOURCE", 0xDC4C }, + { "MTP_PROPERTY_ORIGIN_LOCATION", 0xDC4D }, + { "MTP_PROPERTY_DATE_ADDED", 0xDC4E }, + { "MTP_PROPERTY_NON_CONSUMABLE", 0xDC4F }, + { "MTP_PROPERTY_CORRUPT_UNPLAYABLE", 0xDC50 }, + { "MTP_PROPERTY_PRODUCER_SERIAL_NUMBER", 0xDC51 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT", 0xDC81 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE", 0xDC82 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT", 0xDC83 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH", 0xDC84 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION", 0xDC85 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA", 0xDC86 }, + { "MTP_PROPERTY_WIDTH", 0xDC87 }, + { "MTP_PROPERTY_HEIGHT", 0xDC88 }, + { "MTP_PROPERTY_DURATION", 0xDC89 }, + { "MTP_PROPERTY_RATING", 0xDC8A }, + { "MTP_PROPERTY_TRACK", 0xDC8B }, + { "MTP_PROPERTY_GENRE", 0xDC8C }, + { "MTP_PROPERTY_CREDITS", 0xDC8D }, + { "MTP_PROPERTY_LYRICS", 0xDC8E }, + { "MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID", 0xDC8F }, + { "MTP_PROPERTY_PRODUCED_BY", 0xDC90 }, + { "MTP_PROPERTY_USE_COUNT", 0xDC91 }, + { "MTP_PROPERTY_SKIP_COUNT", 0xDC92 }, + { "MTP_PROPERTY_LAST_ACCESSED", 0xDC93 }, + { "MTP_PROPERTY_PARENTAL_RATING", 0xDC94 }, + { "MTP_PROPERTY_META_GENRE", 0xDC95 }, + { "MTP_PROPERTY_COMPOSER", 0xDC96 }, + { "MTP_PROPERTY_EFFECTIVE_RATING", 0xDC97 }, + { "MTP_PROPERTY_SUBTITLE", 0xDC98 }, + { "MTP_PROPERTY_ORIGINAL_RELEASE_DATE", 0xDC99 }, + { "MTP_PROPERTY_ALBUM_NAME", 0xDC9A }, + { "MTP_PROPERTY_ALBUM_ARTIST", 0xDC9B }, + { "MTP_PROPERTY_MOOD", 0xDC9C }, + { "MTP_PROPERTY_DRM_STATUS", 0xDC9D }, + { "MTP_PROPERTY_SUB_DESCRIPTION", 0xDC9E }, + { "MTP_PROPERTY_IS_CROPPED", 0xDCD1 }, + { "MTP_PROPERTY_IS_COLOUR_CORRECTED", 0xDCD2 }, + { "MTP_PROPERTY_IMAGE_BIT_DEPTH", 0xDCD3 }, + { "MTP_PROPERTY_F_NUMBER", 0xDCD4 }, + { "MTP_PROPERTY_EXPOSURE_TIME", 0xDCD5 }, + { "MTP_PROPERTY_EXPOSURE_INDEX", 0xDCD6 }, + { "MTP_PROPERTY_TOTAL_BITRATE", 0xDE91 }, + { "MTP_PROPERTY_BITRATE_TYPE", 0xDE92 }, + { "MTP_PROPERTY_SAMPLE_RATE", 0xDE93 }, + { "MTP_PROPERTY_NUMBER_OF_CHANNELS", 0xDE94 }, + { "MTP_PROPERTY_AUDIO_BIT_DEPTH", 0xDE95 }, + { "MTP_PROPERTY_SCAN_TYPE", 0xDE97 }, + { "MTP_PROPERTY_AUDIO_WAVE_CODEC", 0xDE99 }, + { "MTP_PROPERTY_AUDIO_BITRATE", 0xDE9A }, + { "MTP_PROPERTY_VIDEO_FOURCC_CODEC", 0xDE9B }, + { "MTP_PROPERTY_VIDEO_BITRATE", 0xDE9C }, + { "MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS", 0xDE9D }, + { "MTP_PROPERTY_KEYFRAME_DISTANCE", 0xDE9E }, + { "MTP_PROPERTY_BUFFER_SIZE", 0xDE9F }, + { "MTP_PROPERTY_ENCODING_QUALITY", 0xDEA0 }, + { "MTP_PROPERTY_ENCODING_PROFILE", 0xDEA1 }, + { "MTP_PROPERTY_DISPLAY_NAME", 0xDCE0 }, + { "MTP_PROPERTY_BODY_TEXT", 0xDCE1 }, + { "MTP_PROPERTY_SUBJECT", 0xDCE2 }, + { "MTP_PROPERTY_PRIORITY", 0xDCE3 }, + { "MTP_PROPERTY_GIVEN_NAME", 0xDD00 }, + { "MTP_PROPERTY_MIDDLE_NAMES", 0xDD01 }, + { "MTP_PROPERTY_FAMILY_NAME", 0xDD02 }, + { "MTP_PROPERTY_PREFIX", 0xDD03 }, + { "MTP_PROPERTY_SUFFIX", 0xDD04 }, + { "MTP_PROPERTY_PHONETIC_GIVEN_NAME", 0xDD05 }, + { "MTP_PROPERTY_PHONETIC_FAMILY_NAME", 0xDD06 }, + { "MTP_PROPERTY_EMAIL_PRIMARY", 0xDD07 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_1", 0xDD08 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_2", 0xDD09 }, + { "MTP_PROPERTY_EMAIL_BUSINESS_1", 0xDD0A }, + { "MTP_PROPERTY_EMAIL_BUSINESS_2", 0xDD0B }, + { "MTP_PROPERTY_EMAIL_OTHERS", 0xDD0C }, + { "MTP_PROPERTY_PHONE_NUMBER_PRIMARY", 0xDD0D }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL", 0xDD0E }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2", 0xDD0F }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS", 0xDD10 }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2", 0xDD11 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE", 0xDD12 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE_2", 0xDD13 }, + { "MTP_PROPERTY_FAX_NUMBER_PRIMARY", 0xDD14 }, + { "MTP_PROPERTY_FAX_NUMBER_PERSONAL", 0xDD15 }, + { "MTP_PROPERTY_FAX_NUMBER_BUSINESS", 0xDD16 }, + { "MTP_PROPERTY_PAGER_NUMBER", 0xDD17 }, + { "MTP_PROPERTY_PHONE_NUMBER_OTHERS", 0xDD18 }, + { "MTP_PROPERTY_PRIMARY_WEB_ADDRESS", 0xDD19 }, + { "MTP_PROPERTY_PERSONAL_WEB_ADDRESS", 0xDD1A }, + { "MTP_PROPERTY_BUSINESS_WEB_ADDRESS", 0xDD1B }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS", 0xDD1C }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2", 0xDD1D }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3", 0xDD1E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL", 0xDD1F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1", 0xDD20 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2", 0xDD21 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY", 0xDD22 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION", 0xDD23 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE", 0xDD24 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY", 0xDD25 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL", 0xDD26 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1", 0xDD27 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2", 0xDD28 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY", 0xDD29 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION", 0xDD2A }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE", 0xDD2B }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY", 0xDD2C }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL", 0xDD2D }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1", 0xDD2E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2", 0xDD2F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY", 0xDD30 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION", 0xDD31 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE", 0xDD32 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY", 0xDD33 }, + { "MTP_PROPERTY_ORGANIZATION_NAME", 0xDD34 }, + { "MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME", 0xDD35 }, + { "MTP_PROPERTY_ROLE", 0xDD36 }, + { "MTP_PROPERTY_BIRTHDATE", 0xDD37 }, + { "MTP_PROPERTY_MESSAGE_TO", 0xDD40 }, + { "MTP_PROPERTY_MESSAGE_CC", 0xDD41 }, + { "MTP_PROPERTY_MESSAGE_BCC", 0xDD42 }, + { "MTP_PROPERTY_MESSAGE_READ", 0xDD43 }, + { "MTP_PROPERTY_MESSAGE_RECEIVED_TIME", 0xDD44 }, + { "MTP_PROPERTY_MESSAGE_SENDER", 0xDD45 }, + { "MTP_PROPERTY_ACTIVITY_BEGIN_TIME", 0xDD50 }, + { "MTP_PROPERTY_ACTIVITY_END_TIME", 0xDD51 }, + { "MTP_PROPERTY_ACTIVITY_LOCATION", 0xDD52 }, + { "MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES", 0xDD54 }, + { "MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES", 0xDD55 }, + { "MTP_PROPERTY_ACTIVITY_RESOURCES", 0xDD56 }, + { "MTP_PROPERTY_ACTIVITY_ACCEPTED", 0xDD57 }, + { "MTP_PROPERTY_ACTIVITY_TENTATIVE", 0xDD58 }, + { "MTP_PROPERTY_ACTIVITY_DECLINED", 0xDD59 }, + { "MTP_PROPERTY_ACTIVITY_REMAINDER_TIME", 0xDD5A }, + { "MTP_PROPERTY_ACTIVITY_OWNER", 0xDD5B }, + { "MTP_PROPERTY_ACTIVITY_STATUS", 0xDD5C }, + { "MTP_PROPERTY_OWNER", 0xDD5D }, + { "MTP_PROPERTY_EDITOR", 0xDD5E }, + { "MTP_PROPERTY_WEBMASTER", 0xDD5F }, + { "MTP_PROPERTY_URL_SOURCE", 0xDD60 }, + { "MTP_PROPERTY_URL_DESTINATION", 0xDD61 }, + { "MTP_PROPERTY_TIME_BOOKMARK", 0xDD62 }, + { "MTP_PROPERTY_OBJECT_BOOKMARK", 0xDD63 }, + { "MTP_PROPERTY_BYTE_BOOKMARK", 0xDD64 }, + { "MTP_PROPERTY_LAST_BUILD_DATE", 0xDD70 }, + { "MTP_PROPERTY_TIME_TO_LIVE", 0xDD71 }, + { "MTP_PROPERTY_MEDIA_GUID", 0xDD72 }, + { 0, 0 }, +}; + +static const CodeEntry sDevicePropCodes[] = { + { "MTP_DEVICE_PROPERTY_UNDEFINED", 0x5000 }, + { "MTP_DEVICE_PROPERTY_BATTERY_LEVEL", 0x5001 }, + { "MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE", 0x5002 }, + { "MTP_DEVICE_PROPERTY_IMAGE_SIZE", 0x5003 }, + { "MTP_DEVICE_PROPERTY_COMPRESSION_SETTING", 0x5004 }, + { "MTP_DEVICE_PROPERTY_WHITE_BALANCE", 0x5005 }, + { "MTP_DEVICE_PROPERTY_RGB_GAIN", 0x5006 }, + { "MTP_DEVICE_PROPERTY_F_NUMBER", 0x5007 }, + { "MTP_DEVICE_PROPERTY_FOCAL_LENGTH", 0x5008 }, + { "MTP_DEVICE_PROPERTY_FOCUS_DISTANCE", 0x5009 }, + { "MTP_DEVICE_PROPERTY_FOCUS_MODE", 0x500A }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE", 0x500B }, + { "MTP_DEVICE_PROPERTY_FLASH_MODE", 0x500C }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_TIME", 0x500D }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE", 0x500E }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_INDEX", 0x500F }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION", 0x5010 }, + { "MTP_DEVICE_PROPERTY_DATETIME", 0x5011 }, + { "MTP_DEVICE_PROPERTY_CAPTURE_DELAY", 0x5012 }, + { "MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE", 0x5013 }, + { "MTP_DEVICE_PROPERTY_CONTRAST", 0x5014 }, + { "MTP_DEVICE_PROPERTY_SHARPNESS", 0x5015 }, + { "MTP_DEVICE_PROPERTY_DIGITAL_ZOOM", 0x5016 }, + { "MTP_DEVICE_PROPERTY_EFFECT_MODE", 0x5017 }, + { "MTP_DEVICE_PROPERTY_BURST_NUMBER", 0x5018 }, + { "MTP_DEVICE_PROPERTY_BURST_INTERVAL", 0x5019 }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER", 0x501A }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL", 0x501B }, + { "MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE", 0x501C }, + { "MTP_DEVICE_PROPERTY_UPLOAD_URL", 0x501D }, + { "MTP_DEVICE_PROPERTY_ARTIST", 0x501E }, + { "MTP_DEVICE_PROPERTY_COPYRIGHT_INFO", 0x501F }, + { "MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER", 0xD401 }, + { "MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME", 0xD402 }, + { "MTP_DEVICE_PROPERTY_VOLUME", 0xD403 }, + { "MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED", 0xD404 }, + { "MTP_DEVICE_PROPERTY_DEVICE_ICON", 0xD405 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_RATE", 0xD410 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT", 0xD411 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX", 0xD412 }, + { "MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO", 0xD406 }, + { "MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE", 0xD407 }, + { 0, 0 }, +}; + +static const char* getCodeName(uint16_t code, const CodeEntry* table) { + const CodeEntry* entry = table; while (entry->name) { if (entry->code == code) return entry->name; entry++; } - return "*** UNKNOWN OPERATION ***"; + return "UNKNOWN"; +} + +const char* MtpDebug::getOperationCodeName(MtpOperationCode code) { + return getCodeName(code, sOperationCodes); +} + +const char* MtpDebug::getFormatCodeName(MtpOperationCode code) { + return getCodeName(code, sFormatCodes); +} + +const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) { + return getCodeName(code, sObjectPropCodes); +} + +const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) { + return getCodeName(code, sDevicePropCodes); } } // namespace android diff --git a/media/mtp/MtpDebug.h b/media/mtp/MtpDebug.h index 87eff7416512..5b53e317f616 100644 --- a/media/mtp/MtpDebug.h +++ b/media/mtp/MtpDebug.h @@ -27,6 +27,9 @@ namespace android { class MtpDebug { public: static const char* getOperationCodeName(MtpOperationCode code); + static const char* getFormatCodeName(MtpObjectFormat code); + static const char* getObjectPropCodeName(MtpPropertyCode code); + static const char* getDevicePropCodeName(MtpPropertyCode code); }; }; // namespace android diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp index d6beb8a20a37..932ad6a0df13 100644 --- a/media/mtp/MtpProperty.cpp +++ b/media/mtp/MtpProperty.cpp @@ -31,6 +31,7 @@ MtpProperty::MtpProperty() mDefaultArrayValues(NULL), mCurrentArrayLength(0), mCurrentArrayValues(NULL), + mGroupCode(0), mFormFlag(kFormNone), mEnumLength(0), mEnumValues(NULL) @@ -52,6 +53,7 @@ MtpProperty::MtpProperty(MtpPropertyCode propCode, mDefaultArrayValues(NULL), mCurrentArrayLength(0), mCurrentArrayValues(NULL), + mGroupCode(0), mFormFlag(kFormNone), mEnumLength(0), mEnumValues(NULL) @@ -142,6 +144,7 @@ void MtpProperty::read(MtpDataPacket& packet, bool deviceProp) { if (deviceProp) readValue(packet, mCurrentValue); } + mGroupCode = packet.getUInt32(); mFormFlag = packet.getUInt8(); if (mFormFlag == kFormRange) { @@ -178,6 +181,7 @@ void MtpProperty::write(MtpDataPacket& packet) { default: writeValue(packet, mDefaultValue); } + packet.putUInt32(mGroupCode); packet.putUInt8(mFormFlag); if (mFormFlag == kFormRange) { writeValue(packet, mMinimumValue); diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h index 4923d40276fc..c5b4e287d612 100644 --- a/media/mtp/MtpProperty.h +++ b/media/mtp/MtpProperty.h @@ -42,6 +42,8 @@ public: kFormRange = 1, kFormEnum = 2, }; + + uint32_t mGroupCode; uint8_t mFormFlag; // for range form diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 5f5cadfd98ac..30abfb8c5ef4 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -78,46 +78,6 @@ static const MtpEventCode kSupportedEventCodes[] = { MTP_EVENT_OBJECT_REMOVED, }; -static const MtpObjectProperty kSupportedObjectProperties[] = { - MTP_PROPERTY_STORAGE_ID, - MTP_PROPERTY_OBJECT_FORMAT, - MTP_PROPERTY_OBJECT_SIZE, - MTP_PROPERTY_OBJECT_FILE_NAME, - MTP_PROPERTY_PARENT_OBJECT, -}; - -static const MtpObjectFormat kSupportedPlaybackFormats[] = { - // MTP_FORMAT_UNDEFINED, - MTP_FORMAT_ASSOCIATION, - // MTP_FORMAT_TEXT, - // MTP_FORMAT_HTML, - MTP_FORMAT_MP3, - //MTP_FORMAT_AVI, - MTP_FORMAT_MPEG, - // MTP_FORMAT_ASF, - MTP_FORMAT_EXIF_JPEG, - MTP_FORMAT_TIFF_EP, - // MTP_FORMAT_BMP, - MTP_FORMAT_GIF, - MTP_FORMAT_JFIF, - MTP_FORMAT_PNG, - MTP_FORMAT_TIFF, - MTP_FORMAT_WMA, - MTP_FORMAT_OGG, - MTP_FORMAT_AAC, - // MTP_FORMAT_FLAC, - // MTP_FORMAT_WMV, - MTP_FORMAT_MP4_CONTAINER, - MTP_FORMAT_MP2, - MTP_FORMAT_3GP_CONTAINER, - // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM, - MTP_FORMAT_ABSTRACT_AV_PLAYLIST, - MTP_FORMAT_WPL_PLAYLIST, - MTP_FORMAT_M3U_PLAYLIST, - // MTP_FORMAT_MPL_PLAYLIST, - MTP_FORMAT_PLS_PLAYLIST, -}; - MtpServer::MtpServer(int fd, MtpDatabase* database, int fileGroup, int filePerm, int directoryPerm) : mFD(fd), @@ -268,7 +228,7 @@ void MtpServer::sendObjectRemoved(MtpObjectHandle handle) { } void MtpServer::initObjectProperties() { - mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT16)); + mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32)); mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16)); mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64)); mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR)); @@ -354,6 +314,10 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { MtpStringBuffer string; char prop_value[PROPERTY_VALUE_MAX]; + MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats(); + MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats(); + MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties(); + // fill in device info mData.putUInt16(MTP_STANDARD_VERSION); mData.putUInt32(6); // MTP Vendor Extension ID @@ -365,10 +329,9 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported mData.putAUInt16(kSupportedEventCodes, sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported - mData.putEmptyArray(); // Device Properties Supported - mData.putEmptyArray(); // Capture Formats - mData.putAUInt16(kSupportedPlaybackFormats, - sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats + mData.putAUInt16(deviceProperties); // Device Properties Supported + mData.putAUInt16(captureFormats); // Capture Formats + mData.putAUInt16(playbackFormats); // Playback Formats // FIXME string.set("Google, Inc."); mData.putString(string); // Manufacturer @@ -383,6 +346,10 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { string.set(prop_value); mData.putString(string); // Serial Number + delete playbackFormats; + delete captureFormats; + delete deviceProperties; + return MTP_RESPONSE_OK; } @@ -443,8 +410,9 @@ MtpResponseCode MtpServer::doGetObjectPropsSupported() { if (!mSessionOpen) return MTP_RESPONSE_SESSION_NOT_OPEN; MtpObjectFormat format = mRequest.getParameter(1); - mData.putAUInt16(kSupportedObjectProperties, - sizeof(kSupportedObjectProperties) / sizeof(uint16_t)); + MtpDevicePropertyList* properties = mDatabase->getSupportedObjectProperties(format); + mData.putAUInt16(properties); + delete properties; return MTP_RESPONSE_OK; } diff --git a/media/mtp/MtpTypes.h b/media/mtp/MtpTypes.h index 2a895a7ea380..7e3c009e57bf 100644 --- a/media/mtp/MtpTypes.h +++ b/media/mtp/MtpTypes.h @@ -78,6 +78,7 @@ typedef Vector<int16_t> Int16List; typedef Vector<int32_t> Int32List; typedef Vector<int64_t> Int64List; +typedef UInt16List MtpObjectPropertyList; typedef UInt16List MtpDevicePropertyList; typedef UInt16List MtpObjectFormatList; typedef UInt32List MtpObjectHandleList; diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h index 224cfb9c26ec..b7afa6639326 100644 --- a/media/mtp/mtp.h +++ b/media/mtp/mtp.h @@ -465,8 +465,8 @@ // Storage Access Capability #define MTP_STORAGE_READ_WRITE 0x0000 -#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE 0x0000 -#define MTP_STORAGE_READ_ONLY_WITH_DELETE 0x0000 +#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE 0x0001 +#define MTP_STORAGE_READ_ONLY_WITH_DELETE 0x0002 // Association Type #define MTP_ASSOCIATION_TYPE_UNDEFINED 0x0000 diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index 246f9fc528ab..f70a1451c91c 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -51,4 +51,9 @@ android:label="MediaRecorder stress tests InstrumentationRunner"> </instrumentation> + <instrumentation android:name=".MediaFrameworkPowerTestRunner" + android:targetPackage="com.android.mediaframeworktest" + android:label="Media Power tests InstrumentationRunner"> + </instrumentation> + </manifest> diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java new file mode 100755 index 000000000000..34db4dbd3bde --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java @@ -0,0 +1,50 @@ +/* + * 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.mediaframeworktest; + +import com.android.mediaframeworktest.power.MediaPlayerPowerTest; + +import junit.framework.TestSuite; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; + + +/** + * Instrumentation Test Runner for all MediaPlayer tests. + * + * Running all tests: + * + * adb shell am instrument \ + * -w com.android.mediaframeworktest/.MediaFrameworkPowerTestRunner + */ + +public class MediaFrameworkPowerTestRunner extends InstrumentationTestRunner { + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(MediaPlayerPowerTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return MediaFrameworkPowerTestRunner.class.getClassLoader(); + } +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java index 3e33951e7a66..c7f461ecf558 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java @@ -25,6 +25,11 @@ import com.android.mediaframeworktest.functional.MediaRecorderTest; import com.android.mediaframeworktest.functional.SimTonesTest; import com.android.mediaframeworktest.functional.MediaPlayerInvokeTest; import com.android.mediaframeworktest.functional.MediaAudioManagerTest; +import com.android.mediaframeworktest.functional.MediaAudioEffectTest; +import com.android.mediaframeworktest.functional.MediaBassBoostTest; +import com.android.mediaframeworktest.functional.MediaEqualizerTest; +import com.android.mediaframeworktest.functional.MediaVirtualizerTest; +import com.android.mediaframeworktest.functional.MediaVisualizerTest; import junit.framework.TestSuite; import android.test.InstrumentationTestRunner; @@ -55,6 +60,11 @@ public class MediaFrameworkTestRunner extends InstrumentationTestRunner { suite.addTestSuite(MediaMimeTest.class); suite.addTestSuite(MediaPlayerInvokeTest.class); suite.addTestSuite(MediaAudioManagerTest.class); + suite.addTestSuite(MediaAudioEffectTest.class); + suite.addTestSuite(MediaBassBoostTest.class); + suite.addTestSuite(MediaEqualizerTest.class); + suite.addTestSuite(MediaVirtualizerTest.class); + suite.addTestSuite(MediaVisualizerTest.class); return suite; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java index 9a48c923994d..ca6e99927042 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java @@ -35,6 +35,7 @@ public class MediaNames { public static final String WAV = "/sdcard/media_api/music/rings_2ch.wav"; public static final String AMR = "/sdcard/media_api/music/test_amr_ietf.amr"; public static final String OGG = "/sdcard/media_api/music/Revelation.ogg"; + public static final String SINE_200_1000 = "/sdcard/media_api/music/sine_200+1000Hz_44K_mo.wav"; public static final int MP3CBR_LENGTH = 71000; public static final int MP3VBR_LENGTH = 71000; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java new file mode 100644 index 000000000000..fd939ae08ab3 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java @@ -0,0 +1,1492 @@ +/* + * 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.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.content.res.AssetFileDescriptor; +import android.media.AudioEffect; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.media.EnvironmentalReverb; +import android.media.Equalizer; +import android.media.MediaPlayer; + +import android.os.Looper; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Junit / Instrumentation test case for the media AudioTrack api + + */ +public class MediaAudioEffectTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaAudioEffectTest"; + + private AudioEffect mEffect = null; + private boolean mHasControl = false; + private boolean mIsEnabled = false; + private int mParameterChanged = -1; + private MediaPlayer mMediaPlayer = null; + private boolean mInitialized = false; + private Looper mLooper = null; + private int mError = 0; + private final Object lock = new Object(); + + public MediaAudioEffectTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + private static void assumeTrue(String message, boolean cond) { + assertTrue("(assume)"+message, cond); + } + + private void log(String testName, String message) { + Log.v(TAG, "["+testName+"] "+message); + } + + private void loge(String testName, String message) { + Log.e(TAG, "["+testName+"] "+message); + } + + //----------------------------------------------------------------- + // AUDIOEFFECT TESTS: + //---------------------------------- + + //----------------------------------------------------------------- + // 0 - static methods + //---------------------------------- + + //Test case 0.0: test queryEffects() and available effects + @LargeTest + public void test0_0QueryEffects() throws Exception { + + AudioEffect.Descriptor[] desc = AudioEffect.queryEffects(); + + assertTrue("test0_0QueryEffects: number of effects < 4: "+desc.length, (desc.length >= 4)); + + boolean hasEQ = false; + boolean hasBassBoost = false; + boolean hasVirtualizer = false; + boolean hasEnvReverb = false; + + for (int i = 0; i < desc.length; i++) { + if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) { + hasEQ = true; + } if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) { + hasBassBoost = true; + } else if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) { + hasVirtualizer = true; + } + else if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)) { + hasEnvReverb = true; + } + } + assertTrue("test0_0QueryEffects: equalizer not found", hasEQ); + assertTrue("test0_0QueryEffects: bass boost not found", hasBassBoost); + assertTrue("test0_0QueryEffects: virtualizer not found", hasVirtualizer); + assertTrue("test0_0QueryEffects: environmental reverb not found", hasEnvReverb); + } + + //----------------------------------------------------------------- + // 1 - constructor + //---------------------------------- + + //Test case 1.0: test constructor from effect type and get effect ID + @LargeTest + public void test1_0ConstructorFromType() throws Exception { + boolean result = true; + String msg = "test1_0ConstructorFromType()"; + AudioEffect.Descriptor[] desc = AudioEffect.queryEffects(); + assertTrue(msg+": no effects found", (desc.length != 0)); + try { + AudioEffect effect = new AudioEffect(desc[0].mType, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + try { + assertTrue(msg +": invalid effect ID", (effect.getId() != 0)); + } catch (IllegalStateException e) { + msg = msg.concat(": AudioEffect not initialized"); + result = false; + } finally { + effect.release(); + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Effect not found: "+desc[0].mName); + result = false; + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + result = false; + } + assertTrue(msg, result); + } + + //Test case 1.1: test constructor from effect uuid + @LargeTest + public void test1_1ConstructorFromUuid() throws Exception { + boolean result = true; + String msg = "test1_1ConstructorFromUuid()"; + AudioEffect.Descriptor[] desc = AudioEffect.queryEffects(); + assertTrue(msg+"no effects found", (desc.length != 0)); + try { + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_NULL, + desc[0].mUuid, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + effect.release(); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Effect not found: "+desc[0].mName); + result = false; + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + result = false; + } + assertTrue(msg, result); + } + + //Test case 1.2: test constructor failure from unknown type + @LargeTest + public void test1_2ConstructorUnknownType() throws Exception { + boolean result = false; + String msg = "test1_2ConstructorUnknownType()"; + + try { + AudioEffect effect = new AudioEffect(UUID.randomUUID(), + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + msg = msg.concat(": could create random AudioEffect"); + if (effect != null) { + effect.release(); + } + } catch (IllegalArgumentException e) { + result = true; + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } + assertTrue(msg, result); + } + + //Test case 1.3: test getEnabled() failure when called on released effect + @LargeTest + public void test1_3GetEnabledAfterRelease() throws Exception { + boolean result = false; + String msg = "test1_3GetEnabledAfterRelease()"; + + try { + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + effect.release(); + try { + effect.getEnabled(); + } catch (IllegalStateException e) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } + assertTrue(msg, result); + } + + //Test case 1.4: test contructor on mediaPlayer audio session + @LargeTest + public void test1_4InsertOnMediaPlayer() throws Exception { + boolean result = false; + String msg = "test1_4InsertOnMediaPlayer()"; + + try { + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SHORTMP3); + + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + mp.getAudioSessionId()); + assertNotNull(msg + ": could not create AudioEffect", effect); + try { + loge(msg, ": effect.setEnabled"); + effect.setEnabled(true); + } catch (IllegalStateException e) { + msg = msg.concat(": AudioEffect not initialized"); + } + + result = true; + effect.release(); + mp.release(); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } + assertTrue(msg, result); + } + + //Test case 1.5: test auxiliary effect attachement on MediaPlayer + @LargeTest + public void test1_5AuxiliaryOnMediaPlayer() throws Exception { + boolean result = false; + String msg = "test1_5AuxiliaryOnMediaPlayer()"; + + try { + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SHORTMP3); + + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + mp.attachAuxEffect(effect.getId()); + mp.setAuxEffectSendLevel(1.0f); + result = true; + effect.release(); + mp.release(); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } + assertTrue(msg, result); + } + + //Test case 1.6: test auxiliary effect attachement failure before setDatasource + @LargeTest + public void test1_6AuxiliaryOnMediaPlayerFailure() throws Exception { + boolean result = false; + String msg = "test1_6AuxiliaryOnMediaPlayerFailure()"; + + try { + createMediaPlayerLooper(); + synchronized(lock) { + try { + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Looper creation: wait was interrupted."); + } + } + assertTrue(mInitialized); // mMediaPlayer has been initialized? + mError = 0; + + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + synchronized(lock) { + try { + mMediaPlayer.attachAuxEffect(effect.getId()); + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Attach effect: wait was interrupted."); + } + } + assertTrue(msg + ": no error on attachAuxEffect", mError != 0); + result = true; + effect.release(); + terminateMediaPlayerLooper(); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } + assertTrue(msg, result); + } + + + //Test case 1.7: test auxiliary effect attachement on AudioTrack + @LargeTest + public void test1_7AuxiliaryOnAudioTrack() throws Exception { + boolean result = false; + String msg = "test1_7AuxiliaryOnAudioTrack()"; + + try { + AudioTrack track = new AudioTrack( + AudioManager.STREAM_MUSIC, + 44100, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT, + AudioTrack.getMinBufferSize(44100, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT), + AudioTrack.MODE_STREAM); + assertNotNull(msg + ": could not create AudioTrack", track); + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + + track.attachAuxEffect(effect.getId()); + track.setAuxEffectSendLevel(1.0f); + result = true; + effect.release(); + track.release(); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 2 - enable/ disable + //---------------------------------- + + + //Test case 2.0: test setEnabled() and getEnabled() in valid state + @LargeTest + public void test2_0SetEnabledGetEnabled() throws Exception { + boolean result = false; + String msg = "test2_0SetEnabledGetEnabled()"; + + try { + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + try { + effect.setEnabled(true); + assertTrue(msg + ": invalid state from getEnabled", effect.getEnabled()); + effect.setEnabled(false); + assertFalse(msg + ": invalid state to getEnabled", effect.getEnabled()); + result = true; + } catch (IllegalStateException e) { + msg = msg.concat(": setEnabled() in wrong state"); + } finally { + effect.release(); + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } + assertTrue(msg, result); + } + + //Test case 2.1: test setEnabled() throws exception after release + @LargeTest + public void test2_1SetEnabledAfterRelease() throws Exception { + boolean result = false; + String msg = "test2_1SetEnabledAfterRelease()"; + + try { + AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + effect.release(); + try { + effect.setEnabled(true); + } catch (IllegalStateException e) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 3 - set parameters + //---------------------------------- + + //Test case 3.0: test setParameter(byte[], byte[]) + @LargeTest + public void test3_0SetParameterByteArrayByteArray() throws Exception { + boolean result = false; + String msg = "test3_0SetParameterByteArrayByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + byte[] param = intToByteArray(Equalizer.PARAM_CURRENT_PRESET); + byte[] value = shortToByteArray((short)0); + if (effect.setParameter(param, value) == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.1: test setParameter(int, int) + @LargeTest + public void test3_1SetParameterIntInt() throws Exception { + boolean result = false; + String msg = "test3_1SetParameterIntInt()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + if (effect.setParameter(EnvironmentalReverb.PARAM_DECAY_TIME, 0) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.2: test setParameter(int, short) + @LargeTest + public void test3_2SetParameterIntShort() throws Exception { + boolean result = false; + String msg = "test3_2SetParameterIntShort()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + if (effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.3: test setParameter(int, byte[]) + @LargeTest + public void test3_3SetParameterIntByteArray() throws Exception { + boolean result = false; + String msg = "test3_3SetParameterIntByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + byte[] value = shortToByteArray((short)0); + if (effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.4: test setParameter(int[], int[]) + @LargeTest + public void test3_4SetParameterIntArrayIntArray() throws Exception { + boolean result = false; + String msg = "test3_4SetParameterIntArrayIntArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + int[] value = new int[1]; + param[0] = EnvironmentalReverb.PARAM_DECAY_TIME; + value[0] = 0; + if (effect.setParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.5: test setParameter(int[], short[]) + @LargeTest + public void test3_5SetParameterIntArrayShortArray() throws Exception { + boolean result = false; + String msg = "test3_5SetParameterIntArrayShortArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + short[] value = new short[1]; + param[0] = Equalizer.PARAM_CURRENT_PRESET; + value[0] = (short)0; + if (effect.setParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.6: test setParameter(int[], byte[]) + @LargeTest + public void test3_6SetParameterIntArrayByteArray() throws Exception { + boolean result = false; + String msg = "test3_6SetParameterIntArrayByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + byte[] value = shortToByteArray((short)0); + param[0] = Equalizer.PARAM_CURRENT_PRESET; + if (effect.setParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("setParameter() called in wrong state"); + loge(msg, "setParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 3.7: test setParameter() throws exception after release() + @LargeTest + public void test3_7SetParameterAfterRelease() throws Exception { + boolean result = false; + String msg = "test3_7SetParameterAfterRelease()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + effect.release(); + effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": setParameter() rejected"); + loge(msg, "setParameter() rejected"); + } catch (IllegalStateException e) { + result = true; + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 4 - get parameters + //---------------------------------- + + //Test case 4.0: test getParameter(byte[], byte[]) + @LargeTest + public void test4_0GetParameterByteArrayByteArray() throws Exception { + boolean result = false; + String msg = "test4_0GetParameterByteArrayByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + byte[] param = intToByteArray(Equalizer.PARAM_CURRENT_PRESET); + byte[] value = new byte[2]; + if (effect.getParameter(param, value) == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.1: test getParameter(int, int[]) + @LargeTest + public void test4_1GetParameterIntIntArray() throws Exception { + boolean result = false; + String msg = "test4_1GetParameterIntIntArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] value = new int[1]; + if (effect.getParameter(EnvironmentalReverb.PARAM_DECAY_TIME, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.2: test getParameter(int, short[]) + @LargeTest + public void test4_2GetParameterIntShortArray() throws Exception { + boolean result = false; + String msg = "test4_2GetParameterIntShortArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + short[] value = new short[1]; + if (effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.3: test getParameter(int, byte[]) + @LargeTest + public void test4_3GetParameterIntByteArray() throws Exception { + boolean result = false; + String msg = "test4_3GetParameterIntByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + byte[] value = new byte[2]; + if (effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.4: test getParameter(int[], int[]) + @LargeTest + public void test4_4GetParameterIntArrayIntArray() throws Exception { + boolean result = false; + String msg = "test4_4GetParameterIntArrayIntArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + int[] value = new int[1]; + param[0] = EnvironmentalReverb.PARAM_DECAY_TIME; + if (effect.getParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.5: test getParameter(int[], short[]) + @LargeTest + public void test4_5GetParameterIntArrayShortArray() throws Exception { + boolean result = false; + String msg = "test4_5GetParameterIntArrayShortArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + short[] value = new short[1]; + param[0] = Equalizer.PARAM_CURRENT_PRESET; + if (effect.getParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.6: test getParameter(int[], byte[]) + @LargeTest + public void test4_6GetParameterIntArrayByteArray() throws Exception { + boolean result = false; + String msg = "test4_6GetParameterIntArrayByteArray()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + int[] param = new int[1]; + byte[] value = new byte[2]; + param[0] = Equalizer.PARAM_CURRENT_PRESET; + if (effect.getParameter(param, value) + == AudioEffect.SUCCESS) { + result = true; + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("getParameter() called in wrong state"); + loge(msg, "getParameter() called in wrong state"); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //Test case 4.7: test getParameter() throws exception after release() + @LargeTest + public void test4_7GetParameterAfterRelease() throws Exception { + boolean result = false; + String msg = "test4_7GetParameterAfterRelease()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + effect.release(); + short[] value = new short[1]; + effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value); + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": getParameter() rejected"); + loge(msg, "getParameter() rejected"); + } catch (IllegalStateException e) { + result = true; + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 5 priority and listeners + //---------------------------------- + + //Test case 5.0: test control passed to higher priority client + @LargeTest + public void test5_0setEnabledLowerPriority() throws Exception { + boolean result = false; + String msg = "test5_0setEnabledLowerPriority()"; + AudioEffect effect1 = null; + AudioEffect effect2 = null; + try { + effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 1, + 0); + + assertNotNull(msg + ": could not create AudioEffect", effect1); + assertNotNull(msg + ": could not create AudioEffect", effect2); + + assertTrue(msg + ": Effect2 does not have control", effect2.hasControl()); + assertFalse(msg + ": Effect1 has control", effect1.hasControl()); + assertTrue(msg + ": Effect1 can enable", + effect1.setEnabled(true) == AudioEffect.ERROR_INVALID_OPERATION); + assertFalse(msg + ": Effect1 has enabled", effect2.getEnabled()); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Effect not found"); + result = false; + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + result = false; + } finally { + if (effect1 != null) { + effect1.release(); + } + if (effect2 != null) { + effect2.release(); + } + } + assertTrue(msg, result); + } + + //Test case 5.1: test control passed to higher priority client + @LargeTest + public void test5_1setParameterLowerPriority() throws Exception { + boolean result = false; + String msg = "test5_1setParameterLowerPriority()"; + AudioEffect effect1 = null; + AudioEffect effect2 = null; + try { + effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 1, + 0); + + assertNotNull(msg + ": could not create AudioEffect", effect1); + assertNotNull(msg + ": could not create AudioEffect", effect2); + + int status = effect2.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0); + assertEquals(msg + ": Effect2 setParameter failed", + AudioEffect.SUCCESS, status); + + status = effect1.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)1); + assertEquals(msg + ": Effect1 setParameter did not fail", + AudioEffect.ERROR_INVALID_OPERATION, status); + + short[] value = new short[1]; + status = effect2.getParameter(Equalizer.PARAM_CURRENT_PRESET, value); + assertEquals(msg + ": Effect2 getParameter failed", + AudioEffect.SUCCESS, status); + assertEquals(msg + ": Effect1 changed parameter", + (short)0, value[0]); + + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Effect not found"); + result = false; + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + result = false; + } finally { + if (effect1 != null) { + effect1.release(); + } + if (effect2 != null) { + effect2.release(); + } + } + assertTrue(msg, result); + } + + //Test case 5.2: test control status listener + @LargeTest + public void test5_2ControlStatusListener() throws Exception { + boolean result = false; + String msg = "test5_2ControlStatusListener()"; + mEffect = null; + AudioEffect effect2 = null; + try { + mHasControl = true; + createListenerLooper(true, false, false); + synchronized(lock) { + try { + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Looper creation: wait was interrupted."); + } + } + assertTrue(mInitialized); + synchronized(lock) { + try { + effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 1, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect2); + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Create second effect: wait was interrupted."); + } + } + assertFalse(msg + ": effect control not lost by effect1", mHasControl); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } finally { + terminateListenerLooper(); + if (effect2 != null) { + effect2.release(); + } + } + assertTrue(msg, result); + } + + //Test case 5.3: test enable status listener + @LargeTest + public void test5_3EnableStatusListener() throws Exception { + boolean result = false; + String msg = "test5_3EnableStatusListener()"; + mEffect = null; + AudioEffect effect2 = null; + try { + createListenerLooper(false, true, false); + synchronized(lock) { + try { + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Looper creation: wait was interrupted."); + } + } + assertTrue(mInitialized); + mEffect.setEnabled(true); + mIsEnabled = true; + effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 1, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect2); + assertTrue(msg + ": effect not enabled", effect2.getEnabled()); + synchronized(lock) { + try { + effect2.setEnabled(false); + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Create second effect: wait was interrupted."); + } + } + assertFalse(msg + ": enable status not updated", mIsEnabled); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } finally { + terminateListenerLooper(); + if (effect2 != null) { + effect2.release(); + } + } + assertTrue(msg, result); + } + + //Test case 5.4: test parameter changed listener + @LargeTest + public void test5_4ParameterChangedListener() throws Exception { + boolean result = false; + String msg = "test5_4ParameterChangedListener()"; + mEffect = null; + AudioEffect effect2 = null; + try { + createListenerLooper(false, false, true); + synchronized(lock) { + try { + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Looper creation: wait was interrupted."); + } + } + assertTrue(mInitialized); + effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 1, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect2); + synchronized(lock) { + try { + mParameterChanged = -1; + effect2.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0); + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Create second effect: wait was interrupted."); + } + } + assertEquals(msg + ": parameter change not received", + Equalizer.PARAM_CURRENT_PRESET, mParameterChanged); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } finally { + terminateListenerLooper(); + if (effect2 != null) { + effect2.release(); + } + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 6 command method + //---------------------------------- + + + //Test case 6.0: test command method + @LargeTest + public void test6_0Command() throws Exception { + boolean result = false; + String msg = "test6_0Command()"; + AudioEffect effect = null; + try { + effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull(msg + ": could not create AudioEffect", effect); + try { + byte[] cmd = new byte[0]; + byte[] reply = new byte[4]; + int status = effect.command(3, cmd, reply); + assertEquals(msg + ": command failed", AudioEffect.SUCCESS, status); + assertTrue(msg + ": effect not enabled", effect.getEnabled()); + result = true; + } catch (IllegalStateException e) { + msg = msg.concat(": command in illegal state"); + } + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + loge(msg, ": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + loge(msg, ": Effect library not loaded"); + } catch (Exception e){ + loge(msg, "Could not create media player:" + e); + } finally { + if (effect != null) { + effect.release(); + } + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // private methods + //---------------------------------- + + /* + * Initializes the message looper so that the MediaPlayer object can + * receive the callback messages. + */ + private void createMediaPlayerLooper() { + new Thread() { + @Override + public void run() { + // Set up a looper to be used by mMediaPlayer. + Looper.prepare(); + + // Save the looper so that we can terminate this thread + // after we are done with it. + mLooper = Looper.myLooper(); + + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer player, int what, int extra) { + synchronized(lock) { + mError = what; + lock.notify(); + } + return true; + } + }); + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer player) { + synchronized(lock) { + lock.notify(); + } + } + }); + synchronized(lock) { + mInitialized = true; + lock.notify(); + } + Looper.loop(); // Blocks forever until Looper.quit() is called. + } + }.start(); + } + /* + * Terminates the message looper thread. + */ + private void terminateMediaPlayerLooper() { + if (mLooper != null) { + mLooper.quit(); + mLooper = null; + } + if (mMediaPlayer != null) { + mMediaPlayer.release(); + } + } + + /* + * Initializes the message looper fro effect listener + */ + class ListenerThread extends Thread { + boolean mControl; + boolean mEnable; + boolean mParameter; + + public ListenerThread(boolean control, boolean enable, boolean parameter) { + super(); + mControl = control; + mEnable = enable; + mParameter = parameter; + } + } + private void createListenerLooper(boolean control, boolean enable, boolean parameter) { + + new ListenerThread(control, enable, parameter) { + @Override + public void run() { + // Set up a looper to be used by mEffect. + Looper.prepare(); + + // Save the looper so that we can terminate this thread + // after we are done with it. + mLooper = Looper.myLooper(); + + mEffect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, + AudioEffect.EFFECT_TYPE_NULL, + 0, + 0); + assertNotNull("could not create AudioEffect", mEffect); + + if (mControl) { + mEffect.setControlStatusListener(new AudioEffect.OnControlStatusChangeListener() { + public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { + synchronized(lock) { + if (effect == mEffect) { + mHasControl = controlGranted; + lock.notify(); + } + } + } + }); + } + if (mEnable) { + mEffect.setEnableStatusListener(new AudioEffect.OnEnableStatusChangeListener() { + public void onEnableStatusChange(AudioEffect effect, boolean enabled) { + synchronized(lock) { + if (effect == mEffect) { + mIsEnabled = enabled; + lock.notify(); + } + } + } + }); + } + if (mParameter) { + mEffect.setParameterListener(new AudioEffect.OnParameterChangeListener() { + public void onParameterChange(AudioEffect effect, int status, byte[] param, + byte[] value) { + synchronized(lock) { + if (effect == mEffect) { + mParameterChanged = byteArrayToInt(param); + lock.notify(); + } + } + } + }); + } + + synchronized(lock) { + mInitialized = true; + lock.notify(); + } + Looper.loop(); // Blocks forever until Looper.quit() is called. + } + }.start(); + } + /* + * Terminates the listener looper thread. + */ + private void terminateListenerLooper() { + if (mEffect != null) { + mEffect.release(); + mEffect = null; + } + if (mLooper != null) { + mLooper.quit(); + mLooper = null; + } + } + + protected int byteArrayToInt(byte[] valueBuf) { + return byteArrayToInt(valueBuf, 0); + + } + + protected int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + + protected byte[] intToByteArray(int value) { + ByteBuffer converter = ByteBuffer.allocate(4); + converter.order(ByteOrder.nativeOrder()); + converter.putInt(value); + return converter.array(); + } + + protected short byteArrayToShort(byte[] valueBuf) { + return byteArrayToShort(valueBuf, 0); + } + + protected short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + + protected byte[] shortToByteArray(short value) { + ByteBuffer converter = ByteBuffer.allocate(2); + converter.order(ByteOrder.nativeOrder()); + short sValue = (short) value; + converter.putShort(sValue); + return converter.array(); + } + +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java new file mode 100644 index 000000000000..8a68c5eef68a --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java @@ -0,0 +1,334 @@ +/* + * 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.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioEffect; +import android.media.AudioManager; +import android.media.BassBoost; +import android.media.Visualizer; +import android.media.MediaPlayer; + +import android.os.Looper; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Junit / Instrumentation test case for the media AudioTrack api + + */ +public class MediaBassBoostTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaBassBoostTest"; + private final static int MIN_ENERGY_RATIO_2 = 4; + private final static short TEST_STRENGTH = 500; + + private BassBoost mBassBoost = null; + private int mSession = -1; + + public MediaBassBoostTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + releaseBassBoost(); + } + + private static void assumeTrue(String message, boolean cond) { + assertTrue("(assume)"+message, cond); + } + + private void log(String testName, String message) { + Log.v(TAG, "["+testName+"] "+message); + } + + private void loge(String testName, String message) { + Log.e(TAG, "["+testName+"] "+message); + } + + //----------------------------------------------------------------- + // BASS BOOST TESTS: + //---------------------------------- + + + //----------------------------------------------------------------- + // 0 - constructor + //---------------------------------- + + //Test case 0.0: test constructor and release + @LargeTest + public void test0_0ConstructorAndRelease() throws Exception { + boolean result = false; + String msg = "test1_0ConstructorAndRelease()"; + BassBoost bb = null; + try { + bb = new BassBoost(0, 0); + assertNotNull(msg + ": could not create BassBoost", bb); + try { + assertTrue(msg +": invalid effect ID", (bb.getId() != 0)); + } catch (IllegalStateException e) { + msg = msg.concat(": BassBoost not initialized"); + } + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": BassBoost not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } finally { + if (bb != null) { + bb.release(); + } + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 1 - get/set parameters + //---------------------------------- + + //Test case 1.0: test strength + @LargeTest + public void test1_0Strength() throws Exception { + boolean result = false; + String msg = "test1_0Strength()"; + getBassBoost(0); + try { + if (mBassBoost.getStrengthSupported()) { + mBassBoost.setStrength((short)TEST_STRENGTH); + short strength = mBassBoost.getRoundedStrength(); + // allow 10% difference between set strength and rounded strength + assertTrue(msg +": got incorrect strength", + ((float)strength > (float)TEST_STRENGTH * 0.9f) && + ((float)strength < (float)TEST_STRENGTH * 1.1f)); + } else { + short strength = mBassBoost.getRoundedStrength(); + assertTrue(msg +": got incorrect strength", strength >= 0 && strength <= 1000); + } + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseBassBoost(); + } + assertTrue(msg, result); + } + + //Test case 1.1: test properties + @LargeTest + public void test1_1Properties() throws Exception { + boolean result = false; + String msg = "test1_1Properties()"; + getBassBoost(0); + try { + BassBoost.Settings settings = mBassBoost.getProperties(); + String str = settings.toString(); + settings = new BassBoost.Settings(str); + mBassBoost.setProperties(settings); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseBassBoost(); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 2 - Effect action + //---------------------------------- + + //Test case 2.0: test actual bass boost influence on sound + @LargeTest + public void test2_0SoundModification() throws Exception { + boolean result = false; + String msg = "test2_0SoundModification()"; + EnergyProbe probe = null; + AudioEffect vc = null; + MediaPlayer mp = null; + AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + am.setStreamVolume(AudioManager.STREAM_MUSIC, + am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), + 0); + + try { + probe = new EnergyProbe(0); + // creating a volume controller on output mix ensures that ro.audio.silent mutes + // audio after the effects and not before + vc = new AudioEffect( + AudioEffect.EFFECT_TYPE_NULL, + UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"), + 0, + 0); + vc.setEnabled(true); + + mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SINE_200_1000); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + getBassBoost(mp.getAudioSessionId()); + mp.prepare(); + mp.start(); + Thread.sleep(200); + // measure reference energy around 1kHz + int refEnergy200 = probe.capture(200); + int refEnergy1000 = probe.capture(1000); + mBassBoost.setStrength((short)1000); + mBassBoost.setEnabled(true); + Thread.sleep(500); + // measure energy around 1kHz with band level at min + int energy200 = probe.capture(200); + int energy1000 = probe.capture(1000); + // verify that the energy ration between low and high frequencies is at least + // MIN_ENERGY_RATIO_2 times higher with bassboost on. + assertTrue(msg + ": bass boost has no effect", + ((float)energy200/(float)energy1000) > + (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000))); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } catch (InterruptedException e) { + loge(msg, "sleep() interrupted"); + } + finally { + releaseBassBoost(); + if (mp != null) { + mp.release(); + } + if (vc != null) { + vc.release(); + } + if (probe != null) { + probe.release(); + } + am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + assertTrue(msg, result); + } + //----------------------------------------------------------------- + // private methods + //---------------------------------- + + private class EnergyProbe { + Visualizer mVisualizer = null; + private byte[] mFft = new byte[1024]; + + public EnergyProbe(int session) { + mVisualizer = new Visualizer(session); + mVisualizer.setCaptureSize(1024); + } + + public int capture(int freq) throws InterruptedException { + int energy = 0; + int count = 0; + if (mVisualizer != null) { + mVisualizer.setEnabled(true); + for (int i = 0; i < 10; i++) { + if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) { + // TODO: check speex FFT as it seems to return only the number of points + // correspondong to valid part of the spectrum (< Fs). + // e.g., if the number of points is 1024, it covers the frequency range + // 0 to 22050 instead of 0 to 44100 as expected from an FFT. + int bin = freq / (22050 / 1024); + int tmp = 0; + for (int j = bin-2; j < bin+3; j++) { + tmp += (int)mFft[j] * (int)mFft[j]; + } + energy += tmp/5; + count++; + } + Thread.sleep(50); + } + mVisualizer.setEnabled(false); + } + if (count == 0) { + return 0; + } + return energy/count; + } + + public void release() { + if (mVisualizer != null) { + mVisualizer.release(); + mVisualizer = null; + } + } + } + + private void getBassBoost(int session) { + if (mBassBoost == null || session != mSession) { + if (session != mSession && mBassBoost != null) { + mBassBoost.release(); + mBassBoost = null; + } + try { + mBassBoost = new BassBoost(0, session); + mSession = session; + } catch (IllegalArgumentException e) { + Log.e(TAG, "getBassBoost() BassBoost not found exception: "+e); + } catch (UnsupportedOperationException e) { + Log.e(TAG, "getBassBoost() Effect library not loaded exception: "+e); + } + } + assertNotNull("could not create mBassBoost", mBassBoost); + } + + private void releaseBassBoost() { + if (mBassBoost != null) { + mBassBoost.release(); + mBassBoost = null; + } + } + +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java new file mode 100644 index 000000000000..e46887b3e4d2 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java @@ -0,0 +1,397 @@ +/* + * 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.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioEffect; +import android.media.AudioManager; +import android.media.Equalizer; +import android.media.Visualizer; +import android.media.MediaPlayer; + +import android.os.Looper; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Junit / Instrumentation test case for the media AudioTrack api + + */ +public class MediaEqualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaEqualizerTest"; + private final static int MIN_NUMBER_OF_BANDS = 4; + private final static int MIN_BAND_LEVEL = -1500; + private final static int MAX_BAND_LEVEL = 1500; + private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000; + private final static int MIN_NUMBER_OF_PRESETS = 4; + private Equalizer mEqualizer = null; + private int mSession = -1; + + public MediaEqualizerTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + releaseEqualizer(); + } + + private static void assumeTrue(String message, boolean cond) { + assertTrue("(assume)"+message, cond); + } + + private void log(String testName, String message) { + Log.v(TAG, "["+testName+"] "+message); + } + + private void loge(String testName, String message) { + Log.e(TAG, "["+testName+"] "+message); + } + + //----------------------------------------------------------------- + // EQUALIZER TESTS: + //---------------------------------- + + + //----------------------------------------------------------------- + // 0 - constructor + //---------------------------------- + + //Test case 0.0: test constructor and release + @LargeTest + public void test0_0ConstructorAndRelease() throws Exception { + boolean result = false; + String msg = "test1_0ConstructorAndRelease()"; + Equalizer eq = null; + try { + eq = new Equalizer(0, 0); + assertNotNull(msg + ": could not create Equalizer", eq); + try { + assertTrue(msg +": invalid effect ID", (eq.getId() != 0)); + } catch (IllegalStateException e) { + msg = msg.concat(": Equalizer not initialized"); + } + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Equalizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } finally { + if (eq != null) { + eq.release(); + } + } + assertTrue(msg, result); + } + + + //----------------------------------------------------------------- + // 1 - get/set parameters + //---------------------------------- + + //Test case 1.0: test setBandLevel() and getBandLevel() + @LargeTest + public void test1_0BandLevel() throws Exception { + boolean result = false; + String msg = "test1_0BandLevel()"; + getEqualizer(0); + try { + short numBands = mEqualizer.getNumberOfBands(); + assertTrue(msg + ": not enough bands", numBands >= MIN_NUMBER_OF_BANDS); + + short[] levelRange = mEqualizer.getBandLevelRange(); + assertTrue(msg + ": min level too high", levelRange[0] <= MIN_BAND_LEVEL); + assertTrue(msg + ": max level too low", levelRange[1] >= MAX_BAND_LEVEL); + + mEqualizer.setBandLevel((short)0, levelRange[1]); + short level = mEqualizer.getBandLevel((short)0); + // 10% margin on actual level compared to requested level + assertTrue(msg + ": setBandLevel failed", + ((float)level > (float)levelRange[1] * 0.9f) && + ((float)level < (float)levelRange[1] * 1.1f)); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseEqualizer(); + } + assertTrue(msg, result); + } + + //Test case 1.1: test band frequency + @LargeTest + public void test1_1BandFrequency() throws Exception { + boolean result = false; + String msg = "test1_1BandFrequency()"; + getEqualizer(0); + try { + short band = mEqualizer.getBand(TEST_FREQUENCY_MILLIHERTZ); + assertTrue(msg + ": getBand failed", band >= 0); + int[] freqRange = mEqualizer.getBandFreqRange(band); + assertTrue(msg + ": getBandFreqRange failed", + (freqRange[0] <= TEST_FREQUENCY_MILLIHERTZ) && + (freqRange[1] >= TEST_FREQUENCY_MILLIHERTZ)); + int freq = mEqualizer.getCenterFreq(band); + assertTrue(msg + ": getCenterFreq failed", + (freqRange[0] <= freq) && (freqRange[1] >= freq)); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseEqualizer(); + } + assertTrue(msg, result); + } + + //Test case 1.2: test presets + @LargeTest + public void test1_2Presets() throws Exception { + boolean result = false; + String msg = "test1_2Presets()"; + getEqualizer(0); + try { + short numPresets = mEqualizer.getNumberOfPresets(); + assertTrue(msg + ": getNumberOfPresets failed", numPresets >= MIN_NUMBER_OF_PRESETS); + mEqualizer.usePreset((short)(numPresets - 1)); + short preset = mEqualizer.getCurrentPreset(); + assertEquals(msg + ": usePreset failed", preset, (short)(numPresets - 1)); + String name = mEqualizer.getPresetName(preset); + assertNotNull(msg + ": getPresetName failed", name); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseEqualizer(); + } + assertTrue(msg, result); + } + + //Test case 1.3: test properties + @LargeTest + public void test1_3Properties() throws Exception { + boolean result = false; + String msg = "test1_3Properties()"; + getEqualizer(0); + try { + Equalizer.Settings settings = mEqualizer.getProperties(); + String str = settings.toString(); + settings = new Equalizer.Settings(str); + mEqualizer.setProperties(settings); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseEqualizer(); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 2 - Effect action + //---------------------------------- + + //Test case 2.0: test that the equalizer actually alters the sound + @LargeTest + public void test2_0SoundModification() throws Exception { + boolean result = false; + String msg = "test2_0SoundModification()"; + EnergyProbe probe = null; + AudioEffect vc = null; + MediaPlayer mp = null; + AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + am.setStreamVolume(AudioManager.STREAM_MUSIC, + am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), + 0); + try { + probe = new EnergyProbe(0); + // creating a volume controller on output mix ensures that ro.audio.silent mutes + // audio after the effects and not before + vc = new AudioEffect( + AudioEffect.EFFECT_TYPE_NULL, + UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"), + 0, + 0); + vc.setEnabled(true); + + mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SINE_200_1000); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + getEqualizer(mp.getAudioSessionId()); + mp.prepare(); + mp.start(); + Thread.sleep(500); + // measure reference energy around 1kHz + int refEnergy = probe.capture(1000); + short band = mEqualizer.getBand(1000000); + short[] levelRange = mEqualizer.getBandLevelRange(); + mEqualizer.setBandLevel(band, levelRange[0]); + mEqualizer.setEnabled(true); + Thread.sleep(500); + // measure energy around 1kHz with band level at min + int energy = probe.capture(1000); + assertTrue(msg + ": equalizer has no effect at 1kHz", energy < refEnergy/4); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } catch (InterruptedException e) { + loge(msg, "sleep() interrupted"); + } + finally { + releaseEqualizer(); + if (mp != null) { + mp.release(); + } + if (vc != null) { + vc.release(); + } + if (probe != null) { + probe.release(); + } + am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // private methods + //---------------------------------- + + private class EnergyProbe { + Visualizer mVisualizer = null; + private byte[] mFft = new byte[1024]; + + public EnergyProbe(int session) { + mVisualizer = new Visualizer(session); + mVisualizer.setCaptureSize(1024); + } + + public int capture(int freq) throws InterruptedException { + int energy = 0; + int count = 0; + if (mVisualizer != null) { + mVisualizer.setEnabled(true); + for (int i = 0; i < 10; i++) { + if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) { + // TODO: check speex FFT as it seems to return only the number of points + // correspondong to valid part of the spectrum (< Fs). + // e.g., if the number of points is 1024, it covers the frequency range + // 0 to 22050 instead of 0 to 44100 as expected from an FFT. + int bin = freq / (22050 / 1024); + int tmp = 0; + for (int j = bin-2; j < bin+3; j++) { + tmp += (int)mFft[j] * (int)mFft[j]; + } + energy += tmp/5; + count++; + } + Thread.sleep(50); + } + mVisualizer.setEnabled(false); + } + if (count == 0) { + return 0; + } + return energy/count; + } + + public void release() { + if (mVisualizer != null) { + mVisualizer.release(); + mVisualizer = null; + } + } + } + + private void getEqualizer(int session) { + if (mEqualizer == null || session != mSession) { + if (session != mSession && mEqualizer != null) { + mEqualizer.release(); + mEqualizer = null; + } + try { + mEqualizer = new Equalizer(0, session); + mSession = session; + } catch (IllegalArgumentException e) { + Log.e(TAG, "getEqualizer() Equalizer not found exception: "+e); + } catch (UnsupportedOperationException e) { + Log.e(TAG, "getEqualizer() Effect library not loaded exception: "+e); + } + } + assertNotNull("could not create mEqualizer", mEqualizer); + } + + private void releaseEqualizer() { + if (mEqualizer != null) { + mEqualizer.release(); + mEqualizer = null; + } + } + +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java new file mode 100644 index 000000000000..6b8ae442a1dc --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java @@ -0,0 +1,339 @@ +/* + * 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.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioEffect; +import android.media.AudioManager; +import android.media.Virtualizer; +import android.media.Visualizer; +import android.media.MediaPlayer; + +import android.os.Looper; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Junit / Instrumentation test case for the media AudioTrack api + + */ +public class MediaVirtualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaVirtualizerTest"; + private final static int MIN_ENERGY_RATIO_2 = 4; + private final static short TEST_STRENGTH = 500; + + private Virtualizer mVirtualizer = null; + private int mSession = -1; + + public MediaVirtualizerTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + releaseVirtualizer(); + } + + private static void assumeTrue(String message, boolean cond) { + assertTrue("(assume)"+message, cond); + } + + private void log(String testName, String message) { + Log.v(TAG, "["+testName+"] "+message); + } + + private void loge(String testName, String message) { + Log.e(TAG, "["+testName+"] "+message); + } + + //----------------------------------------------------------------- + // VIRTUALIZER TESTS: + //---------------------------------- + + + //----------------------------------------------------------------- + // 0 - constructor + //---------------------------------- + + //Test case 0.0: test constructor and release + @LargeTest + public void test0_0ConstructorAndRelease() throws Exception { + boolean result = false; + String msg = "test1_0ConstructorAndRelease()"; + Virtualizer virtualizer = null; + try { + virtualizer = new Virtualizer(0, 0); + assertNotNull(msg + ": could not create Virtualizer", virtualizer); + try { + assertTrue(msg +": invalid effect ID", (virtualizer.getId() != 0)); + } catch (IllegalStateException e) { + msg = msg.concat(": Virtualizer not initialized"); + } + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Virtualizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } finally { + if (virtualizer != null) { + virtualizer.release(); + } + } + assertTrue(msg, result); + } + + + //----------------------------------------------------------------- + // 1 - get/set parameters + //---------------------------------- + + //Test case 1.0: test strength + @LargeTest + public void test1_0Strength() throws Exception { + boolean result = false; + String msg = "test1_0Strength()"; + getVirtualizer(0); + try { + if (mVirtualizer.getStrengthSupported()) { + mVirtualizer.setStrength((short)TEST_STRENGTH); + short strength = mVirtualizer.getRoundedStrength(); + // allow 10% difference between set strength and rounded strength + assertTrue(msg +": got incorrect strength", + ((float)strength > (float)TEST_STRENGTH * 0.9f) && + ((float)strength < (float)TEST_STRENGTH * 1.1f)); + } else { + short strength = mVirtualizer.getRoundedStrength(); + assertTrue(msg +": got incorrect strength", strength >= 0 && strength <= 1000); + } + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseVirtualizer(); + } + assertTrue(msg, result); + } + + //Test case 1.1: test properties + @LargeTest + public void test1_1Properties() throws Exception { + boolean result = false; + String msg = "test1_1Properties()"; + getVirtualizer(0); + try { + Virtualizer.Settings settings = mVirtualizer.getProperties(); + String str = settings.toString(); + settings = new Virtualizer.Settings(str); + mVirtualizer.setProperties(settings); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseVirtualizer(); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 2 - Effect action + //---------------------------------- + + //Test case 2.0: test actual virtualizer influence on sound + @LargeTest + public void test2_0SoundModification() throws Exception { + boolean result = false; + String msg = "test2_0SoundModification()"; + EnergyProbe probe = null; + AudioEffect vc = null; + MediaPlayer mp = null; + AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + am.setStreamVolume(AudioManager.STREAM_MUSIC, + am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), + 0); + + try { + probe = new EnergyProbe(0); + // creating a volume controller on output mix ensures that ro.audio.silent mutes + // audio after the effects and not before + vc = new AudioEffect( + AudioEffect.EFFECT_TYPE_NULL, + UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"), + 0, + 0); + vc.setEnabled(true); + + mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SINE_200_1000); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + getVirtualizer(mp.getAudioSessionId()); + mp.prepare(); + mp.start(); + Thread.sleep(200); + // measure reference energy around 1kHz + int refEnergy200 = probe.capture(200); + int refEnergy1000 = probe.capture(1000); + mVirtualizer.setStrength((short)1000); + mVirtualizer.setEnabled(true); + Thread.sleep(500); + // measure energy around 1kHz with band level at min + int energy200 = probe.capture(200); + int energy1000 = probe.capture(1000); + // verify that the energy ration between low and high frequencies is at least + // four times higher with virtualizer on. + // NOTE: this is what is observed with current virtualizer implementation and the test + // audio file but is not the primary effect of the virtualizer. A better way would + // be to have a stereo PCM capture and check that a strongly paned input is centered + // when output. However, we cannot capture stereo with the visualizer. + assertTrue(msg + ": virtiualizer has no effect", + ((float)energy200/(float)energy1000) > + (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000))); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } catch (InterruptedException e) { + loge(msg, "sleep() interrupted"); + } + finally { + releaseVirtualizer(); + if (mp != null) { + mp.release(); + } + if (vc != null) { + vc.release(); + } + if (probe != null) { + probe.release(); + } + am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + assertTrue(msg, result); + } + //----------------------------------------------------------------- + // private methods + //---------------------------------- + + private class EnergyProbe { + Visualizer mVisualizer = null; + private byte[] mFft = new byte[1024]; + + public EnergyProbe(int session) { + mVisualizer = new Visualizer(session); + mVisualizer.setCaptureSize(1024); + } + + public int capture(int freq) throws InterruptedException { + int energy = 0; + int count = 0; + if (mVisualizer != null) { + mVisualizer.setEnabled(true); + for (int i = 0; i < 10; i++) { + if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) { + // TODO: check speex FFT as it seems to return only the number of points + // correspondong to valid part of the spectrum (< Fs). + // e.g., if the number of points is 1024, it covers the frequency range + // 0 to 22050 instead of 0 to 44100 as expected from an FFT. + int bin = freq / (22050 / 1024); + int tmp = 0; + for (int j = bin-2; j < bin+3; j++) { + tmp += (int)mFft[j] * (int)mFft[j]; + } + energy += tmp/5; + count++; + } + Thread.sleep(50); + } + mVisualizer.setEnabled(false); + } + if (count == 0) { + return 0; + } + return energy/count; + } + + public void release() { + if (mVisualizer != null) { + mVisualizer.release(); + mVisualizer = null; + } + } + } + + private void getVirtualizer(int session) { + if (mVirtualizer == null || session != mSession) { + if (session != mSession && mVirtualizer != null) { + mVirtualizer.release(); + mVirtualizer = null; + } + try { + mVirtualizer = new Virtualizer(0, session); + mSession = session; + } catch (IllegalArgumentException e) { + Log.e(TAG, "getVirtualizer() Virtualizer not found exception: "+e); + } catch (UnsupportedOperationException e) { + Log.e(TAG, "getVirtualizer() Effect library not loaded exception: "+e); + } + } + assertNotNull("could not create mVirtualizer", mVirtualizer); + } + + private void releaseVirtualizer() { + if (mVirtualizer != null) { + mVirtualizer.release(); + mVirtualizer = null; + } + } + +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java new file mode 100644 index 000000000000..26fdbfe4db95 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java @@ -0,0 +1,505 @@ +/* + * 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.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioEffect; +import android.media.AudioManager; +import android.media.Visualizer; +import android.media.MediaPlayer; + +import android.os.Looper; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Junit / Instrumentation test case for the media AudioTrack api + + */ +public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaVisualizerTest"; + private final static int MIN_CAPTURE_RATE_MAX = 20000; + private final static int MIN_SAMPLING_RATE = 8000000; + private final static int MAX_SAMPLING_RATE = 48000000; + private final static int MIN_CAPTURE_SIZE_MAX = 1024; + private final static int MAX_CAPTURE_SIZE_MIN = 128; + + private Visualizer mVisualizer = null; + private int mSession = -1; + private boolean mInitialized = false; + private Looper mLooper = null; + private final Object lock = new Object(); + private byte[] mWaveform = null; + private byte[] mFft = null; + private boolean mCaptureWaveform = false; + private boolean mCaptureFft = false; + + public MediaVisualizerTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + releaseVisualizer(); + } + + private static void assumeTrue(String message, boolean cond) { + assertTrue("(assume)"+message, cond); + } + + private void log(String testName, String message) { + Log.v(TAG, "["+testName+"] "+message); + } + + private void loge(String testName, String message) { + Log.e(TAG, "["+testName+"] "+message); + } + + //----------------------------------------------------------------- + // VISUALIZER TESTS: + //---------------------------------- + + + //----------------------------------------------------------------- + // 0 - constructor + //---------------------------------- + + //Test case 0.0: test constructor and release + @LargeTest + public void test0_0ConstructorAndRelease() throws Exception { + boolean result = false; + String msg = "test1_0ConstructorAndRelease()"; + Visualizer visualizer = null; + try { + visualizer = new Visualizer(0); + assertNotNull(msg + ": could not create Visualizer", visualizer); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Visualizer not found"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": Effect library not loaded"); + } finally { + if (visualizer != null) { + visualizer.release(); + } + } + assertTrue(msg, result); + } + + + //----------------------------------------------------------------- + // 1 - get/set parameters + //---------------------------------- + + //Test case 1.0: check capture rate and sampling rate + @LargeTest + public void test1_0CaptureRates() throws Exception { + boolean result = false; + String msg = "test1_0CaptureRates()"; + getVisualizer(0); + try { + int captureRate = mVisualizer.getMaxCaptureRate(); + assertTrue(msg +": insufficient max capture rate", + captureRate >= MIN_CAPTURE_RATE_MAX); + int samplingRate = mVisualizer.getSamplingRate(); + assertTrue(msg +": invalid sampling rate", + samplingRate >= MIN_SAMPLING_RATE && samplingRate <= MAX_SAMPLING_RATE); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseVisualizer(); + } + assertTrue(msg, result); + } + + //Test case 1.1: check capture size + @LargeTest + public void test1_1CaptureSize() throws Exception { + boolean result = false; + String msg = "test1_1CaptureSize()"; + getVisualizer(0); + try { + int[] range = mVisualizer.getCaptureSizeRange(); + assertTrue(msg +": insufficient min capture size", + range[0] <= MAX_CAPTURE_SIZE_MIN); + assertTrue(msg +": insufficient min capture size", + range[1] >= MIN_CAPTURE_SIZE_MAX); + mVisualizer.setCaptureSize(range[0]); + assertEquals(msg +": insufficient min capture size", + range[0], mVisualizer.getCaptureSize()); + mVisualizer.setCaptureSize(range[1]); + assertEquals(msg +": insufficient min capture size", + range[1], mVisualizer.getCaptureSize()); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } finally { + releaseVisualizer(); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // 2 - check capture + //---------------------------------- + + //Test case 2.0: test capture in polling mode + @LargeTest + public void test2_0PollingCapture() throws Exception { + boolean result = false; + String msg = "test2_0PollingCapture()"; + AudioEffect vc = null; + MediaPlayer mp = null; + AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + am.setStreamVolume(AudioManager.STREAM_MUSIC, + am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), + 0); + + try { + // creating a volume controller on output mix ensures that ro.audio.silent mutes + // audio after the effects and not before + vc = new AudioEffect( + AudioEffect.EFFECT_TYPE_NULL, + UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"), + 0, + 0); + vc.setEnabled(true); + + mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SINE_200_1000); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + getVisualizer(mp.getAudioSessionId()); + mVisualizer.setEnabled(true); + // check capture on silence + byte[] data = new byte[mVisualizer.getCaptureSize()]; + mVisualizer.getWaveForm(data); + int energy = computeEnergy(data, true); + assertEquals(msg +": getWaveForm reports energy for silence", + 0, energy); + mVisualizer.getFft(data); + energy = computeEnergy(data, false); + assertEquals(msg +": getFft reports energy for silence", + 0, energy); + mp.prepare(); + mp.start(); + Thread.sleep(500); + // check capture on sound + mVisualizer.getWaveForm(data); + energy = computeEnergy(data, true); + assertTrue(msg +": getWaveForm reads insufficient level", + energy > 0); + mVisualizer.getFft(data); + energy = computeEnergy(data, false); + assertTrue(msg +": getFft reads insufficient level", + energy > 0); + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } catch (InterruptedException e) { + loge(msg, "sleep() interrupted"); + } + finally { + releaseVisualizer(); + if (mp != null) { + mp.release(); + } + if (vc != null) { + vc.release(); + } + am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + assertTrue(msg, result); + } + + //Test case 2.1: test capture with listener + @LargeTest + public void test2_1ListenerCapture() throws Exception { + boolean result = false; + String msg = "test2_1ListenerCapture()"; + AudioEffect vc = null; + MediaPlayer mp = null; + AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + am.setStreamVolume(AudioManager.STREAM_MUSIC, + am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), + 0); + + try { + // creating a volume controller on output mix ensures that ro.audio.silent mutes + // audio after the effects and not before + vc = new AudioEffect( + AudioEffect.EFFECT_TYPE_NULL, + UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"), + 0, + 0); + vc.setEnabled(true); + + mp = new MediaPlayer(); + mp.setDataSource(MediaNames.SINE_200_1000); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + + getVisualizer(mp.getAudioSessionId()); + createListenerLooper(); + synchronized(lock) { + try { + lock.wait(1000); + } catch(Exception e) { + Log.e(TAG, "Looper creation: wait was interrupted."); + } + } + assertTrue(mInitialized); + + mVisualizer.setEnabled(true); + + // check capture on silence + synchronized(lock) { + try { + mCaptureWaveform = true; + lock.wait(1000); + mCaptureWaveform = false; + } catch(Exception e) { + Log.e(TAG, "Capture waveform: wait was interrupted."); + } + } + assertNotNull(msg +": waveform capture failed", mWaveform); + int energy = computeEnergy(mWaveform, true); + assertEquals(msg +": getWaveForm reports energy for silence", + 0, energy); + + synchronized(lock) { + try { + mCaptureFft = true; + lock.wait(1000); + mCaptureFft = false; + } catch(Exception e) { + Log.e(TAG, "Capture FFT: wait was interrupted."); + } + } + assertNotNull(msg +": FFT capture failed", mFft); + energy = computeEnergy(mFft, false); + assertEquals(msg +": getFft reports energy for silence", + 0, energy); + + mp.prepare(); + mp.start(); + Thread.sleep(500); + + // check capture on sound + synchronized(lock) { + try { + mCaptureWaveform = true; + lock.wait(1000); + mCaptureWaveform = false; + } catch(Exception e) { + Log.e(TAG, "Capture waveform: wait was interrupted."); + } + } + assertNotNull(msg +": waveform capture failed", mWaveform); + energy = computeEnergy(mWaveform, true); + assertTrue(msg +": getWaveForm reads insufficient level", + energy > 0); + + synchronized(lock) { + try { + mCaptureFft = true; + lock.wait(1000); + mCaptureFft = false; + } catch(Exception e) { + Log.e(TAG, "Capture FFT: wait was interrupted."); + } + } + assertNotNull(msg +": FFT capture failed", mFft); + energy = computeEnergy(mFft, false); + assertTrue(msg +": getFft reads insufficient level", + energy > 0); + + result = true; + } catch (IllegalArgumentException e) { + msg = msg.concat(": Bad parameter value"); + loge(msg, "Bad parameter value"); + } catch (UnsupportedOperationException e) { + msg = msg.concat(": get parameter() rejected"); + loge(msg, "get parameter() rejected"); + } catch (IllegalStateException e) { + msg = msg.concat("get parameter() called in wrong state"); + loge(msg, "get parameter() called in wrong state"); + } catch (InterruptedException e) { + loge(msg, "sleep() interrupted"); + } + finally { + terminateListenerLooper(); + releaseVisualizer(); + if (mp != null) { + mp.release(); + } + if (vc != null) { + vc.release(); + } + am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); + } + assertTrue(msg, result); + } + + //----------------------------------------------------------------- + // private methods + //---------------------------------- + + private int computeEnergy(byte[] data, boolean unsigned) { + int energy = 0; + if (data.length != 0) { + for (int i = 0; i < data.length; i++) { + int tmp; + // convert from unsigned 8 bit to signed 16 bit + if (unsigned) { + tmp = ((int)data[i] & 0xFF) - 128; + } else { + tmp = (int)data[i]; + } + energy += tmp*tmp; + } + energy /= data.length; + } + return energy; + } + + private void getVisualizer(int session) { + if (mVisualizer == null || session != mSession) { + if (session != mSession && mVisualizer != null) { + mVisualizer.release(); + mVisualizer = null; + } + try { + mVisualizer = new Visualizer(session); + mSession = session; + } catch (IllegalArgumentException e) { + Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e); + } catch (UnsupportedOperationException e) { + Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e); + } + } + assertNotNull("could not create mVisualizer", mVisualizer); + } + + private void releaseVisualizer() { + if (mVisualizer != null) { + mVisualizer.release(); + mVisualizer = null; + } + } + + private void createListenerLooper() { + + new Thread() { + @Override + public void run() { + // Set up a looper to be used by mEffect. + Looper.prepare(); + + // Save the looper so that we can terminate this thread + // after we are done with it. + mLooper = Looper.myLooper(); + + if (mVisualizer != null) { + mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() { + public void onWaveFormDataCapture( + Visualizer visualizer, byte[] waveform, int samplingRate) { + synchronized(lock) { + if (visualizer == mVisualizer) { + if (mCaptureWaveform) { + mWaveform = waveform; + lock.notify(); + } + } + } + } + + public void onFftDataCapture( + Visualizer visualizer, byte[] fft, int samplingRate) { + synchronized(lock) { + if (visualizer == mVisualizer) { + if (mCaptureFft) { + mFft = fft; + lock.notify(); + } + } + } + } + }, + 10000, + true, + true); + } + + synchronized(lock) { + mInitialized = true; + lock.notify(); + } + Looper.loop(); // Blocks forever until Looper.quit() is called. + } + }.start(); + } + /* + * Terminates the listener looper thread. + */ + private void terminateListenerLooper() { + if (mLooper != null) { + mLooper.quit(); + mLooper = null; + } + } + +} + diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java new file mode 100644 index 000000000000..9e91740ca5c2 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java @@ -0,0 +1,81 @@ +/* + * 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.mediaframeworktest.power; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; +import android.media.MediaPlayer; +import android.os.Environment; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import java.io.File; + +/** + * Junit / Instrumentation test case for the power measurment the media player + */ +public class MediaPlayerPowerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaPlayerPowerTest"; + private String MP3_POWERTEST = + Environment.getExternalStorageDirectory().toString() + "/power_sample_mp3.mp3"; + private String MP3_STREAM = "http://75.17.48.204:10088/power_media/power_sample_mp3.mp3"; + private String OGG_STREAM = "http://75.17.48.204:10088/power_media/power_sample_ogg.mp3"; + private String AAC_STREAM = "http://75.17.48.204:10088/power_media/power_sample_aac.mp3"; + + public MediaPlayerPowerTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + protected void setUp() throws Exception { + getActivity(); + super.setUp(); + + } + + public void audioPlayback(String filePath) { + try { + MediaPlayer mp = new MediaPlayer(); + mp.setDataSource(filePath); + mp.prepare(); + mp.start(); + Thread.sleep(200000); + mp.stop(); + mp.release(); + } catch (Exception e) { + Log.v(TAG, e.toString()); + assertTrue("MP3 Playback", false); + } + } + + // A very simple test case which start the audio player. + // Power measurment will be done in other application. + public void testPowerLocalMP3Playback() throws Exception { + audioPlayback(MP3_POWERTEST); + } + + public void testPowerStreamMP3Playback() throws Exception { + audioPlayback(MP3_STREAM); + } + + public void testPowerStreamOGGPlayback() throws Exception { + audioPlayback(OGG_STREAM); + } + + public void testPowerStreamAACPlayback() throws Exception { + audioPlayback(AAC_STREAM); + } +} diff --git a/native/android/Android.mk b/native/android/Android.mk index 950a1e9a7ad1..bd2b27af7f18 100644 --- a/native/android/Android.mk +++ b/native/android/Android.mk @@ -7,6 +7,7 @@ include $(CLEAR_VARS) # LOCAL_SRC_FILES:= \ asset_manager.cpp \ + configuration.cpp \ input.cpp \ looper.cpp \ native_activity.cpp \ diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp index 36c381ef1b84..3f7c1b69af17 100644 --- a/native/android/asset_manager.cpp +++ b/native/android/asset_manager.cpp @@ -17,7 +17,7 @@ #define LOG_TAG "NAsset" #include <utils/Log.h> -#include <android/asset_manager.h> +#include <android/asset_manager_jni.h> #include <utils/AssetManager.h> #include <utils/AssetDir.h> #include <utils/Asset.h> diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp new file mode 100644 index 000000000000..d76164ff418e --- /dev/null +++ b/native/android/configuration.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Configuration" +#include <utils/Log.h> + +#include <utils/AssetManager.h> + +#include <android_runtime/android_content_res_Configuration.h> + +using namespace android; + +AConfiguration* AConfiguration_new() { + AConfiguration* config = new AConfiguration; + memset(config, 0, sizeof(AConfiguration)); + return config; +} + +void AConfiguration_delete(AConfiguration* config) { + delete config; +} + +void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) { + ((AssetManager*)am)->getConfiguration(out); +} + +void AConfiguration_copy(AConfiguration* dest, AConfiguration* src) { + *dest = *src; +} + +int32_t AConfiguration_getMcc(AConfiguration* config) { + return config->mcc; +} + +int32_t AConfiguration_getMnc(AConfiguration* config) { + return config->mnc; +} + +void AConfiguration_getLanguage(AConfiguration* config, char* outLanguage) { + outLanguage[0] = config->language[0]; + outLanguage[1] = config->language[1]; +} + +void AConfiguration_getCountry(AConfiguration* config, char* outCountry) { + outCountry[0] = config->country[0]; + outCountry[1] = config->country[1]; +} + +int32_t AConfiguration_getOrientation(AConfiguration* config) { + return config->orientation; +} + +int32_t AConfiguration_getTouchscreen(AConfiguration* config) { + return config->touchscreen; +} + +int32_t AConfiguration_getDensity(AConfiguration* config) { + return config->density; +} + +int32_t AConfiguration_getKeyboard(AConfiguration* config) { + return config->keyboard; +} + +int32_t AConfiguration_getNavigation(AConfiguration* config) { + return config->navigation; +} + +int32_t AConfiguration_getKeysHidden(AConfiguration* config) { + return config->inputFlags&ResTable_config::MASK_KEYSHIDDEN; +} + +int32_t AConfiguration_getNavHidden(AConfiguration* config) { + return (config->inputFlags&ResTable_config::MASK_NAVHIDDEN) + >> ResTable_config::SHIFT_NAVHIDDEN; +} + +int32_t AConfiguration_getSdkVersion(AConfiguration* config) { + return config->sdkVersion; +} + +int32_t AConfiguration_getScreenSize(AConfiguration* config) { + return config->screenLayout&ResTable_config::MASK_SCREENSIZE; +} + +int32_t AConfiguration_getScreenLong(AConfiguration* config) { + return (config->screenLayout&ResTable_config::MASK_SCREENLONG) + >> ResTable_config::SHIFT_SCREENLONG; +} + +int32_t AConfiguration_getUiModeType(AConfiguration* config) { + return config->uiMode&ResTable_config::MASK_UI_MODE_TYPE; +} + +int32_t AConfiguration_getUiModeNight(AConfiguration* config) { + return (config->uiMode&ResTable_config::MASK_UI_MODE_NIGHT) + >> ResTable_config::SHIFT_UI_MODE_NIGHT; + +} + +// ---------------------------------------------------------------------- + +void AConfiguration_setMcc(AConfiguration* config, int32_t mcc) { + config->mcc = mcc; +} + +void AConfiguration_setMnc(AConfiguration* config, int32_t mnc) { + config->mnc = mnc; +} + +void AConfiguration_setLanguage(AConfiguration* config, const char* language) { + config->language[0] = language[0]; + config->language[1] = language[1]; +} + +void AConfiguration_setCountry(AConfiguration* config, const char* country) { + config->country[0] = country[0]; + config->country[1] = country[1]; +} + +void AConfiguration_setOrientation(AConfiguration* config, int32_t orientation) { + config->orientation = orientation; +} + +void AConfiguration_setTouchscreen(AConfiguration* config, int32_t touchscreen) { + config->touchscreen = touchscreen; +} + +void AConfiguration_setDensity(AConfiguration* config, int32_t density) { + config->density = density; +} + +void AConfiguration_setKeyboard(AConfiguration* config, int32_t keyboard) { + config->keyboard = keyboard; +} + +void AConfiguration_setNavigation(AConfiguration* config, int32_t navigation) { + config->navigation = navigation; +} + +void AConfiguration_setKeysHidden(AConfiguration* config, int32_t keysHidden) { + config->inputFlags = (config->inputFlags&~ResTable_config::MASK_KEYSHIDDEN) + | (keysHidden&ResTable_config::MASK_KEYSHIDDEN); +} + +void AConfiguration_setNavHidden(AConfiguration* config, int32_t navHidden) { + config->inputFlags = (config->inputFlags&~ResTable_config::MASK_NAVHIDDEN) + | ((navHidden<<ResTable_config::SHIFT_NAVHIDDEN)&ResTable_config::MASK_NAVHIDDEN); +} + +void AConfiguration_setSdkVersion(AConfiguration* config, int32_t sdkVersion) { + config->sdkVersion = sdkVersion; +} + +void AConfiguration_setScreenSize(AConfiguration* config, int32_t screenSize) { + config->screenLayout = (config->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | (screenSize&ResTable_config::MASK_SCREENSIZE); +} + +void AConfiguration_setScreenLong(AConfiguration* config, int32_t screenLong) { + config->screenLayout = (config->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ((screenLong<<ResTable_config::SHIFT_SCREENLONG)&ResTable_config::MASK_SCREENLONG); +} + +void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType) { + config->uiMode = (config->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | (uiModeType&ResTable_config::MASK_UI_MODE_TYPE); +} + +void AConfiguration_setUiModeNight(AConfiguration* config, int32_t uiModeNight) { + config->uiMode = (config->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ((uiModeNight<<ResTable_config::SHIFT_UI_MODE_NIGHT)&ResTable_config::MASK_UI_MODE_NIGHT); + +} + +// ---------------------------------------------------------------------- + +int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2) { + return (config1->diff(*config2)); +} + +int32_t AConfiguration_match(AConfiguration* base, AConfiguration* requested) { + return base->match(*requested); +} + +int32_t AConfiguration_isBetterThan(AConfiguration* base, AConfiguration* test, + AConfiguration* requested) { + return base->isBetterThan(*test, requested); +} diff --git a/native/copy-to-ndk.sh b/native/copy-to-ndk.sh new file mode 100644 index 000000000000..4f5a16a4b595 --- /dev/null +++ b/native/copy-to-ndk.sh @@ -0,0 +1,55 @@ +# Take care of copying current header files over to the correct +# location in the NDK. + +copyndkheaders() { + local CURR_PLATFORM=android-9 + local ALL_PLATFORMS="$CURR_PLATFORM android-8 android-5 android-4 android-3" + + local SRC_HEADERS=$ANDROID_BUILD_TOP/frameworks/base/native/include/android + local NDK_PLATFORMS=$ANDROID_BUILD_TOP/development/ndk/platforms + local DST_HEADERS=$NDK_PLATFORMS/$CURR_PLATFORM + + local SRC_LIB_ANDROID=$ANDROID_PRODUCT_OUT/system/lib/libandroid.so + local DST_LIB_ANDROID=$NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/lib/libandroid.so + + local didsomething="" + + #echo "SRC_HEADERS: $SRC_HEADERS" + + for i in $(cd $SRC_HEADERS; ls *.h); do + local src=$SRC_HEADERS/$i + local changed="" + for j in $ALL_PLATFORMS; do + local dst=$NDK_PLATFORMS/$j/arch-arm/usr/include/android/$i + if [ "$changed" == "" -a -e $dst ]; then + #echo "Exists: $dst" + if diff $src $dst >/dev/null; then + echo "$i: has not changed from $j" >/dev/null + changed="false" + else + changed="true" + echo "$i: has changed from $j" >/dev/null + fi + fi + done + if [ "$changed" == "true" -o "$changed" == "" ]; then + echo "Updating: $i" + cp $src $NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/include/android/$i + didsomething="true" + fi + done + + if diff $SRC_LIB_ANDROID $DST_LIB_ANDROID >/dev/null; then + echo "libandroid.so: has not changed" >/dev/null + else + echo "Updating: $DST_LIB_ANDROID" + cp $SRC_LIB_ANDROID $DST_LIB_ANDROID + didsomething="true" + fi + if [ "$didsomething" != "" ]; then + echo "Headers changed... rebuilding platforms." + sh $ANDROID_BUILD_TOP/ndk/build/tools/build-platforms.sh + fi +} + +copyndkheaders diff --git a/native/include/android/asset_manager.h b/native/include/android/asset_manager.h index 89989f8cc4f5..4fa0ef3a3a12 100644 --- a/native/include/android/asset_manager.h +++ b/native/include/android/asset_manager.h @@ -18,8 +18,6 @@ #ifndef ANDROID_ASSET_MANAGER_H #define ANDROID_ASSET_MANAGER_H -#include <jni.h> - #ifdef __cplusplus extern "C" { #endif @@ -43,14 +41,6 @@ enum { /** - * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager - * object. Note that the caller is responsible for obtaining and holding a VM reference - * to the jobject to prevent its being garbage collected while the native object is - * in use. - */ -AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager); - -/** * Open the named directory within the asset hierarchy. The directory can then * be inspected with the AAssetDir functions. To open the top-level directory, * pass in "" as the dirName. diff --git a/native/include/android/asset_manager_jni.h b/native/include/android/asset_manager_jni.h new file mode 100644 index 000000000000..aec2d3c97c07 --- /dev/null +++ b/native/include/android/asset_manager_jni.h @@ -0,0 +1,40 @@ +/* + * 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_ASSET_MANAGER_JNI_H +#define ANDROID_ASSET_MANAGER_JNI_H + +#include <android/asset_manager.h> +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager + * object. Note that the caller is responsible for obtaining and holding a VM reference + * to the jobject to prevent its being garbage collected while the native object is + * in use. + */ +AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager); + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_ASSET_MANAGER_JNI_H diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h new file mode 100644 index 000000000000..79b9b1e302ed --- /dev/null +++ b/native/include/android/configuration.h @@ -0,0 +1,319 @@ +/* + * 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_CONFIGURATION_H +#define ANDROID_CONFIGURATION_H + +#include <android/asset_manager.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct AConfiguration; +typedef struct AConfiguration AConfiguration; + +enum { + ACONFIGURATION_ORIENTATION_ANY = 0x0000, + ACONFIGURATION_ORIENTATION_PORT = 0x0001, + ACONFIGURATION_ORIENTATION_LAND = 0x0002, + ACONFIGURATION_ORIENTATION_SQUARE = 0x0003, + + ACONFIGURATION_TOUCHSCREEN_ANY = 0x0000, + ACONFIGURATION_TOUCHSCREEN_NOTOUCH = 0x0001, + ACONFIGURATION_TOUCHSCREEN_STYLUS = 0x0002, + ACONFIGURATION_TOUCHSCREEN_FINGER = 0x0003, + + ACONFIGURATION_DENSITY_DEFAULT = 0, + ACONFIGURATION_DENSITY_LOW = 120, + ACONFIGURATION_DENSITY_MEDIUM = 160, + ACONFIGURATION_DENSITY_HIGH = 240, + ACONFIGURATION_DENSITY_NONE = 0xffff, + + ACONFIGURATION_KEYBOARD_ANY = 0x0000, + ACONFIGURATION_KEYBOARD_NOKEYS = 0x0001, + ACONFIGURATION_KEYBOARD_QWERTY = 0x0002, + ACONFIGURATION_KEYBOARD_12KEY = 0x0003, + + ACONFIGURATION_NAVIGATION_ANY = 0x0000, + ACONFIGURATION_NAVIGATION_NONAV = 0x0001, + ACONFIGURATION_NAVIGATION_DPAD = 0x0002, + ACONFIGURATION_NAVIGATION_TRACKBALL = 0x0003, + ACONFIGURATION_NAVIGATION_WHEEL = 0x0004, + + ACONFIGURATION_KEYSHIDDEN_ANY = 0x0000, + ACONFIGURATION_KEYSHIDDEN_NO = 0x0001, + ACONFIGURATION_KEYSHIDDEN_YES = 0x0002, + ACONFIGURATION_KEYSHIDDEN_SOFT = 0x0003, + + ACONFIGURATION_NAVHIDDEN_ANY = 0x0000, + ACONFIGURATION_NAVHIDDEN_NO = 0x0001, + ACONFIGURATION_NAVHIDDEN_YES = 0x0002, + + ACONFIGURATION_SCREENSIZE_ANY = 0x00, + ACONFIGURATION_SCREENSIZE_SMALL = 0x01, + ACONFIGURATION_SCREENSIZE_NORMAL = 0x02, + ACONFIGURATION_SCREENSIZE_LARGE = 0x03, + ACONFIGURATION_SCREENSIZE_XLARGE = 0x04, + + ACONFIGURATION_SCREENLONG_ANY = 0x00, + ACONFIGURATION_SCREENLONG_NO = 0x1, + ACONFIGURATION_SCREENLONG_YES = 0x2, + + ACONFIGURATION_UI_MODE_TYPE_ANY = 0x00, + ACONFIGURATION_UI_MODE_TYPE_NORMAL = 0x01, + ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02, + 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_MCC = 0x0001, + ACONFIGURATION_MNC = 0x0002, + ACONFIGURATION_LOCALE = 0x0004, + ACONFIGURATION_TOUCHSCREEN = 0x0008, + ACONFIGURATION_KEYBOARD = 0x0010, + ACONFIGURATION_KEYBOARD_HIDDEN = 0x0020, + ACONFIGURATION_NAVIGATION = 0x0040, + ACONFIGURATION_ORIENTATION = 0x0080, + ACONFIGURATION_DENSITY = 0x0100, + ACONFIGURATION_SCREEN_SIZE = 0x0200, + ACONFIGURATION_VERSION = 0x0400, + ACONFIGURATION_SCREEN_LAYOUT = 0x0800, + ACONFIGURATION_UI_MODE = 0x1000, +}; + +/** + * Create a new AConfiguration, initialized with no values set. + */ +AConfiguration* AConfiguration_new(); + +/** + * Free an AConfiguration that was previously created with + * AConfiguration_new(). + */ +void AConfiguration_delete(AConfiguration* config); + +/** + * Create and return a new AConfiguration based on the current configuration in + * use in the given AssetManager. + */ +void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am); + +/** + * Copy the contents of 'src' to 'dest'. + */ +void AConfiguration_copy(AConfiguration* dest, AConfiguration* src); + +/** + * Return the current MCC set in the configuration. 0 if not set. + */ +int32_t AConfiguration_getMcc(AConfiguration* config); + +/** + * Set the current MCC in the configuration. 0 to clear. + */ +void AConfiguration_setMcc(AConfiguration* config, int32_t mcc); + +/** + * Return the current MNC set in the configuration. 0 if not set. + */ +int32_t AConfiguration_getMnc(AConfiguration* config); + +/** + * Set the current MNC in the configuration. 0 to clear. + */ +void AConfiguration_setMnc(AConfiguration* config, int32_t mnc); + +/** + * Return the current language code set in the configuration. The output will + * be filled with an array of two characters. They are not 0-terminated. If + * a language is not set, they will be 0. + */ +void AConfiguration_getLanguage(AConfiguration* config, char* outLanguage); + +/** + * Set the current language code in the configuration, from the first two + * characters in the string. + */ +void AConfiguration_setLanguage(AConfiguration* config, const char* language); + +/** + * Return the current country code set in the configuration. The output will + * be filled with an array of two characters. They are not 0-terminated. If + * a country is not set, they will be 0. + */ +void AConfiguration_getCountry(AConfiguration* config, char* outCountry); + +/** + * Set the current country code in the configuration, from the first two + * characters in the string. + */ +void AConfiguration_setCountry(AConfiguration* config, const char* country); + +/** + * Return the current ACONFIGURATION_ORIENTATION_* set in the configuration. + */ +int32_t AConfiguration_getOrientation(AConfiguration* config); + +/** + * Set the current orientation in the configuration. + */ +void AConfiguration_setOrientation(AConfiguration* config, int32_t orientation); + +/** + * Return the current ACONFIGURATION_TOUCHSCREEN_* set in the configuration. + */ +int32_t AConfiguration_getTouchscreen(AConfiguration* config); + +/** + * Set the current touchscreen in the configuration. + */ +void AConfiguration_setTouchscreen(AConfiguration* config, int32_t touchscreen); + +/** + * Return the current ACONFIGURATION_DENSITY_* set in the configuration. + */ +int32_t AConfiguration_getDensity(AConfiguration* config); + +/** + * Set the current density in the configuration. + */ +void AConfiguration_setDensity(AConfiguration* config, int32_t density); + +/** + * Return the current ACONFIGURATION_KEYBOARD_* set in the configuration. + */ +int32_t AConfiguration_getKeyboard(AConfiguration* config); + +/** + * Set the current keyboard in the configuration. + */ +void AConfiguration_setKeyboard(AConfiguration* config, int32_t keyboard); + +/** + * Return the current ACONFIGURATION_NAVIGATION_* set in the configuration. + */ +int32_t AConfiguration_getNavigation(AConfiguration* config); + +/** + * Set the current navigation in the configuration. + */ +void AConfiguration_setNavigation(AConfiguration* config, int32_t navigation); + +/** + * Return the current ACONFIGURATION_KEYSHIDDEN_* set in the configuration. + */ +int32_t AConfiguration_getKeysHidden(AConfiguration* config); + +/** + * Set the current keys hidden in the configuration. + */ +void AConfiguration_setKeysHidden(AConfiguration* config, int32_t keysHidden); + +/** + * Return the current ACONFIGURATION_NAVHIDDEN_* set in the configuration. + */ +int32_t AConfiguration_getNavHidden(AConfiguration* config); + +/** + * Set the current nav hidden in the configuration. + */ +void AConfiguration_setNavHidden(AConfiguration* config, int32_t navHidden); + +/** + * Return the current SDK (API) version set in the configuration. + */ +int32_t AConfiguration_getSdkVersion(AConfiguration* config); + +/** + * Set the current SDK version in the configuration. + */ +void AConfiguration_setSdkVersion(AConfiguration* config, int32_t sdkVersion); + +/** + * Return the current ACONFIGURATION_SCREENSIZE_* set in the configuration. + */ +int32_t AConfiguration_getScreenSize(AConfiguration* config); + +/** + * Set the current screen size in the configuration. + */ +void AConfiguration_setScreenSize(AConfiguration* config, int32_t screenSize); + +/** + * Return the current ACONFIGURATION_SCREENLONG_* set in the configuration. + */ +int32_t AConfiguration_getScreenLong(AConfiguration* config); + +/** + * Set the current screen long in the configuration. + */ +void AConfiguration_setScreenLong(AConfiguration* config, int32_t screenLong); + +/** + * Return the current ACONFIGURATION_UI_MODE_TYPE_* set in the configuration. + */ +int32_t AConfiguration_getUiModeType(AConfiguration* config); + +/** + * Set the current UI mode type in the configuration. + */ +void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType); + +/** + * Return the current ACONFIGURATION_UI_MODE_NIGHT_* set in the configuration. + */ +int32_t AConfiguration_getUiModeNight(AConfiguration* config); + +/** + * Set the current UI mode night in the configuration. + */ +void AConfiguration_setUiModeNight(AConfiguration* config, int32_t uiModeNight); + +/** + * Perform a diff between two configurations. Returns a bit mask of + * ACONFIGURATION_* constants, each bit set meaning that configuration element + * is different between them. + */ +int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2); + +/** + * Determine whether 'base' is a valid configuration for use within the + * environment 'requested'. Returns 0 if there are any values in 'base' + * that conflict with 'requested'. Returns 1 if it does not conflict. + */ +int32_t AConfiguration_match(AConfiguration* base, AConfiguration* requested); + +/** + * Determine whether the configuration in 'test' is better than the existing + * configuration in 'base'. If 'requested' is non-NULL, this decision is based + * on the overall configuration given there. If it is NULL, this decision is + * simply based on which configuration is more specific. Returns non-0 if + * 'test' is better than 'base'. + * + * This assumes you have already filtered the configurations with + * AConfiguration_match(). + */ +int32_t AConfiguration_isBetterThan(AConfiguration* base, AConfiguration* test, + AConfiguration* requested); + +#ifdef __cplusplus +}; +#endif + +#endif // ANDROID_CONFIGURATION_H diff --git a/native/include/android/native_activity.h b/native/include/android/native_activity.h index ee4204d884f2..d74e1ce5a3d3 100644 --- a/native/include/android/native_activity.h +++ b/native/include/android/native_activity.h @@ -197,6 +197,12 @@ typedef struct ANativeActivityCallbacks { void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect); /** + * The current device AConfiguration has changed. The new configuration can + * be retrieved from assetManager. + */ + void (*onConfigurationChanged)(ANativeActivity* activity); + + /** * The system is running low on memory. Use this callback to release * resources you do not need, to help the system avoid killing more * important processes. @@ -208,7 +214,9 @@ typedef struct ANativeActivityCallbacks { * This is the function that must be in the native code to instantiate the * application's native activity. It is called with the activity instance (see * above); if the code is being instantiated from a previously saved instance, - * the savedState will be non-NULL and point to the saved data. + * the savedState will be non-NULL and point to the saved data. You must make + * any copy of this data you need -- it will be released after you return from + * this function. */ typedef void ANativeActivity_createFunc(ANativeActivity* activity, void* savedState, size_t savedStateSize); diff --git a/native/include/android/native_window.h b/native/include/android/native_window.h index 7599d7e550f6..ad03d0e930da 100644 --- a/native/include/android/native_window.h +++ b/native/include/android/native_window.h @@ -36,12 +36,23 @@ struct ANativeWindow; typedef struct ANativeWindow ANativeWindow; typedef struct ANativeWindow_Buffer { + // The number of pixels that are show horizontally. int32_t width; + + // The number of pixels that are shown vertically. int32_t height; + + // The number of *pixels* that a line in the buffer takes in + // memory. This may be >= width. int32_t stride; + + // The format of the buffer. One of WINDOW_FORMAT_* int32_t format; + + // The actual bits. void* bits; + // Do not touch. uint32_t reserved[6]; } ANativeWindow_Buffer; diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp index 23837efa9f49..dcf8735c634d 100644 --- a/opengl/libs/EGL/getProcAddress.cpp +++ b/opengl/libs/EGL/getProcAddress.cpp @@ -27,9 +27,12 @@ namespace android { // ---------------------------------------------------------------------------- #undef API_ENTRY -#undef CALL_GL_API +#undef CALL_GL_EXTENSION_API #undef GL_EXTENSION #undef GL_EXTENSION_NAME +#undef GL_EXTENSION_ARRAY +#undef GL_EXTENSION_LIST +#undef GET_TLS #if defined(__arm__) @@ -60,7 +63,7 @@ namespace android { : \ ); - #define GL_EXTENSION_NAME(_n) __glExtFwd##_n + #define GL_EXTENSION_NAME(_n) __glExtFwd##_n #define GL_EXTENSION(_n) \ void API_ENTRY(GL_EXTENSION_NAME(_n))() { \ @@ -78,97 +81,40 @@ namespace android { #endif -GL_EXTENSION(0) -GL_EXTENSION(1) -GL_EXTENSION(2) -GL_EXTENSION(3) -GL_EXTENSION(4) -GL_EXTENSION(5) -GL_EXTENSION(6) -GL_EXTENSION(7) -GL_EXTENSION(8) -GL_EXTENSION(9) -GL_EXTENSION(10) -GL_EXTENSION(11) -GL_EXTENSION(12) -GL_EXTENSION(13) -GL_EXTENSION(14) -GL_EXTENSION(15) - -GL_EXTENSION(16) -GL_EXTENSION(17) -GL_EXTENSION(18) -GL_EXTENSION(19) -GL_EXTENSION(20) -GL_EXTENSION(21) -GL_EXTENSION(22) -GL_EXTENSION(23) -GL_EXTENSION(24) -GL_EXTENSION(25) -GL_EXTENSION(26) -GL_EXTENSION(27) -GL_EXTENSION(28) -GL_EXTENSION(29) -GL_EXTENSION(30) -GL_EXTENSION(31) - -GL_EXTENSION(32) -GL_EXTENSION(33) -GL_EXTENSION(34) -GL_EXTENSION(35) -GL_EXTENSION(36) -GL_EXTENSION(37) -GL_EXTENSION(38) -GL_EXTENSION(39) -GL_EXTENSION(40) -GL_EXTENSION(41) -GL_EXTENSION(42) -GL_EXTENSION(43) -GL_EXTENSION(44) -GL_EXTENSION(45) -GL_EXTENSION(46) -GL_EXTENSION(47) - -GL_EXTENSION(48) -GL_EXTENSION(49) -GL_EXTENSION(50) -GL_EXTENSION(51) -GL_EXTENSION(52) -GL_EXTENSION(53) -GL_EXTENSION(54) -GL_EXTENSION(55) -GL_EXTENSION(56) -GL_EXTENSION(57) -GL_EXTENSION(58) -GL_EXTENSION(59) -GL_EXTENSION(60) -GL_EXTENSION(61) -GL_EXTENSION(62) -GL_EXTENSION(63) +#define GL_EXTENSION_LIST(name) \ + name(0) name(1) name(2) name(3) \ + name(4) name(5) name(6) name(7) \ + name(8) name(9) name(10) name(11) \ + name(12) name(13) name(14) name(15) \ + name(16) name(17) name(18) name(19) \ + name(20) name(21) name(22) name(23) \ + name(24) name(25) name(26) name(27) \ + name(28) name(29) name(30) name(31) \ + name(32) name(33) name(34) name(35) \ + name(36) name(37) name(38) name(39) \ + name(40) name(41) name(42) name(43) \ + name(44) name(45) name(46) name(47) \ + name(48) name(49) name(50) name(51) \ + name(52) name(53) name(54) name(55) \ + name(56) name(57) name(58) name(59) \ + name(60) name(61) name(62) name(63) + + +GL_EXTENSION_LIST( GL_EXTENSION ) + +#define GL_EXTENSION_ARRAY(_n) GL_EXTENSION_NAME(_n), extern const __eglMustCastToProperFunctionPointerType gExtensionForwarders[MAX_NUMBER_OF_GL_EXTENSIONS] = { - GL_EXTENSION_NAME(0), GL_EXTENSION_NAME(1), GL_EXTENSION_NAME(2), GL_EXTENSION_NAME(3), - GL_EXTENSION_NAME(4), GL_EXTENSION_NAME(5), GL_EXTENSION_NAME(6), GL_EXTENSION_NAME(7), - GL_EXTENSION_NAME(8), GL_EXTENSION_NAME(9), GL_EXTENSION_NAME(10), GL_EXTENSION_NAME(11), - GL_EXTENSION_NAME(12), GL_EXTENSION_NAME(13), GL_EXTENSION_NAME(14), GL_EXTENSION_NAME(15), - GL_EXTENSION_NAME(16), GL_EXTENSION_NAME(17), GL_EXTENSION_NAME(18), GL_EXTENSION_NAME(19), - GL_EXTENSION_NAME(20), GL_EXTENSION_NAME(21), GL_EXTENSION_NAME(22), GL_EXTENSION_NAME(23), - GL_EXTENSION_NAME(24), GL_EXTENSION_NAME(25), GL_EXTENSION_NAME(26), GL_EXTENSION_NAME(27), - GL_EXTENSION_NAME(28), GL_EXTENSION_NAME(29), GL_EXTENSION_NAME(30), GL_EXTENSION_NAME(31), - GL_EXTENSION_NAME(32), GL_EXTENSION_NAME(33), GL_EXTENSION_NAME(34), GL_EXTENSION_NAME(35), - GL_EXTENSION_NAME(36), GL_EXTENSION_NAME(37), GL_EXTENSION_NAME(38), GL_EXTENSION_NAME(39), - GL_EXTENSION_NAME(40), GL_EXTENSION_NAME(41), GL_EXTENSION_NAME(42), GL_EXTENSION_NAME(43), - GL_EXTENSION_NAME(44), GL_EXTENSION_NAME(45), GL_EXTENSION_NAME(46), GL_EXTENSION_NAME(47), - GL_EXTENSION_NAME(48), GL_EXTENSION_NAME(49), GL_EXTENSION_NAME(50), GL_EXTENSION_NAME(51), - GL_EXTENSION_NAME(52), GL_EXTENSION_NAME(53), GL_EXTENSION_NAME(54), GL_EXTENSION_NAME(55), - GL_EXTENSION_NAME(56), GL_EXTENSION_NAME(57), GL_EXTENSION_NAME(58), GL_EXTENSION_NAME(59), - GL_EXTENSION_NAME(60), GL_EXTENSION_NAME(61), GL_EXTENSION_NAME(62), GL_EXTENSION_NAME(63) + GL_EXTENSION_LIST( GL_EXTENSION_ARRAY ) }; +#undef GET_TLS +#undef GL_EXTENSION_LIST +#undef GL_EXTENSION_ARRAY #undef GL_EXTENSION_NAME #undef GL_EXTENSION #undef API_ENTRY -#undef CALL_GL_API +#undef CALL_GL_EXTENSION_API // ---------------------------------------------------------------------------- }; // namespace android diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp index 924737e697db..a12edf238e7d 100644 --- a/opengl/libs/GLES2/gl2.cpp +++ b/opengl/libs/GLES2/gl2.cpp @@ -39,6 +39,8 @@ using namespace android; #undef CALL_GL_API #undef CALL_GL_API_RETURN +#define DEBUG_CALL_GL_API 0 + #if USE_FAST_TLS_KEY #ifdef HAVE_ARM_TLS_REGISTER @@ -73,10 +75,24 @@ using namespace android; #define API_ENTRY(_api) _api +#if DEBUG_CALL_GL_API + + #define CALL_GL_API(_api, ...) \ + gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl; \ + _c->_api(__VA_ARGS__); \ + GLenum status = GL_NO_ERROR; \ + while ((status = glGetError()) != GL_NO_ERROR) { \ + LOGD("[" #_api "] 0x%x", status); \ + } + +#else + #define CALL_GL_API(_api, ...) \ gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl; \ - _c->_api(__VA_ARGS__) - + _c->_api(__VA_ARGS__); + +#endif + #define CALL_GL_API_RETURN(_api, ...) \ gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl; \ return _c->_api(__VA_ARGS__) diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index f1c6532a90fb..c6e0a2423a1f 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -24,6 +24,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.res.ObbInfo; +import android.content.res.ObbScanner; import android.net.Uri; import android.os.Environment; import android.os.IBinder; @@ -142,6 +144,10 @@ public class DefaultContainerService extends IntentService { public boolean checkFreeStorage(boolean external, Uri fileUri) { return checkFreeStorageInner(external, fileUri); } + + public ObbInfo getObbInfo(String filename) { + return ObbScanner.getObbInfo(filename); + } }; public DefaultContainerService() { diff --git a/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png b/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png Binary files differnew file mode 100644 index 000000000000..d809b84004e9 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/button_frame_default.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png b/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png Binary files differnew file mode 100644 index 000000000000..5cc007c0bf39 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/button_frame_pressed.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png Binary files differnew file mode 100644 index 000000000000..92fc7f8da470 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png Binary files differnew file mode 100644 index 000000000000..77f09acb0fdd --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png Binary files differnew file mode 100644 index 000000000000..e375ee97a245 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/system_panel_airplane_default.png diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png Binary files differnew file mode 100644 index 000000000000..f62502b12519 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/system_panel_brightness_default.png diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png Binary files differnew file mode 100644 index 000000000000..e887fb8adbee --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_default.png diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png Binary files differnew file mode 100644 index 000000000000..58159d5a836d --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/system_panel_orientation_locked.png diff --git a/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png b/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png Binary files differnew file mode 100644 index 000000000000..6e857b5ef232 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/system_panel_sound_default.png diff --git a/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png b/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png Binary files differnew file mode 100644 index 000000000000..7ab1f261cb27 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/button_frame_default.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png b/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png Binary files differnew file mode 100644 index 000000000000..08f7a4d6bb45 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/button_frame_pressed.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png Binary files differnew file mode 100644 index 000000000000..4d197179368d --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png Binary files differnew file mode 100644 index 000000000000..830cbd521858 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png Binary files differnew file mode 100644 index 000000000000..eb875323cd6a --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/system_panel_airplane_default.png diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png Binary files differnew file mode 100644 index 000000000000..3bfc83e8fae3 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/system_panel_brightness_default.png diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png Binary files differnew file mode 100644 index 000000000000..0d8479caf1c2 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_default.png diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png Binary files differnew file mode 100644 index 000000000000..8f1d26ceb8c3 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/system_panel_orientation_locked.png diff --git a/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png b/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png Binary files differnew file mode 100644 index 000000000000..22636d604248 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/system_panel_sound_default.png diff --git a/packages/SystemUI/res/drawable/button_frame.xml b/packages/SystemUI/res/drawable/button_frame.xml new file mode 100644 index 000000000000..5db39a5da562 --- /dev/null +++ b/packages/SystemUI/res/drawable/button_frame.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/button_frame_pressed" /> + <item android:drawable="@drawable/button_frame_default" /> +</selector> + diff --git a/packages/SystemUI/res/drawable/status_bar_expand.xml b/packages/SystemUI/res/drawable/status_bar_expand.xml new file mode 100644 index 000000000000..f966920344cd --- /dev/null +++ b/packages/SystemUI/res/drawable/status_bar_expand.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@drawable/status_bar_expand_pressed" /> + <item android:drawable="@drawable/status_bar_expand_default" /> +</selector> + diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index 9ae2c250139a..ffb1571a44c1 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -39,13 +39,13 @@ android:onClick="notificationIconsClicked" android:background="@drawable/status_bar_icon_tray" > - <view + <ImageView class="com.android.systemui.statusbar.tablet.NotificationIconArea$MoreView" - android:id="@+id/more" + android:id="@+id/expand" android:layout_width="wrap_content" - android:layout_height="match_parent" - android:src="@drawable/stat_notify_more" - android:layout_marginLeft="10dip" + android:layout_height="wrap_content" + android:src="@drawable/status_bar_expand" + android:onClick="notificationIconsClicked" /> <view class="com.android.systemui.statusbar.tablet.NotificationIconArea$IconLayout" @@ -64,39 +64,47 @@ </com.android.systemui.statusbar.tablet.NotificationIconArea> - <RelativeLayout android:id="@+id/systemInfo" - android:layout_width="200dip" + <LinearLayout android:id="@+id/ticker" + android:layout_width="300dip" android:layout_height="match_parent" - android:layout_centerHorizontal="true" - android:clickable="true" - android:onClick="systemInfoClicked" + android:paddingLeft="6dip" + android:animationCache="false" + android:layout_alignLeft="@id/notificationIcons" + android:layout_alignTop="@id/notificationIcons" + android:orientation="horizontal" + android:visibility="gone" > - <com.android.systemui.statusbar.Clock - android:id="@+id/clock" - android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerInParent="true" - android:singleLine="true" - android:textSize="16sp" - android:textStyle="bold" - android:padding="6dip" - /> - <ImageView + <ImageView android:id="@+id/tickerIcon" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toLeftOf="@id/clock" - android:layout_centerVertical="true" - android:src="@drawable/dots_empty" + android:layout_height="match_parent" + android:layout_marginRight="8dip" /> - <ImageView - android:layout_width="wrap_content" + <com.android.systemui.statusbar.TickerView android:id="@+id/tickerText" + android:layout_width="0dip" + android:layout_weight="1" android:layout_height="wrap_content" - android:layout_toRightOf="@id/clock" - android:layout_centerVertical="true" - android:src="@drawable/dots_full" - /> - </RelativeLayout> + android:paddingTop="2dip" + android:paddingRight="10dip"> + <TextView + android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + /> + <TextView + android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + /> + </com.android.systemui.statusbar.TickerView> + </LinearLayout> + + <include layout="@layout/status_bar_center" + android:layout_width="100dip" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + /> <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back" android:layout_width="wrap_content" @@ -125,60 +133,5 @@ android:src="@drawable/status_bar_home" systemui:keyCode="3" /> - -<!-- - - <LinearLayout android:id="@+id/ticker" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="6dip" - android:animationCache="false" - android:orientation="horizontal" > - <ImageSwitcher android:id="@+id/tickerIcon" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginRight="8dip" - > - <com.android.systemui.statusbar.AnimatedImageView - android:layout_width="25dip" - android:layout_height="25dip" - /> - <com.android.systemui.statusbar.AnimatedImageView - android:layout_width="25dip" - android:layout_height="25dip" - /> - </ImageSwitcher> - <com.android.systemui.statusbar.TickerView android:id="@+id/tickerText" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="wrap_content" - android:paddingTop="2dip" - android:paddingRight="10dip"> - <TextView - android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - /> - <TextView - android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - /> - </com.android.systemui.statusbar.TickerView> - </LinearLayout> - - <com.android.systemui.statusbar.DateView android:id="@+id/date" - android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:singleLine="true" - android:gravity="center_vertical|left" - android:paddingLeft="6px" - android:paddingRight="6px" - android:background="@drawable/status_bar_background" - /> ---> </RelativeLayout> diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_center.xml b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml new file mode 100644 index 000000000000..c32e997d947a --- /dev/null +++ b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml @@ -0,0 +1,54 @@ +<?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. +--> + +<!-- Center of status bar: System info display, system info panel trigger --> +<RelativeLayout android:id="@+id/systemInfo" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" + android:layout_width="100dip" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:clickable="true" + android:onClick="systemInfoClicked" + > + <com.android.systemui.statusbar.Clock + style="@*android:style/TextAppearance.StatusBar.Icon" + android:id="@+id/clock" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:gravity="center" + android:textSize="16sp" + android:textStyle="bold" + android:padding="2dip" + /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@id/clock" + android:src="@drawable/dots_empty" + /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/clock" + android:src="@drawable/dots_full" + /> +</RelativeLayout> + + diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml index 84a718a70448..3489eec47d37 100644 --- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml +++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml @@ -46,17 +46,23 @@ android:background="@android:drawable/divider_horizontal_dark" /> - <LinearLayout - android:id="@+id/content" - android:layout_width="match_parent" + <ScrollView + android:id="@+id/notificationScroller" android:layout_height="wrap_content" - android:gravity="center_horizontal|bottom" - android:animationCache="false" - android:orientation="vertical" - android:background="@drawable/status_bar_background" - android:clickable="true" - android:focusable="true" - android:descendantFocusability="afterDescendants" + android:layout_width="match_parent" > - </LinearLayout> + <LinearLayout + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal|bottom" + android:animationCache="false" + android:orientation="vertical" + android:background="@drawable/status_bar_background" + android:clickable="true" + android:focusable="true" + android:descendantFocusability="afterDescendants" + > + </LinearLayout> + </ScrollView> </LinearLayout> diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml index 2222d082baa2..fc3790078301 100644 --- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml +++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml @@ -2,7 +2,7 @@ <!-- /* apps/common/assets/default/default/skins/StatusBar.xml ** -** Copyright 2006, The Android Open Source Project +** 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. @@ -18,35 +18,118 @@ */ --> -<!-- android:background="@drawable/status_bar_closed_default_background" --> -<FrameLayout +<com.android.systemui.statusbar.tablet.SystemPanel xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_height="300dip" - android:layout_width="400dip" - android:paddingLeft="8dip" - android:paddingRight="8dip" + android:layout_height="wrap_content" + android:layout_width="match_parent" android:background="#FF000000" + android:orientation="vertical" > - - <RelativeLayout - android:id="@+id/content" + + <TextView android:id="@+id/settings_button" + style="?android:attr/textAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="left|center_vertical" + android:layout_marginTop="2dip" + android:layout_marginBottom="1dip" + android:layout_marginRight="10dip" + android:padding="8dip" + android:textSize="20sp" + android:text="@string/system_panel_settings_button" + /> + + <View + android:layout_width="match_parent" + android:layout_height="1sp" + android:background="@android:drawable/divider_horizontal_dark" + /> + + <LinearLayout + android:padding="8dip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageButton android:id="@+id/brightness" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/system_panel_brightness_default" + android:background="@drawable/button_frame" + /> + <ImageButton android:id="@+id/sound" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="8dip" + android:src="@drawable/system_panel_sound_default" + android:background="@drawable/button_frame" + /> + <ImageButton android:id="@+id/orientation" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="8dip" + android:src="@drawable/system_panel_orientation_default" + android:background="@drawable/button_frame" + /> + <ImageButton android:id="@+id/airplane" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="8dip" + android:src="@drawable/system_panel_airplane_default" + android:background="@drawable/button_frame" + /> + </LinearLayout> + + <RelativeLayout + android:padding="8dip" android:layout_width="match_parent" - android:layout_height="match_parent" - android:animationCache="false" - android:background="@drawable/status_bar_background" - android:clickable="true" - android:focusable="true" - android:descendantFocusability="afterDescendants" + android:layout_height="wrap_content" > + <ImageView android:id="@+id/battery_meter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:src="@drawable/dots_empty" + /> + + <TextView android:id="@+id/battery_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/battery_meter" + /> + + <com.android.systemui.statusbar.Clock + style="@*android:style/TextAppearance.StatusBar.Icon" + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textSize="20sp" + android:textStyle="bold" + android:padding="2dip" + android:layout_centerHorizontal="true" + /> + + <TextView android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/clock" + /> - <TextView - android:id="@+id/systemPanelDummy" + <ImageView android:id="@+id/signal_meter" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerInParent="true" - android:textColor="#FFCCCCCC" - android:textSize="18sp" + android:layout_alignParentRight="true" + android:src="@drawable/dots_full" + /> + + <TextView android:id="@+id/signal_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/signal_meter" /> </RelativeLayout> -</FrameLayout> + +</com.android.systemui.statusbar.tablet.SystemPanel> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ba3a3d1d3538..2df3b6d51858 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -51,4 +51,7 @@ power usage activity to find out what drained the battery. --> <string name="battery_low_why">Battery use</string> + <!-- Name of the button that links to the Settings app. [MAXCHARS=NONE] --> + <string name="system_panel_settings_button">Settings</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java index 5add6def08c2..91b583b98618 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java @@ -1086,12 +1086,12 @@ public class PhoneStatusBarService extends StatusBarService { } private class MyTicker extends Ticker { - MyTicker(Context context, StatusBarView sb) { + MyTicker(Context context, View sb) { super(context, sb); } @Override - void tickerStarting() { + public void tickerStarting() { mTicking = true; mIcons.setVisibility(View.GONE); mTickerView.setVisibility(View.VISIBLE); @@ -1103,7 +1103,7 @@ public class PhoneStatusBarService extends StatusBarService { } @Override - void tickerDone() { + public void tickerDone() { mIcons.setVisibility(View.VISIBLE); mTickerView.setVisibility(View.GONE); mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); @@ -1114,7 +1114,7 @@ public class PhoneStatusBarService extends StatusBarService { } } - void tickerHalting() { + public void tickerHalting() { mIcons.setVisibility(View.VISIBLE); mTickerView.setVisibility(View.GONE); mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java index 07e865376680..0aaa3705cebf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java @@ -141,7 +141,7 @@ public abstract class Ticker { } }; - Ticker(Context context, StatusBarView sb) { + public Ticker(Context context, View sb) { mContext = context; mTickerView = sb.findViewById(R.id.ticker); @@ -163,7 +163,7 @@ public abstract class Ticker { } - void addEntry(StatusBarNotification n) { + public void addEntry(StatusBarNotification n) { int initialCount = mSegments.size(); // If what's being displayed has the same text and icon, just drop it @@ -212,7 +212,7 @@ public abstract class Ticker { } } - void removeEntry(StatusBarNotification n) { + public void removeEntry(StatusBarNotification n) { for (int i=mSegments.size()-1; i>=0; i--) { Segment seg = mSegments.get(i); if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { @@ -221,13 +221,13 @@ public abstract class Ticker { } } - void halt() { + public void halt() { mHandler.removeCallbacks(mAdvanceTicker); mSegments.clear(); tickerHalting(); } - void reflowText() { + public void reflowText() { if (mSegments.size() > 0) { Segment seg = mSegments.get(0); CharSequence text = seg.getText(); @@ -266,8 +266,8 @@ public abstract class Ticker { mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); } - abstract void tickerStarting(); - abstract void tickerDone(); - abstract void tickerHalting(); + public abstract void tickerStarting(); + public abstract void tickerDone(); + public abstract void tickerHalting(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java index 9749ae442741..814081198d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java @@ -34,5 +34,9 @@ public class TickerView extends TextSwitcher super.onSizeChanged(w, h, oldw, oldh); mTicker.reflowText(); } + + public void setTicker(Ticker t) { + mTicker = t; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java index 3c7b13062bf9..7c7d74c3ce10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java @@ -31,24 +31,16 @@ import com.android.systemui.R; public class NotificationIconArea extends LinearLayout { private static final String TAG = "NotificationIconArea"; - MoreView mMoreView; IconLayout mIconLayout; DraggerView mDraggerView; public NotificationIconArea(Context context, AttributeSet attrs) { super(context, attrs); - mMoreView = (MoreView) findViewById(R.id.more); mIconLayout = (IconLayout)findViewById(R.id.icons); mDraggerView = (DraggerView) findViewById(R.id.handle); } - static class MoreView extends ImageView { - public MoreView(Context context, AttributeSet attrs) { - super(context, attrs); - } - } - static class IconLayout extends LinearLayout { public IconLayout(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java new file mode 100644 index 000000000000..236b5215507b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java @@ -0,0 +1,279 @@ +/* + * 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.systemui.statusbar.tablet; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.media.AudioManager; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.IBinder; +import android.os.IPowerManager; +import android.os.Message; +import android.os.RemoteException; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.AttributeSet; +import android.util.Slog; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RemoteViews; +import android.widget.ScrollView; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.systemui.statusbar.*; +import com.android.systemui.R; + +public class SystemPanel extends LinearLayout { + private static final String TAG = "SystemPanel"; + + private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 5; + private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON; + private static final int DEFAULT_BACKLIGHT = (int) (android.os.Power.BRIGHTNESS_ON * 0.4f); + + + private TabletStatusBarService mBar; + private boolean mAirplaneMode; + + private ImageButton mBrightnessButton; + private ImageButton mSoundButton; + private ImageButton mOrientationButton; + private ImageButton mAirplaneButton; + + private final AudioManager mAudioManager; + + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + mSoundButton.setAlpha(getSilentMode() ? 0x7F : 0xFF); + } + } + }; + + public void setBar(TabletStatusBarService bar) { + mBar = bar; + } + + public SystemPanel(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SystemPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // get notified of phone state changes + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + + public void onAttachedToWindow() { + TextView settingsButton = (TextView)findViewById(R.id.settings_button); + settingsButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + getContext().startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + mBar.animateCollapse(); + }}); + + mBrightnessButton = (ImageButton)findViewById(R.id.brightness); + mBrightnessButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + rotateBrightness(); + } + }); + + mSoundButton = (ImageButton)findViewById(R.id.sound); + mSoundButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + setSilentMode(!getSilentMode()); + mSoundButton.setAlpha(getSilentMode() ? 0x7F : 0xFF); + } + }); + mOrientationButton = (ImageButton)findViewById(R.id.orientation); + mOrientationButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Toast.makeText(getContext(), "Orientation control not implemented; please adjust neck angle.", Toast.LENGTH_SHORT).show(); + } + }); + + mAirplaneButton = (ImageButton)findViewById(R.id.airplane); + mAirplaneButton.setAlpha(mAirplaneMode ? 0xFF : 0x7F); + mAirplaneButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + boolean newMode = !getAirplaneMode(); + Toast.makeText(getContext(), "Attempting to turn " + + (newMode ? "on" : "off") + " airplane mode (flaky).", + Toast.LENGTH_SHORT).show(); + setAirplaneMode(newMode); + } + }); + + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + getContext().registerReceiver(mReceiver, filter); + } + + public void onDetachedFromWindow() { + getContext().unregisterReceiver(mReceiver); + } + + // ---------------------------------------------------------------------- + +// private boolean isAutoBrightness() { +// Context context = getContext(); +// try { +// IPowerManager power = IPowerManager.Stub.asInterface( +// ServiceManager.getService("power")); +// if (power != null) { +// int brightnessMode = Settings.System.getInt(context.getContentResolver(), +// Settings.System.SCREEN_BRIGHTNESS_MODE); +// return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; +// } +// } catch (RemoteException e) { +// } catch (Settings.SettingNotFoundException e) { +// } +// return false; +// } + + private void rotateBrightness() { + int alpha = 0xFF; + Context context = getContext(); + try { + IPowerManager power = IPowerManager.Stub.asInterface( + ServiceManager.getService("power")); + if (power != null) { + ContentResolver cr = context.getContentResolver(); + int brightness = Settings.System.getInt(cr, + Settings.System.SCREEN_BRIGHTNESS); + int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + //Only get brightness setting if available + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available)) { + brightnessMode = Settings.System.getInt(cr, + Settings.System.SCREEN_BRIGHTNESS_MODE); + } + + // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM + // Technically, not a toggle... + if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { + brightness = MINIMUM_BACKLIGHT; + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + alpha = 0x40; + } else if (brightness < DEFAULT_BACKLIGHT) { + brightness = DEFAULT_BACKLIGHT; + alpha = 0xC0; + } else if (brightness < MAXIMUM_BACKLIGHT) { + brightness = MAXIMUM_BACKLIGHT; + alpha = 0xFF; + } else { + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + brightness = MINIMUM_BACKLIGHT; + alpha = 0x60; + } + + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available)) { + // Set screen brightness mode (automatic or manual) + Settings.System.putInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + brightnessMode); + } else { + // Make sure we set the brightness if automatic mode isn't available + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + } + if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) { + power.setBacklightBrightness(brightness); + Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); + } + } + } catch (RemoteException e) { + } catch (Settings.SettingNotFoundException e) { + } + + mBrightnessButton.setAlpha(alpha); + } + + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + Slog.d(TAG, "phone service state changed: " + serviceState.getState()); + mAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + if (mAirplaneButton != null) { + mAirplaneButton.setAlpha(mAirplaneMode ? 0xFF : 0x7F); + } + } + }; + + private boolean getAirplaneMode() { + return mAirplaneMode; + } + + private void setAirplaneMode(boolean on) { + Settings.System.putInt( + mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + getContext().sendBroadcast(intent); + } + + boolean getSilentMode() { + return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + } + + void setSilentMode(boolean on) { + if (on) { + mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, 1) == 1) + ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java index b1c4ee3d9c53..9c86f2d71164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java @@ -16,30 +16,34 @@ package com.android.systemui.statusbar.tablet; +import android.animation.Animator; +import android.app.ActivityManagerNative; import android.app.Notification; +import android.app.PendingIntent; import android.app.Service; +import android.app.StatusBarManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.RemoteException; import android.util.Slog; import android.view.Gravity; import android.view.LayoutInflater; -import android.widget.RemoteViews; -import android.app.ActivityManagerNative; -import android.app.PendingIntent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.graphics.Rect; -import android.os.RemoteException; import android.view.WindowManagerImpl; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RemoteViews; +import android.widget.ScrollView; +import android.widget.TextSwitcher; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; @@ -53,26 +57,37 @@ public class TabletStatusBarService extends StatusBarService { public static final boolean DEBUG = false; public static final String TAG = "TabletStatusBar"; - View mStatusBarView; - NotificationIconArea mNotificationIconArea; + int mIconSize; H mHandler = new H(); + // tracking all current notifications + private NotificationData mNotns = new NotificationData(); + + View mStatusBarView; + NotificationIconArea mNotificationIconArea; + View mNotificationPanel; - View mSystemPanel; + SystemPanel mSystemPanel; ViewGroup mPile; TextView mClearButton; NotificationIconArea.IconLayout mIconLayout; - private NotificationData mNotns = new NotificationData(); - + KickerController mKicker; + View mKickerView; + boolean mTicking; + boolean mExpandedVisible; + + // for disabling the status bar + int mDisabled = 0; + protected void addPanelWindows() { mNotificationPanel = View.inflate(this, R.layout.sysbar_panel_notifications, null); - mSystemPanel = View.inflate(this, R.layout.sysbar_panel_system, null); + mSystemPanel = (SystemPanel) View.inflate(this, R.layout.sysbar_panel_system, null); mNotificationPanel.setVisibility(View.GONE); mSystemPanel.setVisibility(View.GONE); @@ -86,8 +101,8 @@ public class TabletStatusBarService extends StatusBarService { ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); @@ -98,12 +113,12 @@ public class TabletStatusBarService extends StatusBarService { WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); lp = new WindowManager.LayoutParams( - 400, // ViewGroup.LayoutParams.WRAP_CONTENT, - 200, // ViewGroup.LayoutParams.WRAP_CONTENT, + 500, // ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); @@ -112,23 +127,12 @@ public class TabletStatusBarService extends StatusBarService { lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard; WindowManagerImpl.getDefault().addView(mSystemPanel, lp); - - // Lorem ipsum, Dolores - TextView tv = ((TextView) mSystemPanel.findViewById(R.id.systemPanelDummy)); - if (tv != null) tv.setText("System status: great"); - - mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); - mPile.removeAllViews(); - - mClearButton = (TextView)mNotificationPanel.findViewById(R.id.clear_all_button); - mClearButton.setOnClickListener(mClearButtonListener); + mSystemPanel.setBar(this); } @Override public void onCreate() { super.onCreate(); // will add the main bar view - - addPanelWindows(); } protected View makeStatusBarView() { @@ -145,6 +149,20 @@ public class TabletStatusBarService extends StatusBarService { // where the icons go mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); + mKicker = new KickerController((Context)this, mStatusBarView); + + // Add the windows + addPanelWindows(); + + mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); + mPile.removeAllViews(); + + ScrollView scroller = (ScrollView)mPile.getParent(); + scroller.setFillViewport(true); + + mClearButton = (TextView)mNotificationPanel.findViewById(R.id.clear_all_button); + mClearButton.setOnClickListener(mClearButtonListener); + return sb; } @@ -162,10 +180,12 @@ public class TabletStatusBarService extends StatusBarService { case MSG_OPEN_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "opening notifications panel"); mNotificationPanel.setVisibility(View.VISIBLE); + mExpandedVisible = true; break; case MSG_CLOSE_NOTIFICATION_PANEL: if (DEBUG) Slog.d(TAG, "closing notifications panel"); mNotificationPanel.setVisibility(View.GONE); + mExpandedVisible = false; break; case MSG_OPEN_SYSTEM_PANEL: if (DEBUG) Slog.d(TAG, "opening system panel"); @@ -195,8 +215,7 @@ public class TabletStatusBarService extends StatusBarService { public void addNotification(IBinder key, StatusBarNotification notification) { if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); addNotificationViews(key, notification); - - // TODO: kicker; immersive mode + // tick() } public void updateNotification(IBinder key, StatusBarNotification notification) { @@ -274,7 +293,127 @@ public class TabletStatusBarService extends StatusBarService { } public void disable(int state) { - // TODO + /* + final int old = mDisabled; + final int diff = state ^ old; + mDisabled = state; + + if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { + Slog.d(TAG, "DISABLE_EXPAND: yes"); + animateCollapse(); + } + } + if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); + if (mTicking) { + mKicker.halt(); + } else { + mNotificationIconArea.setVisibility(View.INVISIBLE); + } + } else { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); + if (!mExpandedVisible) { + mNotificationIconArea.setVisibility(View.VISIBLE); + } + } + } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes"); + mKicker.halt(); + } + } + */ + } + + void performDisableActions(int net) { + /* + int old = mDisabled; + int diff = net ^ old; + mDisabled = net; + + // act accordingly + if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { + Slog.d(TAG, "DISABLE_EXPAND: yes"); + animateCollapse(); + } + } + if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); + if (mTicking) { + mNotificationIconArea.setVisibility(View.INVISIBLE); + mKicker.halt(); + } else { + mNotificationIconArea.setVisibility(View.INVISIBLE); + } + } else { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); + if (!mExpandedVisible) { + mNotificationIconArea.setVisibility(View.VISIBLE); + } + } + } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + mKicker.halt(); + } + } + */ + } + + private void tick(StatusBarNotification n) { + // Show the ticker if one is requested. Also don't do this + // until status bar window is attached to the window manager, + // because... well, what's the point otherwise? And trying to + // run a ticker without being attached will crash! + if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) { + if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS + | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { + mKicker.addEntry(n); + } + } + } + + private class KickerController { + View mView; + ImageView mKickerIcon; + TextSwitcher mKickerText; + + public KickerController(Context context, View sb) { + mView = sb.findViewById(R.id.ticker); + mKickerIcon = (ImageView) mView.findViewById(R.id.tickerIcon); + mKickerText = (TextSwitcher) mView.findViewById(R.id.tickerText); + } + + public void halt() { + tickerHalting(); + } + + public void addEntry(StatusBarNotification n) { + mKickerIcon.setImageResource(n.notification.icon); + mKickerText.setCurrentText(n.notification.tickerText); + tickerStarting(); + } + + public void tickerStarting() { + mTicking = true; + mIconLayout.setVisibility(View.GONE); + mKickerView.setVisibility(View.VISIBLE); + } + + public void tickerDone() { + mIconLayout.setVisibility(View.VISIBLE); + mKickerView.setVisibility(View.GONE); + mTicking = false; + } + + public void tickerHalting() { + mIconLayout.setVisibility(View.VISIBLE); + mKickerView.setVisibility(View.GONE); + mTicking = false; + } } public void animateExpand() { @@ -405,8 +544,9 @@ public class TabletStatusBarService extends StatusBarService { } StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { - NotificationData list = mNotns; - ViewGroup parent = mPile; + if (DEBUG) { + Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); + } // Construct the icon. final StatusBarIconView iconView = new StatusBarIconView(this, notification.pkg + "/0x" + Integer.toHexString(notification.id)); @@ -422,19 +562,13 @@ public class TabletStatusBarService extends StatusBarService { } // Construct the expanded view. NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); - if (!inflateViews(entry, parent)) { + if (!inflateViews(entry, mPile)) { handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " + notification); return null; } - // Add the expanded view. - final int viewIndex = list.add(entry); - if (parent != null) parent.addView(entry.row, viewIndex); // Add the icon. -// final int iconIndex = 0; // XXX: sort into ongoing and regular buckets -// mIconLayout.addView(iconView, iconIndex, -// new LinearLayout.LayoutParams(mIconSize, mIconSize)); - + mNotns.add(entry); refreshIcons(); return iconView; @@ -443,13 +577,25 @@ public class TabletStatusBarService extends StatusBarService { private void refreshIcons() { // XXX: need to implement a new limited linear layout class // to avoid removing & readding everything - mIconLayout.removeAllViews(); + int N = mNotns.size(); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize); + + if (DEBUG) { + Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout=" + + mIconLayout + ", mPile=" + mPile); + } + + mIconLayout.removeAllViews(); for (int i=0; i<4; i++) { if (i>=N) break; mIconLayout.addView(mNotns.get(N-i-1).icon, i, params); } + + mPile.removeAllViews(); + for (int i=0; i<N; i++) { + mPile.addView(mNotns.get(N-i-1).row); + } } private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 73fa93c345a3..546c0f447c6c 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -406,7 +406,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public final void openPanel(int featureId, KeyEvent event) { - openPanel(getPanelState(featureId, true), event); + if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && + mActionBar.isOverflowReserved()) { + mActionBar.showOverflowMenu(); + } else { + openPanel(getPanelState(featureId, true), event); + } } private void openPanel(PanelFeatureState st, KeyEvent event) { @@ -497,7 +502,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public final void closePanel(int featureId) { - if (featureId == FEATURE_CONTEXT_MENU) { + if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && + mActionBar.isOverflowReserved()) { + mActionBar.hideOverflowMenu(); + } else if (featureId == FEATURE_CONTEXT_MENU) { closeContextMenu(); } else { closePanel(getPanelState(featureId, true), true); @@ -591,7 +599,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // The panel key was pushed, so set the chording key mPanelChordingKey = keyCode; mPanelMayLongPress = false; - + PanelFeatureState st = getPanelState(featureId, true); if (!st.isOpen) { if (getContext().getResources().getConfiguration().keyboard @@ -600,7 +608,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } return preparePanel(st, event); } - } else if (mPanelMayLongPress && mPanelChordingKey == keyCode && (event.getFlags()&KeyEvent.FLAG_LONG_PRESS) != 0) { // We have had a long press while in a state where this @@ -635,25 +642,40 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } boolean playSoundEffect = false; - PanelFeatureState st = getPanelState(featureId, true); - if (st.isOpen || st.isHandled) { + final PanelFeatureState st = getPanelState(featureId, true); + if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null) { + if (mActionBar.isOverflowReserved()) { + if (!mActionBar.isOverflowMenuShowing()) { + final Callback cb = getCallback(); + if (cb != null) { + if (cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) { + playSoundEffect = mActionBar.showOverflowMenu(); + } + } + } else { + playSoundEffect = mActionBar.hideOverflowMenu(); + } + } + } else { + if (st.isOpen || st.isHandled) { - // Play the sound effect if the user closed an open menu (and not if - // they just released a menu shortcut) - playSoundEffect = st.isOpen; + // Play the sound effect if the user closed an open menu (and not if + // they just released a menu shortcut) + playSoundEffect = st.isOpen; - // Close menu - closePanel(st, true); + // Close menu + closePanel(st, true); - } else if (st.isPrepared) { + } else if (st.isPrepared) { - // Write 'menu opened' to event log - EventLog.writeEvent(50001, 0); + // Write 'menu opened' to event log + EventLog.writeEvent(50001, 0); - // Show menu - openPanel(st, event); + // Show menu + openPanel(st, event); - playSoundEffect = true; + playSoundEffect = true; + } } if (playSoundEffect) { @@ -833,6 +855,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } private void reopenMenu(boolean toggleMenuMode) { + if (mActionBar != null) { + if (!mActionBar.isOverflowMenuShowing() || !toggleMenuMode) { + final Callback cb = getCallback(); + if (cb != null) { + final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + if (cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { + mActionBar.showOverflowMenu(); + } + } + } else { + mActionBar.hideOverflowMenu(); + } + return; + } + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); // Save the future expanded mode state since closePanel will reset it @@ -1379,12 +1416,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } case KeyEvent.KEYCODE_MENU: { - if (mActionBar != null && mActionBar.isOverflowReserved()) { - mActionBar.showOverflowMenu(); - } else { - onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, - event); - } + onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, + event); return true; } @@ -2239,6 +2272,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { requestFeature(FEATURE_ACTION_BAR); } + if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) { + requestFeature(FEATURE_ACTION_BAR_OVERLAY); + } + if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) { requestFeature(FEATURE_ACTION_MODE_OVERLAY); } @@ -2325,7 +2362,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mIsFloating) { layoutResource = com.android.internal.R.layout.dialog_title; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { - layoutResource = com.android.internal.R.layout.screen_action_bar; + if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) { + layoutResource = com.android.internal.R.layout.screen_action_bar_overlay; + } else { + layoutResource = com.android.internal.R.layout.screen_action_bar; + } } else { layoutResource = com.android.internal.R.layout.screen_title; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index cb7fe06dcb2f..a07c38583107 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -37,6 +37,7 @@ import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.LocalPowerManager; +import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -48,15 +49,20 @@ import android.provider.Settings; import com.android.internal.policy.PolicyManager; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.ITelephony; +import com.android.internal.view.BaseInputHandler; import com.android.internal.widget.PointerLocationView; import android.util.Config; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import android.view.Display; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.IWindowManager; +import android.view.InputChannel; +import android.view.InputQueue; +import android.view.InputHandler; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowOrientationListener; @@ -221,6 +227,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mPointerLocationMode = 0; PointerLocationView mPointerLocationView = null; + InputChannel mPointerLocationInputChannel; + + private final InputHandler mPointerLocationInputHandler = new BaseInputHandler() { + @Override + public void handleMotion(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + synchronized (mLock) { + mPointerLocationView.addTouchEvent(event); + } + } + }; // The current size of the screen. int mW, mH; @@ -617,8 +634,26 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowManagerImpl wm = (WindowManagerImpl) mContext.getSystemService(Context.WINDOW_SERVICE); wm.addView(addView, lp); + + if (mPointerLocationInputChannel == null) { + try { + mPointerLocationInputChannel = + mWindowManager.monitorInput("PointerLocationView"); + InputQueue.registerInputChannel(mPointerLocationInputChannel, + mPointerLocationInputHandler, mHandler.getLooper().getQueue()); + } catch (RemoteException ex) { + Slog.e(TAG, "Could not set up input monitoring channel for PointerLocation.", + ex); + } + } } if (removeView != null) { + if (mPointerLocationInputChannel != null) { + InputQueue.unregisterInputChannel(mPointerLocationInputChannel); + mPointerLocationInputChannel.dispose(); + mPointerLocationInputChannel = null; + } + WindowManagerImpl wm = (WindowManagerImpl) mContext.getSystemService(Context.WINDOW_SERVICE); wm.removeView(removeView); @@ -732,20 +767,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { : Configuration.KEYBOARDHIDDEN_YES; } - public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY) { - if (mPointerLocationView == null) { - return; - } - synchronized (mLock) { - if (mPointerLocationView == null) { - return; - } - ev.offsetLocation(targetX, targetY); - mPointerLocationView.addTouchEvent(ev); - ev.offsetLocation(-targetX, -targetY); - } - } - /** {@inheritDoc} */ public int windowTypeToLayerLw(int type) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 252b42ab097f..1c7faa4ac9b8 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1696,7 +1696,10 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // Delegate master volume control to effect in output mix effect chain if needed sp<EffectChain> chain = getEffectChain_l(AudioSystem::SESSION_OUTPUT_MIX); if (chain != 0) { - uint32_t v = (uint32_t)(masterVolume * (1 << 24)); + uint32_t v = 0; + if (!masterMute) { + v = (uint32_t)(masterVolume * (1 << 24)); + } chain->setVolume_l(&v, &v); masterVolume = (float)((v + (1 << 23)) >> 24); chain.clear(); @@ -1750,7 +1753,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // compute volume for this track int16_t left, right, aux; - if (track->isMuted() || masterMute || track->isPausing() || + if (track->isMuted() || track->isPausing() || mStreamTypes[track->type()].mute) { left = right = aux = 0; if (track->isPausing()) { @@ -5351,7 +5354,7 @@ void AudioFlinger::EffectModule::process() return; } - if (mState == ACTIVE || mState == STOPPING || mState == STOPPED) { + if (mState == ACTIVE || mState == STOPPING || mState == STOPPED || mState == RESTART) { // do 32 bit to 16 bit conversion for auxiliary effect input buffer if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { AudioMixer::ditherAndClamp(mConfig.inputCfg.buffer.s32, @@ -6032,8 +6035,8 @@ void AudioFlinger::EffectHandle::dump(char* buffer, size_t size) AudioFlinger::EffectChain::EffectChain(const wp<ThreadBase>& wThread, int sessionId) : mThread(wThread), mSessionId(sessionId), mActiveTrackCnt(0), mOwnInBuffer(false), - mVolumeCtrlIdx(-1), mLeftVolume(0), mRightVolume(0), - mNewLeftVolume(0), mNewRightVolume(0) + mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX), + mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX) { mStrategy = AudioSystem::getStrategyForStream(AudioSystem::MUSIC); } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index cc1566b7096f..ae4e1682044d 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -314,14 +314,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { case ConnectivityManager.TYPE_WIFI: if (DBG) Slog.v(TAG, "Starting Wifi Service."); WifiStateTracker wst = new WifiStateTracker(context, mHandler); - WifiService wifiService = new WifiService(context, wst); + WifiService wifiService = new WifiService(context); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); - wifiService.startWifi(); + wifiService.checkAndStartWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; wst.startMonitoring(); //TODO: as part of WWS refactor, create only when needed - mWifiWatchdogService = new WifiWatchdogService(context, wst); + mWifiWatchdogService = new WifiWatchdogService(context); break; case ConnectivityManager.TYPE_MOBILE: @@ -1205,16 +1205,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { sendConnectedBroadcast(info); } - private void handleScanResultsAvailable(NetworkInfo info) { - int networkType = info.getType(); - if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " + - info.getTypeName() + " network. Don't know how to handle."); - } - - mNetTrackers[networkType].interpretScanResultsAvailable(); - } - private void handleNotificationChange(boolean visible, int id, Notification notification) { NotificationManager notificationManager = (NotificationManager) mContext @@ -1619,11 +1609,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } break; - case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE: - info = (NetworkInfo) msg.obj; - handleScanResultsAvailable(info); - break; - case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj); diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index 91951233fca1..c2c799b4e0a0 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -75,7 +75,8 @@ public class InputManager { int sw); private static native boolean nativeHasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); - private static native void nativeRegisterInputChannel(InputChannel inputChannel); + private static native void nativeRegisterInputChannel(InputChannel inputChannel, + boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); private static native int nativeInjectInputEvent(InputEvent event, int injectorPid, int injectorUid, int syncMode, int timeoutMillis); @@ -225,14 +226,38 @@ public class InputManager { return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists); } + /** + * Creates an input channel that will receive all input from the input dispatcher. + * @param inputChannelName The input channel name. + * @return The input channel. + */ + public InputChannel monitorInput(String inputChannelName) { + if (inputChannelName == null) { + throw new IllegalArgumentException("inputChannelName must not be null."); + } + + InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); + nativeRegisterInputChannel(inputChannels[0], true); + inputChannels[0].dispose(); // don't need to retain the Java object reference + return inputChannels[1]; + } + + /** + * Registers an input channel so that it can be used as an input event target. + * @param inputChannel The input channel to register. + */ public void registerInputChannel(InputChannel inputChannel) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } - nativeRegisterInputChannel(inputChannel); + nativeRegisterInputChannel(inputChannel, false); } + /** + * Unregisters an input channel. + * @param inputChannel The input channel to unregister. + */ public void unregisterInputChannel(InputChannel inputChannel) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 36b3a5ecee88..164142e909d6 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -77,9 +77,12 @@ import android.view.inputmethod.EditorInfo; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.text.Collator; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; /** * This class provides a system service that manages input methods. @@ -463,6 +466,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt); + mStatusBar = statusBar; + statusBar.setIconVisibility("ime", false); + buildInputMethodListLocked(mMethodList, mMethodMap); final String enabledStr = Settings.Secure.getString( @@ -506,9 +512,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - mStatusBar = statusBar; - statusBar.setIconVisibility("ime", false); - mSettingsObserver = new SettingsObserver(mHandler); updateFromSettingsLocked(); } @@ -1217,6 +1220,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client); + return; } mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER); @@ -1508,21 +1512,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub hideInputMethodMenuLocked(); int N = immis.size(); - - mItems = new CharSequence[N]; - mIms = new InputMethodInfo[N]; - - int j = 0; + + final Map<CharSequence, InputMethodInfo> imMap = + new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); + for (int i = 0; i < N; ++i) { InputMethodInfo property = immis.get(i); if (property == null) { continue; } - mItems[j] = property.loadLabel(pm); - mIms[j] = property; - j++; + imMap.put(property.loadLabel(pm), property); } - + + N = imMap.size(); + mItems = imMap.keySet().toArray(new CharSequence[N]); + mIms = imMap.values().toArray(new InputMethodInfo[N]); + int checkedItem = 0; for (int i = 0; i < N; ++i) { if (mIms[i].getId().equals(lastInputMethodId)) { diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index ef5e9cc1f614..d604886ed1d3 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -16,34 +16,42 @@ package com.android.server; +import com.android.internal.app.IMediaContainerService; import com.android.server.am.ActivityManagerService; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.res.ObbInfo; -import android.content.res.ObbScanner; import android.net.Uri; -import android.os.storage.IMountService; -import android.os.storage.IMountServiceListener; -import android.os.storage.IMountShutdownObserver; -import android.os.storage.StorageResultCode; import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.IBinder; -import android.os.Environment; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.storage.IMountService; +import android.os.storage.IMountServiceListener; +import android.os.storage.IMountShutdownObserver; +import android.os.storage.IObbActionListener; +import android.os.storage.StorageResultCode; import android.util.Slog; + import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; /** * MountService implements back-end services for platform storage @@ -137,9 +145,77 @@ class MountService extends IMountService.Stub final private HashSet<String> mAsecMountSet = new HashSet<String>(); /** - * Private hash of currently mounted filesystem images. + * Mounted OBB tracking information. Used to track the current state of all + * OBBs. + */ + final private Map<IObbActionListener, ObbState> mObbMounts = new HashMap<IObbActionListener, ObbState>(); + final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); + + class ObbState implements IBinder.DeathRecipient { + public ObbState(String filename, IObbActionListener token, int callerUid) { + this.filename = filename; + this.token = token; + this.callerUid = callerUid; + mounted = false; + } + + // OBB source filename + String filename; + + // Token of remote Binder caller + IObbActionListener token; + + // Binder.callingUid() + public int callerUid; + + // Whether this is mounted currently. + boolean mounted; + + @Override + public void binderDied() { + ObbAction action = new UnmountObbAction(this, true); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + + removeObbState(this); + + token.asBinder().unlinkToDeath(this, 0); + } + } + + // OBB Action Handler + final private ObbActionHandler mObbActionHandler; + + // OBB action handler messages + private static final int OBB_RUN_ACTION = 1; + private static final int OBB_MCS_BOUND = 2; + private static final int OBB_MCS_UNBIND = 3; + private static final int OBB_MCS_RECONNECT = 4; + private static final int OBB_MCS_GIVE_UP = 5; + + /* + * Default Container Service information */ - final private HashSet<String> mObbMountSet = new HashSet<String>(); + static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( + "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); + + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); + + class DefaultContainerConnection implements ServiceConnection { + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG_OBB) + Slog.i(TAG, "onServiceConnected"); + IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); + } + + public void onServiceDisconnected(ComponentName name) { + if (DEBUG_OBB) + Slog.i(TAG, "onServiceDisconnected"); + } + }; + + // Used in the ObbActionHandler + private IMediaContainerService mContainerService = null; // Handler messages private static final int H_UNMOUNT_PM_UPDATE = 1; @@ -363,7 +439,7 @@ class MountService extends IMountService.Stub public void binderDied() { if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); - synchronized(mListeners) { + synchronized (mListeners) { mListeners.remove(this); mListener.asBinder().unlinkToDeath(this, 0); } @@ -917,6 +993,9 @@ class MountService extends IMountService.Stub mHandlerThread.start(); mHandler = new MountServiceHandler(mHandlerThread.getLooper()); + // Add OBB Action Handler to MountService thread. + mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); + /* * Vold does not run in the simulator, so pretend the connector thread * ran and did its thing. @@ -1013,7 +1092,7 @@ class MountService extends IMountService.Stub private void setUmsEnabling(boolean enable) { synchronized (mListeners) { - mUmsEnabling = true; + mUmsEnabling = enable; } } @@ -1351,12 +1430,16 @@ class MountService extends IMountService.Stub mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); } - private boolean isCallerOwnerOfPackage(String packageName) { + private boolean isCallerOwnerOfPackageOrSystem(String packageName) { final int callerUid = Binder.getCallingUid(); - return isUidOwnerOfPackage(packageName, callerUid); + return isUidOwnerOfPackageOrSystem(packageName, callerUid); } - private boolean isUidOwnerOfPackage(String packageName, int callerUid) { + private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { + if (callerUid == android.os.Process.SYSTEM_UID) { + return true; + } + if (packageName == null) { return false; } @@ -1375,12 +1458,6 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); - // XXX replace with call to IMediaContainerService - ObbInfo obbInfo = ObbScanner.getObbInfo(filename); - if (!isCallerOwnerOfPackage(obbInfo.packageName)) { - throw new IllegalArgumentException("Caller package does not match OBB file"); - } - try { ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); String []tok = rsp.get(0).split(" "); @@ -1392,7 +1469,7 @@ class MountService extends IMountService.Stub } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { - throw new IllegalArgumentException(String.format("OBB '%s' not found", filename)); + return null; } else { throw new IllegalStateException(String.format("Unexpected response code %d", code)); } @@ -1400,95 +1477,390 @@ class MountService extends IMountService.Stub } public boolean isObbMounted(String filename) { + synchronized (mObbMounts) { + return mObbPathToStateMap.containsKey(filename); + } + } + + public void mountObb(String filename, String key, IObbActionListener token) { waitForReady(); warnOnNotMounted(); - // XXX replace with call to IMediaContainerService - ObbInfo obbInfo = ObbScanner.getObbInfo(filename); - if (!isCallerOwnerOfPackage(obbInfo.packageName)) { - throw new IllegalArgumentException("Caller package does not match OBB file"); + final ObbState obbState; + + synchronized (mObbMounts) { + if (isObbMounted(filename)) { + throw new IllegalArgumentException("OBB file is already mounted"); + } + + if (mObbMounts.containsKey(token)) { + throw new IllegalArgumentException("You may only have one OBB mounted at a time"); + } + + final int callerUid = Binder.getCallingUid(); + obbState = new ObbState(filename, token, callerUid); + addObbState(obbState); } - synchronized (mObbMountSet) { - return mObbMountSet.contains(filename); + try { + token.asBinder().linkToDeath(obbState, 0); + } catch (RemoteException rex) { + Slog.e(TAG, "Failed to link to listener death"); } + + MountObbAction action = new MountObbAction(obbState, key); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + + if (DEBUG_OBB) + Slog.i(TAG, "Send to OBB handler: " + action.toString()); } - public int mountObb(String filename, String key) { - waitForReady(); - warnOnNotMounted(); + public void unmountObb(String filename, boolean force, IObbActionListener token) { + final ObbState obbState; - synchronized (mObbMountSet) { - if (mObbMountSet.contains(filename)) { - return StorageResultCode.OperationFailedStorageMounted; + synchronized (mObbMounts) { + if (!isObbMounted(filename)) { + throw new IllegalArgumentException("OBB is not mounted"); } + obbState = mObbPathToStateMap.get(filename); } - final int callerUid = Binder.getCallingUid(); + UnmountObbAction action = new UnmountObbAction(obbState, force); + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); + + if (DEBUG_OBB) + Slog.i(TAG, "Send to OBB handler: " + action.toString()); + } - // XXX replace with call to IMediaContainerService - ObbInfo obbInfo = ObbScanner.getObbInfo(filename); - if (!isUidOwnerOfPackage(obbInfo.packageName, callerUid)) { - throw new IllegalArgumentException("Caller package does not match OBB file"); + private void addObbState(ObbState obbState) { + synchronized (mObbMounts) { + mObbMounts.put(obbState.token, obbState); + mObbPathToStateMap.put(obbState.filename, obbState); } + } - if (key == null) { - key = "none"; + private void removeObbState(ObbState obbState) { + synchronized (mObbMounts) { + mObbMounts.remove(obbState.token); + mObbPathToStateMap.remove(obbState.filename); } + } - int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb mount %s %s %d", filename, key, callerUid); - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - int code = e.getCode(); - if (code != VoldResponseCode.OpFailedStorageBusy) { - rc = StorageResultCode.OperationFailedInternalError; + private class ObbActionHandler extends Handler { + private boolean mBound = false; + private List<ObbAction> mActions = new LinkedList<ObbAction>(); + + ObbActionHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OBB_RUN_ACTION: { + ObbAction action = (ObbAction) msg.obj; + + if (DEBUG_OBB) + Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); + + // If a bind was already initiated we don't really + // need to do anything. The pending install + // will be processed later on. + if (!mBound) { + // If this is the only one pending we might + // have to bind to the service again. + if (!connectToService()) { + 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; + } + case OBB_MCS_BOUND: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_BOUND"); + if (msg.obj != null) { + mContainerService = (IMediaContainerService) msg.obj; + } + if (mContainerService == null) { + // Something seriously wrong. Bail out + Slog.e(TAG, "Cannot bind to media container service"); + for (ObbAction action : mActions) { + // Indicate service bind error + action.handleError(); + } + mActions.clear(); + } else if (mActions.size() > 0) { + ObbAction action = mActions.get(0); + if (action != null) { + action.execute(this); + } + } else { + // Should never happen ideally. + Slog.w(TAG, "Empty queue"); + } + break; + } + case OBB_MCS_RECONNECT: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_RECONNECT"); + if (mActions.size() > 0) { + if (mBound) { + disconnectService(); + } + if (!connectToService()) { + Slog.e(TAG, "Failed to bind to media container service"); + for (ObbAction action : mActions) { + // Indicate service bind error + action.handleError(); + } + mActions.clear(); + } + } + break; + } + case OBB_MCS_UNBIND: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_UNBIND"); + + // Delete pending install + if (mActions.size() > 0) { + mActions.remove(0); + } + if (mActions.size() == 0) { + if (mBound) { + disconnectService(); + } + } else { + // There are more pending requests in queue. + // Just post MCS_BOUND message to trigger processing + // of next pending install. + mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); + } + break; + } + case OBB_MCS_GIVE_UP: { + if (DEBUG_OBB) + Slog.i(TAG, "OBB_MCS_GIVE_UP"); + mActions.remove(0); + break; + } } } - if (rc == StorageResultCode.OperationSucceeded) { - synchronized (mObbMountSet) { - mObbMountSet.add(filename); + private boolean connectToService() { + if (DEBUG_OBB) + Slog.i(TAG, "Trying to bind to DefaultContainerService"); + + Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); + if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { + mBound = true; + return true; } + return false; + } + + private void disconnectService() { + mContainerService = null; + mBound = false; + mContext.unbindService(mDefContainerConn); } - return rc; } - public int unmountObb(String filename, boolean force) { - waitForReady(); - warnOnNotMounted(); + abstract class ObbAction { + private static final int MAX_RETRIES = 3; + private int mRetries; + + ObbState mObbState; - ObbInfo obbInfo = ObbScanner.getObbInfo(filename); - if (!isCallerOwnerOfPackage(obbInfo.packageName)) { - throw new IllegalArgumentException("Caller package does not match OBB file"); + ObbAction(ObbState obbState) { + mObbState = obbState; } - synchronized (mObbMountSet) { - if (!mObbMountSet.contains(filename)) { - return StorageResultCode.OperationFailedStorageNotMounted; + public void execute(ObbActionHandler handler) { + try { + if (DEBUG_OBB) + Slog.i(TAG, "Starting to execute action: " + this.toString()); + mRetries++; + if (mRetries > MAX_RETRIES) { + Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP); + handleError(); + return; + } else { + handleExecute(); + if (DEBUG_OBB) + Slog.i(TAG, "Posting install MCS_UNBIND"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); + } + } catch (RemoteException e) { + if (DEBUG_OBB) + Slog.i(TAG, "Posting install MCS_RECONNECT"); + mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); + } catch (Exception e) { + if (DEBUG_OBB) + Slog.d(TAG, "Error handling OBB action", e); + handleError(); } - } + } - int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb unmount %s%s", filename, (force ? " force" : "")); - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - int code = e.getCode(); - if (code == VoldResponseCode.OpFailedStorageBusy) { - rc = StorageResultCode.OperationFailedStorageBusy; + abstract void handleExecute() throws RemoteException; + abstract void handleError(); + } + + class MountObbAction extends ObbAction { + private String mKey; + + MountObbAction(ObbState obbState, String key) { + super(obbState); + mKey = key; + } + + public void handleExecute() throws RemoteException { + ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename); + 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; + } + } + + if (rc == StorageResultCode.OperationSucceeded) { + try { + mObbState.token.onObbResult(mObbState.filename, "mounted"); + } catch (RemoteException e) { + Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); + } } else { - rc = StorageResultCode.OperationFailedInternalError; + Slog.e(TAG, "Couldn't mount OBB file"); + + // We didn't succeed, so remove this from the mount-set. + removeObbState(mObbState); } } - if (rc == StorageResultCode.OperationSucceeded) { - synchronized (mObbMountSet) { - mObbMountSet.remove(filename); + public void handleError() { + removeObbState(mObbState); + + try { + mObbState.token.onObbResult(mObbState.filename, "error"); + } catch (RemoteException e) { + Slog.e(TAG, "Couldn't send back OBB mount error for " + mObbState.filename); } } - return rc; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("MountObbAction{"); + sb.append("filename="); + sb.append(mObbState.filename); + sb.append(",callerUid="); + sb.append(mObbState.callerUid); + sb.append(",token="); + sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL"); + sb.append('}'); + return sb.toString(); + } + } + + class UnmountObbAction extends ObbAction { + private boolean mForceUnmount; + + UnmountObbAction(ObbState obbState, boolean force) { + super(obbState); + mForceUnmount = force; + } + + public void handleExecute() throws RemoteException { + ObbInfo obbInfo = mContainerService.getObbInfo(mObbState.filename); + + if (!isCallerOwnerOfPackageOrSystem(obbInfo.packageName)) { + throw new IllegalArgumentException("Caller package does not match OBB file"); + } + + 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 (rc == StorageResultCode.OperationSucceeded) { + removeObbState(mObbState); + + try { + mObbState.token.onObbResult(mObbState.filename, "unmounted"); + } catch (RemoteException e) { + Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); + } + } else { + try { + mObbState.token.onObbResult(mObbState.filename, "error"); + } catch (RemoteException e) { + Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); + } + } + } + + public void handleError() { + removeObbState(mObbState); + + try { + mObbState.token.onObbResult(mObbState.filename, "error"); + } catch (RemoteException e) { + Slog.e(TAG, "Couldn't send back OBB unmount error for " + mObbState.filename); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("UnmountObbAction{"); + sb.append("filename="); + sb.append(mObbState.filename != null ? mObbState.filename : "null"); + sb.append(",force="); + sb.append(mForceUnmount); + sb.append(",callerUid="); + sb.append(mObbState.callerUid); + sb.append(",token="); + sb.append(mObbState.token != null ? mObbState.token.toString() : "null"); + sb.append('}'); + return sb.toString(); + } } } diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java index b369f716c7c5..7b5d18ac8b2c 100644 --- a/services/java/com/android/server/ViewServer.java +++ b/services/java/com/android/server/ViewServer.java @@ -60,6 +60,8 @@ class ViewServer implements Runnable { private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; // Keeps a connection open and notifies when the list of windows changes private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; + // Returns the focused window + private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer; private Thread mThread; @@ -250,6 +252,8 @@ class ViewServer implements Runnable { result = writeValue(mClient, VALUE_SERVER_VERSION); } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { result = mWindowManager.viewServerListWindows(mClient); + } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { + result = mWindowManager.viewServerGetFocusedWindow(mClient); } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { result = windowManagerAutolistLoop(); } else { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 15080b2f25b3..b43b33e4f9ce 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -17,6 +17,8 @@ package com.android.server; import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; @@ -26,25 +28,30 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.wifi.IWifiManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiStateTracker; +import android.net.wifi.WifiStateMachine; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; -import android.net.NetworkStateTracker; import android.net.DhcpInfo; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; import android.os.Binder; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.Slog; import java.util.ArrayList; @@ -72,7 +79,7 @@ public class WifiService extends IWifiManager.Stub { private static final String TAG = "WifiService"; private static final boolean DBG = true; - private final WifiStateTracker mWifiStateTracker; + private final WifiStateMachine mWifiStateMachine; private Context mContext; @@ -123,10 +130,63 @@ public class WifiService extends IWifiManager.Stub { private boolean mIsReceiverRegistered = false; - WifiService(Context context, WifiStateTracker tracker) { + + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + + // Variables relating to the 'available networks' notification + /** + * The icon to show in the 'available networks' notification. This will also + * be the ID of the Notification given to the NotificationManager. + */ + private static final int ICON_NETWORKS_AVAILABLE = + com.android.internal.R.drawable.stat_notify_wifi_in_range; + /** + * When a notification is shown, we wait this amount before possibly showing it again. + */ + private final long NOTIFICATION_REPEAT_DELAY_MS; + /** + * Whether the user has set the setting to show the 'available networks' notification. + */ + private boolean mNotificationEnabled; + /** + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. + */ + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. + */ + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. + */ + private Notification mNotification; + /** + * Whether the notification is being shown, as set by us. That is, if the + * user cancels the notification, we will not receive the callback so this + * will still be true. We only guarantee if this is false, then the + * notification is not showing. + */ + private boolean mNotificationShown; + /** + * The number of continuous scans that must occur before consider the + * supplicant in a scanning state. This allows supplicant to associate with + * remembered networks that are in the scan results. + */ + private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3; + /** + * The number of scans since the last network state change. When this + * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the + * supplicant to actually be scanning. When the network state changes to + * something other than scanning, we reset this to 0. + */ + private int mNumScansSinceNetworkStateChange; + + + WifiService(Context context) { mContext = context; - mWifiStateTracker = tracker; - mWifiStateTracker.enableRssiPolling(true); + mWifiStateMachine = new WifiStateMachine(mContext); + mWifiStateMachine.enableRssiPolling(true); mBatteryStats = BatteryStatsService.getService(); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); @@ -164,6 +224,42 @@ public class WifiService extends IWifiManager.Stub { } },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + // reset & clear notification on any wifi state change + resetNotification(); + } else if (intent.getAction().equals( + WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + // reset & clear notification on a network connect & disconnect + switch(mNetworkInfo.getDetailedState()) { + case CONNECTED: + case DISCONNECTED: + resetNotification(); + break; + } + } else if (intent.getAction().equals( + WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + checkAndSetNotification(); + } + } + }, filter); + + // Setting is in seconds + NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; + mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); + mNotificationEnabledSettingObserver.register(); } /** @@ -172,7 +268,7 @@ public class WifiService extends IWifiManager.Stub { * * This function is used only at boot time */ - public void startWifi() { + public void checkAndStartWifi() { /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */ boolean wifiEnabled = !isAirplaneModeOn() && (getPersistedWifiEnabled() || testAndClearWifiSavedState()); @@ -255,17 +351,13 @@ public class WifiService extends IWifiManager.Stub { Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0); } - NetworkStateTracker getNetworkStateTracker() { - return mWifiStateTracker; - } - /** * see {@link android.net.wifi.WifiManager#pingSupplicant()} * @return {@code true} if the operation succeeds, {@code false} otherwise */ public boolean pingSupplicant() { enforceAccessPermission(); - return mWifiStateTracker.pingSupplicant(); + return mWifiStateMachine.pingSupplicant(); } /** @@ -274,7 +366,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean startScan(boolean forceActive) { enforceChangePermission(); - return mWifiStateTracker.startScan(forceActive); + return mWifiStateMachine.startScan(forceActive); } private void enforceAccessPermission() { @@ -304,7 +396,7 @@ public class WifiService extends IWifiManager.Stub { enforceChangePermission(); if (DBG) { - Slog.e(TAG, "Invoking mWifiStateTracker.setWifiEnabled\n"); + Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n"); } // set a flag if the user is enabling Wifi while in airplane mode @@ -312,7 +404,7 @@ public class WifiService extends IWifiManager.Stub { mAirplaneModeOverwridden.set(true); } - mWifiStateTracker.setWifiEnabled(enable); + mWifiStateMachine.setWifiEnabled(enable); persistWifiEnabled(enable); if (enable) { @@ -338,7 +430,7 @@ public class WifiService extends IWifiManager.Stub { */ public int getWifiEnabledState() { enforceAccessPermission(); - return mWifiStateTracker.getWifiState(); + return mWifiStateMachine.getWifiState(); } /** @@ -362,7 +454,7 @@ public class WifiService extends IWifiManager.Stub { setWifiApConfiguration(wifiConfig); } - mWifiStateTracker.setWifiApEnabled(wifiConfig, enabled); + mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled); return true; } @@ -377,7 +469,7 @@ public class WifiService extends IWifiManager.Stub { */ public int getWifiApEnabledState() { enforceAccessPermission(); - return mWifiStateTracker.getWifiApState(); + return mWifiStateMachine.getWifiApState(); } /** @@ -427,7 +519,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disconnect() { enforceChangePermission(); - return mWifiStateTracker.disconnectCommand(); + return mWifiStateMachine.disconnectCommand(); } /** @@ -436,7 +528,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean reconnect() { enforceChangePermission(); - return mWifiStateTracker.reconnectCommand(); + return mWifiStateMachine.reconnectCommand(); } /** @@ -445,7 +537,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean reassociate() { enforceChangePermission(); - return mWifiStateTracker.reassociateCommand(); + return mWifiStateMachine.reassociateCommand(); } /** @@ -454,7 +546,7 @@ public class WifiService extends IWifiManager.Stub { */ public List<WifiConfiguration> getConfiguredNetworks() { enforceAccessPermission(); - return mWifiStateTracker.getConfiguredNetworks(); + return mWifiStateMachine.getConfiguredNetworks(); } /** @@ -464,7 +556,7 @@ public class WifiService extends IWifiManager.Stub { */ public int addOrUpdateNetwork(WifiConfiguration config) { enforceChangePermission(); - return mWifiStateTracker.addOrUpdateNetwork(config); + return mWifiStateMachine.addOrUpdateNetwork(config); } /** @@ -475,7 +567,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean removeNetwork(int netId) { enforceChangePermission(); - return mWifiStateTracker.removeNetwork(netId); + return mWifiStateMachine.removeNetwork(netId); } /** @@ -487,7 +579,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean enableNetwork(int netId, boolean disableOthers) { enforceChangePermission(); - return mWifiStateTracker.enableNetwork(netId, disableOthers); + return mWifiStateMachine.enableNetwork(netId, disableOthers); } /** @@ -498,7 +590,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disableNetwork(int netId) { enforceChangePermission(); - return mWifiStateTracker.disableNetwork(netId); + return mWifiStateMachine.disableNetwork(netId); } /** @@ -511,7 +603,7 @@ public class WifiService extends IWifiManager.Stub { * Make sure we have the latest information, by sending * a status request to the supplicant. */ - return mWifiStateTracker.requestConnectionInfo(); + return mWifiStateMachine.requestConnectionInfo(); } /** @@ -521,7 +613,7 @@ public class WifiService extends IWifiManager.Stub { */ public List<ScanResult> getScanResults() { enforceAccessPermission(); - return mWifiStateTracker.getScanResultsList(); + return mWifiStateMachine.getScanResultsList(); } /** @@ -533,7 +625,7 @@ public class WifiService extends IWifiManager.Stub { public boolean saveConfiguration() { boolean result = true; enforceChangePermission(); - return mWifiStateTracker.saveConfig(); + return mWifiStateMachine.saveConfig(); } /** @@ -576,7 +668,7 @@ public class WifiService extends IWifiManager.Stub { numChannels); } - mWifiStateTracker.setNumAllowedChannels(numChannels); + mWifiStateMachine.setNumAllowedChannels(numChannels); return true; } @@ -596,7 +688,7 @@ public class WifiService extends IWifiManager.Stub { * Wi-Fi is not currently enabled), get the value from * Settings. */ - numChannels = mWifiStateTracker.getNumAllowedChannels(); + numChannels = mWifiStateMachine.getNumAllowedChannels(); if (numChannels < 0) { numChannels = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS, @@ -622,9 +714,59 @@ public class WifiService extends IWifiManager.Stub { */ public DhcpInfo getDhcpInfo() { enforceAccessPermission(); - return mWifiStateTracker.getDhcpInfo(); + return mWifiStateMachine.getDhcpInfo(); + } + + /** + * see {@link android.net.wifi.WifiManager#startWifi} + * + */ + public void startWifi() { + enforceChangePermission(); + /* TODO: may be add permissions for access only to connectivity service + * TODO: if a start issued, keep wifi alive until a stop issued irrespective + * of WifiLock & device idle status unless wifi enabled status is toggled + */ + + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.reconnectCommand(); + } + + /** + * see {@link android.net.wifi.WifiManager#stopWifi} + * + */ + public void stopWifi() { + enforceChangePermission(); + /* TODO: may be add permissions for access only to connectivity service + * TODO: if a stop is issued, wifi is brought up only by startWifi + * unless wifi enabled status is toggled + */ + mWifiStateMachine.setDriverStart(false); + } + + + /** + * see {@link android.net.wifi.WifiManager#addToBlacklist} + * + */ + public void addToBlacklist(String bssid) { + enforceChangePermission(); + + mWifiStateMachine.addToBlacklist(bssid); + } + + /** + * see {@link android.net.wifi.WifiManager#clearBlacklist} + * + */ + public void clearBlacklist() { + enforceChangePermission(); + + mWifiStateMachine.clearBlacklist(); } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -641,11 +783,11 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.cancel(mIdleIntent); mDeviceIdle = false; mScreenOff = false; - mWifiStateTracker.enableRssiPolling(true); + mWifiStateMachine.enableRssiPolling(true); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { Slog.d(TAG, "ACTION_SCREEN_OFF"); mScreenOff = true; - mWifiStateTracker.enableRssiPolling(false); + mWifiStateMachine.enableRssiPolling(false); /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off * AND the "stay on while plugged in" setting doesn't match the @@ -653,7 +795,7 @@ public class WifiService extends IWifiManager.Stub { * or plugged in to AC). */ if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) { - WifiInfo info = mWifiStateTracker.requestConnectionInfo(); + WifiInfo info = mWifiStateMachine.requestConnectionInfo(); if (info.getSupplicantState() != SupplicantState.COMPLETED) { // we used to go to sleep immediately, but this caused some race conditions // we don't have time to track down for this release. Delay instead, @@ -704,7 +846,7 @@ public class WifiService extends IWifiManager.Stub { isBluetoothPlaying = true; } } - mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); + mWifiStateMachine.setBluetoothScanMode(isBluetoothPlaying); } else { return; @@ -771,21 +913,21 @@ public class WifiService extends IWifiManager.Stub { /* Disable tethering when airplane mode is enabled */ if (airplaneMode) { - mWifiStateTracker.setWifiApEnabled(null, false); + mWifiStateMachine.setWifiApEnabled(null, false); } if (wifiShouldBeEnabled) { if (wifiShouldBeStarted) { - mWifiStateTracker.setWifiEnabled(true); - mWifiStateTracker.setScanOnlyMode( + mWifiStateMachine.setWifiEnabled(true); + mWifiStateMachine.setScanOnlyMode( strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY); - mWifiStateTracker.startWifi(true); + mWifiStateMachine.setDriverStart(true); } else { - mWifiStateTracker.requestCmWakeLock(); - mWifiStateTracker.disconnectAndStop(); + mWifiStateMachine.requestCmWakeLock(); + mWifiStateMachine.setDriverStart(false); } } else { - mWifiStateTracker.setWifiEnabled(false); + mWifiStateMachine.setWifiEnabled(false); } } @@ -832,17 +974,17 @@ public class WifiService extends IWifiManager.Stub { + ", uid=" + Binder.getCallingUid()); return; } - pw.println("Wi-Fi is " + mWifiStateTracker.getWifiStateByName()); + pw.println("Wi-Fi is " + mWifiStateMachine.getWifiStateByName()); pw.println("Stay-awake conditions: " + Settings.System.getInt(mContext.getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0)); pw.println(); pw.println("Internal state:"); - pw.println(mWifiStateTracker); + pw.println(mWifiStateMachine); pw.println(); pw.println("Latest scan results:"); - List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList(); + List<ScanResult> scanResults = mWifiStateMachine.getScanResultsList(); if (scanResults != null && scanResults.size() != 0) { pw.println(" BSSID Frequency RSSI Flags SSID"); for (ScanResult r : scanResults) { @@ -1069,7 +1211,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } } } @@ -1084,7 +1226,7 @@ public class WifiService extends IWifiManager.Stub { // our new size == 1 (first call), but this function won't // be called often and by making the stopPacket call each // time we're less fragile and self-healing. - mWifiStateTracker.stopPacketFiltering(); + mWifiStateMachine.stopPacketFiltering(); } int uid = Binder.getCallingUid(); @@ -1121,7 +1263,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } Long ident = Binder.clearCallingIdentity(); @@ -1140,4 +1282,166 @@ public class WifiService extends IWifiManager.Stub { return (mMulticasters.size() > 0); } } + + private void checkAndSetNotification() { + // If we shouldn't place a notification on available networks, then + // don't bother doing any of the following + if (!mNotificationEnabled) return; + + State state = mNetworkInfo.getState(); + if ((state == NetworkInfo.State.DISCONNECTED) + || (state == NetworkInfo.State.UNKNOWN)) { + // Look for an open network + List<ScanResult> scanResults = mWifiStateMachine.getScanResultsList(); + if (scanResults != null) { + int numOpenNetworks = 0; + for (int i = scanResults.size() - 1; i >= 0; i--) { + ScanResult scanResult = scanResults.get(i); + + if (TextUtils.isEmpty(scanResult.capabilities)) { + numOpenNetworks++; + } + } + + if (numOpenNetworks > 0) { + if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { + /* + * We've scanned continuously at least + * NUM_SCANS_BEFORE_NOTIFICATION times. The user + * probably does not have a remembered network in range, + * since otherwise supplicant would have tried to + * associate and thus resetting this counter. + */ + setNotificationVisible(true, numOpenNetworks, false, 0); + } + return; + } + } + } + + // No open networks in range, remove the notification + setNotificationVisible(false, 0, false, 0); + } + + /** + * Clears variables related to tracking whether a notification has been + * shown recently and clears the current notification. + */ + private void resetNotification() { + mNotificationRepeatTime = 0; + mNumScansSinceNetworkStateChange = 0; + setNotificationVisible(false, 0, false, 0); + } + + /** + * Display or don't display a notification that there are open Wi-Fi networks. + * @param visible {@code true} if notification should be visible, {@code false} otherwise + * @param numNetworks the number networks seen + * @param force {@code true} to force notification to be shown/not-shown, + * even if it is already shown/not-shown. + * @param delay time in milliseconds after which the notification should be made + * visible or invisible. + */ + private void setNotificationVisible(boolean visible, int numNetworks, boolean force, + int delay) { + + // Since we use auto cancel on the notification, when the + // mNetworksAvailableNotificationShown is true, the notification may + // have actually been canceled. However, when it is false we know + // for sure that it is not being shown (it will not be shown any other + // place than here) + + // If it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown && !force) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + Message message; + if (visible) { + + // Not enough time has passed to show the notification again + if (System.currentTimeMillis() < mNotificationRepeatTime) { + return; + } + + if (mNotification == null) { + // Cache the Notification mainly so we can remove the + // EVENT_NOTIFICATION_CHANGED message with this Notification from + // the queue later + mNotification = new Notification(); + mNotification.when = 0; + mNotification.icon = ICON_NETWORKS_AVAILABLE; + mNotification.flags = Notification.FLAG_AUTO_CANCEL; + mNotification.contentIntent = PendingIntent.getActivity(mContext, 0, + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); + } + + CharSequence title = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available, numNetworks); + CharSequence details = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available_detailed, numNetworks); + mNotification.tickerText = title; + mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent); + + mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS; + + notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification); + /* + * TODO: Clean up connectivity service & remove this + */ + /* message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1, + ICON_NETWORKS_AVAILABLE, mNotification); */ + + + } else { + + notificationManager.cancel(ICON_NETWORKS_AVAILABLE); + /* + * TODO: Clean up connectivity service & remove this + */ + /* + // Remove any pending messages to show the notification + mCsHandler.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification); + + message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, + ICON_NETWORKS_AVAILABLE); + */ + } + + //mCsHandler.sendMessageDelayed(message, delay); + + mNotificationShown = visible; + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + + public NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mNotificationEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mNotificationEnabled = getValue(); + resetNotification(); + } + + private boolean getValue() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + } + } + + } diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index be14cd3d70f1..46d6befc0c0e 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -27,7 +27,6 @@ import android.net.DhcpInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiStateTracker; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -77,7 +76,6 @@ public class WifiWatchdogService { private Context mContext; private ContentResolver mContentResolver; - private WifiStateTracker mWifiStateTracker; private WifiManager mWifiManager; /** @@ -108,10 +106,9 @@ public class WifiWatchdogService { /** Whether the current AP check should be canceled. */ private boolean mShouldCancel; - WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) { + WifiWatchdogService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mWifiStateTracker = wifiStateTracker; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); createThread(); @@ -752,7 +749,7 @@ public class WifiWatchdogService { // Black list this "bad" AP, this will cause an attempt to connect to another blacklistAp(ap.bssid); // Initiate an association to an alternate AP - mWifiStateTracker.reassociateCommand(); + mWifiManager.reassociate(); } private void blacklistAp(String bssid) { @@ -763,7 +760,7 @@ public class WifiWatchdogService { // Before taking action, make sure we should not cancel our processing if (shouldCancel()) return; - mWifiStateTracker.addToBlacklist(bssid); + mWifiManager.addToBlacklist(bssid); if (D) { myLogD("Blacklisting " + bssid); @@ -858,7 +855,7 @@ public class WifiWatchdogService { * (and blacklisted them). Clear the blacklist so the AP with best * signal is chosen. */ - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); if (V) { myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); @@ -929,7 +926,7 @@ public class WifiWatchdogService { * should revert anything done by the watchdog monitoring. */ private void handleReset() { - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); setIdleState(true); } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index c4de958a0873..3f5888bf84e2 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -62,10 +62,14 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.Typeface; +import android.graphics.Paint.FontMetricsInt; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; @@ -92,6 +96,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; +import android.util.TypedValue; import android.view.Display; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -115,6 +120,7 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.WindowManagerPolicy; +import android.view.Surface.OutOfResourcesException; import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; @@ -369,6 +375,7 @@ public class WindowManagerService extends IWindowManager.Stub private DimAnimator mDimAnimator = null; Surface mBlurSurface; boolean mBlurShown; + Watermark mWatermark; int mTransactionSequence = 0; @@ -4373,11 +4380,19 @@ public class WindowManagerService extends IWindowManager.Stub } return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw); } - + public boolean hasKeys(int[] keycodes, boolean[] keyExists) { return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists); } + public InputChannel monitorInput(String inputChannelName) { + if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, + "monitorInput()")) { + throw new SecurityException("Requires READ_INPUT_STATE permission"); + } + return mInputManager.monitorInput(inputChannelName); + } + public void enableScreenAfterBoot() { synchronized(mWindowMap) { if (mSystemBooted) { @@ -4720,6 +4735,51 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Returns the focused window in the following format: + * windowHashCodeInHexadecimal windowName + * + * @param client The remote client to send the listing to. + * @return False if an error occurred, true otherwise. + */ + boolean viewServerGetFocusedWindow(Socket client) { + if (isSystemSecure()) { + return false; + } + + boolean result = true; + + WindowState focusedWindow = getFocusedWindow(); + + BufferedWriter out = null; + + // Any uncaught exception will crash the system process + try { + OutputStream clientStream = client.getOutputStream(); + out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); + + if(focusedWindow != null) { + out.write(Integer.toHexString(System.identityHashCode(focusedWindow))); + out.write(' '); + out.append(focusedWindow.mAttrs.getTitle()); + } + out.write('\n'); + out.flush(); + } catch (Exception e) { + result = false; + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + result = false; + } + } + } + + return result; + } + + /** * Sends a command to a target window. The result of the command, if any, will be * written in the output stream of the specified socket. * @@ -7817,6 +7877,7 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RemoteException e) { // Ignore if process has died. } + notifyFocusChanged(); } if (lastFocus != null) { @@ -7826,7 +7887,6 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RemoteException e) { // Ignore if process has died. } - notifyFocusChanged(); } } } break; @@ -8471,12 +8531,6 @@ public class WindowManagerService extends IWindowManager.Stub updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); } - if (mFxSession == null) { - mFxSession = new SurfaceSession(); - } - - if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); - // Initialize state of exiting tokens. for (i=mExitingTokens.size()-1; i>=0; i--) { mExitingTokens.get(i).hasVisible = false; @@ -8493,8 +8547,24 @@ public class WindowManagerService extends IWindowManager.Stub float buttonBrightness = -1; boolean focusDisplayed = false; boolean animating = false; + boolean createWatermark = false; + + if (mFxSession == null) { + mFxSession = new SurfaceSession(); + createWatermark = true; + } + + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION"); Surface.openTransaction(); + + if (createWatermark) { + createWatermark(); + } + if (mWatermark != null) { + mWatermark.positionSurface(dw, dh); + } + try { boolean wallpaperForceHidingChanged = false; int repeats = 0; @@ -9997,6 +10067,93 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.release(); } + static int getPropertyInt(String name, int defUnits, int defDps, DisplayMetrics dm) { + String str = SystemProperties.get(name); + try { + int val = Integer.parseInt(str); + return val; + } catch (Exception e) { + } + if (defUnits == TypedValue.COMPLEX_UNIT_PX) { + return defDps; + } + int val = (int)TypedValue.applyDimension(defUnits, defDps, dm); + return val; + } + + class Watermark { + Surface mSurface; + int mWidth; + int mHeight; + int mXPercent; + int mYPercent; + + Watermark(SurfaceSession session, String text) { + final DisplayMetrics dm = new DisplayMetrics(); + mDisplay.getMetrics(dm); + + int fontSize = getPropertyInt("ro.watermark.height", + TypedValue.COMPLEX_UNIT_DIP, 48, dm); + mXPercent = getPropertyInt("ro.watermark.x", + TypedValue.COMPLEX_UNIT_PX, 50, dm); + mYPercent = getPropertyInt("ro.watermark.y", + TypedValue.COMPLEX_UNIT_PX, 99, dm); + int color = getPropertyInt("ro.watermark.color", + TypedValue.COMPLEX_UNIT_PX, 0x80ffffff, dm); + int shadowRadius = getPropertyInt("ro.watermark.shadow.radius", + TypedValue.COMPLEX_UNIT_PX, 5, dm); + int shadowDx = getPropertyInt("ro.watermark.shadow.dx", + TypedValue.COMPLEX_UNIT_PX, 0, dm); + int shadowDy = getPropertyInt("ro.watermark.shadow.dy", + TypedValue.COMPLEX_UNIT_PX, 0, dm); + int shadowColor = getPropertyInt("ro.watermark.shadow.color", + TypedValue.COMPLEX_UNIT_PX, 0xff000000, dm); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setTextSize(fontSize); + paint.setColor(color); + paint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); + paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)); + + FontMetricsInt fm = paint.getFontMetricsInt(); + mHeight = fm.descent - fm.ascent + 20; + mWidth = (int)paint.measureText(text) + 20; + + try { + mSurface = new Surface(session, 0, + "WatermarkSurface", + -1, mWidth, mHeight, PixelFormat.TRANSLUCENT, 0); + mSurface.setLayer(TYPE_LAYER_MULTIPLIER*100); + Rect dirty = new Rect(0, 0, mWidth, mHeight); + Canvas c = mSurface.lockCanvas(dirty); + c.drawText(text, 10, -fm.ascent+10, paint); + mSurface.unlockCanvasAndPost(c); + mSurface.show(); + } catch (OutOfResourcesException e) { + } + } + + void positionSurface(int dw, int dh) { + int availW = dw - mWidth; + int availH = dh - mHeight; + mSurface.setPosition((availW*mXPercent)/100, + (availH*mYPercent)/100); + } + } + + void createWatermark() { + if (mWatermark != null) { + return; + } + + String text = SystemProperties.get("ro.watermark.text"); + if (text == null || text.length() <= 0) { + return; + } + + mWatermark = new Watermark(mFxSession, text); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission("android.permission.DUMP") diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index a32cd4c2b374..df930ad445e6 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -3578,6 +3578,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { // Tell anyone interested that we are done booting! + SystemProperties.set("sys.boot_completed", "1"); broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null), null, null, 0, null, null, diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 0982b32719a4..ebe71ab777b6 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -213,7 +213,7 @@ public: void setDisplayOrientation(int32_t displayId, int32_t orientation); status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, - jweak inputChannelObjWeak); + jweak inputChannelObjWeak, bool monitor); status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel); void setInputWindows(JNIEnv* env, jobjectArray windowObjArray); @@ -334,6 +334,7 @@ private: bool mWindowsReady; Vector<InputWindow> mWindows; Vector<InputWindow*> mWallpaperWindows; + Vector<sp<InputChannel> > mMonitoringChannels; // Focus tracking for keys, trackball, etc. InputWindow* mFocusedWindow; @@ -382,6 +383,10 @@ private: static void addTarget(const InputWindow* window, int32_t targetFlags, nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets); + void registerMonitoringChannel(const sp<InputChannel>& inputChannel); + void unregisterMonitoringChannel(const sp<InputChannel>& inputChannel); + void addMonitoringTargetsLd(Vector<InputTarget>& outTargets); + static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } @@ -492,7 +497,7 @@ void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orient } status_t NativeInputManager::registerInputChannel(JNIEnv* env, - const sp<InputChannel>& inputChannel, jobject inputChannelObj) { + const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) { jweak inputChannelObjWeak = env->NewWeakGlobalRef(inputChannelObj); if (! inputChannelObjWeak) { LOGE("Could not create weak reference for input channel."); @@ -519,9 +524,14 @@ status_t NativeInputManager::registerInputChannel(JNIEnv* env, status = mInputManager->registerInputChannel(inputChannel); if (! status) { + // Success. + if (monitor) { + registerMonitoringChannel(inputChannel); + } return OK; } + // Failed! { AutoMutex _l(mInputChannelRegistryLock); mInputChannelObjWeakByReceiveFd.removeItem(inputChannel->getReceivePipeFd()); @@ -552,6 +562,8 @@ status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, env->DeleteWeakGlobalRef(inputChannelObjWeak); + unregisterMonitoringChannel(inputChannel); + return mInputManager->unregisterInputChannel(inputChannel); } @@ -829,6 +841,8 @@ void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputC env->DeleteLocalRef(inputChannelObjLocal); } + + unregisterMonitoringChannel(inputChannel); } bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel, @@ -1429,7 +1443,9 @@ int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uin // If there is no currently touched window then fail. if (! mTouchedWindow) { - LOGW("Dropping event because there is no touched window to receive it."); +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because there is no touched window to receive it."); +#endif injectionResult = INPUT_EVENT_INJECTION_FAILED; injectionPermission = INJECTION_PERMISSION_GRANTED; break; // failed, exit wait loop @@ -1587,6 +1603,8 @@ int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t outTargets.clear(); return INPUT_EVENT_INJECTION_SUCCEEDED; } + + addMonitoringTargetsLd(outTargets); } pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); @@ -1631,6 +1649,8 @@ int32_t NativeInputManager::waitForNonTouchEventTargets(MotionEvent* motionEvent } windowType = focusedWindow->layoutParamsType; + + addMonitoringTargetsLd(outTargets); } // release lock pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); @@ -1657,6 +1677,8 @@ int32_t NativeInputManager::waitForTouchEventTargets(MotionEvent* motionEvent, } windowType = touchedWindow->layoutParamsType; + + addMonitoringTargetsLd(outTargets); } // release lock int32_t eventType; @@ -1714,6 +1736,39 @@ void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) android_server_PowerManagerService_userActivity(eventTime, eventType); } +void NativeInputManager::registerMonitoringChannel(const sp<InputChannel>& inputChannel) { + { // acquire lock + AutoMutex _l(mDispatchLock); + mMonitoringChannels.push(inputChannel); + } // release lock +} + +void NativeInputManager::unregisterMonitoringChannel(const sp<InputChannel>& inputChannel) { + { // acquire lock + AutoMutex _l(mDispatchLock); + + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + if (mMonitoringChannels[i] == inputChannel) { + mMonitoringChannels.removeAt(i); + break; + } + } + } // release lock +} + +void NativeInputManager::addMonitoringTargetsLd(Vector<InputTarget>& outTargets) { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + outTargets.push(); + + InputTarget& target = outTargets.editTop(); + target.inputChannel = mMonitoringChannels[i]; + target.flags = 0; + target.timeout = -1; + target.xOffset = 0; + target.yOffset = 0; + } +} + static void dumpMotionRange(String8& dump, const char* name, const InputDeviceInfo::MotionRange* range) { if (range) { @@ -1805,6 +1860,11 @@ void NativeInputManager::dumpDispatchStateLd(String8& dump) { mWindows[i].ownerPid, mWindows[i].ownerUid, mWindows[i].dispatchingTimeout / 1000000.0); } + + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + dump.appendFormat(" monitoringChannel[%d]: '%s'\n", + i, mMonitoringChannels[i]->getName().string()); + } } // ---------------------------------------------------------------------------- @@ -2012,7 +2072,7 @@ static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env, } static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, - jobject inputChannelObj) { + jobject inputChannelObj, jboolean monitor) { if (checkInputManagerUnitialized(env)) { return; } @@ -2026,15 +2086,17 @@ static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, status_t status = gNativeInputManager->registerInputChannel( - env, inputChannel, inputChannelObj); + env, inputChannel, inputChannelObj, monitor); if (status) { jniThrowRuntimeException(env, "Failed to register input channel. " "Check logs for details."); return; } - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, - android_server_InputManager_handleInputChannelDisposed, NULL); + if (! monitor) { + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + android_server_InputManager_handleInputChannelDisposed, NULL); + } } static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, @@ -2149,7 +2211,7 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetSwitchState }, { "nativeHasKeys", "(II[I[Z)Z", (void*) android_server_InputManager_nativeHasKeys }, - { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V", + { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;Z)V", (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index a14bfb569b8b..79772edec355 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -5,6 +5,7 @@ LOCAL_SRC_FILES:= \ clz.cpp.arm \ DisplayHardware/DisplayHardware.cpp \ DisplayHardware/DisplayHardwareBase.cpp \ + DisplayHardware/HWComposer.cpp \ BlurFilter.cpp.arm \ GLExtensions.cpp \ Layer.cpp \ diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index 2eac0a80a08b..166c52804e10 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -36,11 +36,11 @@ #include "DisplayHardware/DisplayHardware.h" -#include <hardware/copybit.h> #include <hardware/overlay.h> #include <hardware/gralloc.h> #include "GLExtensions.h" +#include "HWComposer.h" using namespace android; @@ -76,7 +76,7 @@ DisplayHardware::DisplayHardware( const sp<SurfaceFlinger>& flinger, uint32_t dpy) : DisplayHardwareBase(flinger, dpy), - mFlags(0) + mFlags(0), mHwc(0) { init(dpy); } @@ -262,6 +262,17 @@ void DisplayHardware::init(uint32_t dpy) // Unbind the context from this thread eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + + // initialize the H/W composer + mHwc = new HWComposer(); + if (mHwc->initCheck() == NO_ERROR) { + mHwc->setFrameBuffer(mDisplay, mSurface); + } +} + +HWComposer& DisplayHardware::getHwComposer() const { + return *mHwc; } /* @@ -317,7 +328,12 @@ void DisplayHardware::flip(const Region& dirty) const } mPageFlipCount++; - eglSwapBuffers(dpy, surface); + + if (mHwc->initCheck() == NO_ERROR) { + mHwc->commit(); + } else { + eglSwapBuffers(dpy, surface); + } checkEGLErrors("eglSwapBuffers"); // for debugging diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index 66bf5215d5d6..f2cfd2db579b 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -34,12 +34,11 @@ #include "DisplayHardware/DisplayHardwareBase.h" struct overlay_control_device_t; -struct framebuffer_device_t; -struct copybit_image_t; namespace android { class FramebufferNativeWindow; +class HWComposer; class DisplayHardware : public DisplayHardwareBase { @@ -80,6 +79,9 @@ public: uint32_t getPageFlipCount() const; EGLDisplay getEGLDisplay() const { return mDisplay; } overlay_control_device_t* getOverlayEngine() const { return mOverlayEngine; } + + // Hardware Composer + HWComposer& getHwComposer() const; status_t compositionComplete() const; @@ -107,6 +109,8 @@ private: GLint mMaxViewportDims; GLint mMaxTextureSize; + HWComposer* mHwc; + sp<FramebufferNativeWindow> mNativeWindow; overlay_control_device_t* mOverlayEngine; }; diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp new file mode 100644 index 000000000000..8ca880b09502 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -0,0 +1,96 @@ +/* + * 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 <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <hardware/hardware.h> + +#include <cutils/log.h> + +#include <EGL/egl.h> + +#include "HWComposer.h" + +namespace android { +// --------------------------------------------------------------------------- + +HWComposer::HWComposer() + : mModule(0), mHwc(0), mList(0), + mDpy(EGL_NO_DISPLAY), mSur(EGL_NO_SURFACE) +{ + int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule); + LOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID); + if (err == 0) { + err = hwc_open(mModule, &mHwc); + LOGE_IF(err, "%s device failed to initialize (%s)", + HWC_HARDWARE_COMPOSER, strerror(-err)); + } +} + +HWComposer::~HWComposer() { + free(mList); + if (mHwc) { + hwc_close(mHwc); + } +} + +status_t HWComposer::initCheck() const { + return mHwc ? NO_ERROR : NO_INIT; +} + +void HWComposer::setFrameBuffer(EGLDisplay dpy, EGLSurface sur) { + mDpy = (hwc_display_t)dpy; + mSur = (hwc_surface_t)sur; +} + +status_t HWComposer::createWorkList(size_t numLayers) { + if (mHwc && (!mList || mList->numHwLayers < numLayers)) { + free(mList); + size_t size = sizeof(hwc_layer_list) + numLayers*sizeof(hwc_layer_t); + mList = (hwc_layer_list_t*)malloc(size); + mList->flags = HWC_GEOMETRY_CHANGED; + mList->numHwLayers = numLayers; + } + return NO_ERROR; +} + +status_t HWComposer::prepare() const { + int err = mHwc->prepare(mHwc, mList); + return (status_t)err; +} + +status_t HWComposer::commit() const { + int err = mHwc->set(mHwc, mDpy, mSur, mList); + mList->flags &= ~HWC_GEOMETRY_CHANGED; + return (status_t)err; +} + +HWComposer::iterator HWComposer::begin() { + return mList ? &(mList->hwLayers[0]) : NULL; +} + +HWComposer::iterator HWComposer::end() { + return mList ? &(mList->hwLayers[mList->numHwLayers]) : NULL; +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h new file mode 100644 index 000000000000..729f23bbe0f5 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -0,0 +1,70 @@ +/* + * 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_SF_HWCOMPOSER_H +#define ANDROID_SF_HWCOMPOSER_H + +#include <stdint.h> +#include <sys/types.h> + +#include <EGL/egl.h> + +#include <hardware/hwcomposer.h> + +namespace android { +// --------------------------------------------------------------------------- + +class HWComposer +{ +public: + + HWComposer(); + ~HWComposer(); + + status_t initCheck() const; + + // tells the HAL what the framebuffer is + void setFrameBuffer(EGLDisplay dpy, EGLSurface sur); + + // create a work list for numLayers layer + status_t createWorkList(size_t numLayers); + + // Asks the HAL what it can do + status_t prepare() const; + + // commits the list + status_t commit() const; + + + typedef hwc_layer_t const * const_iterator; + typedef hwc_layer_t* iterator; + + iterator begin(); + iterator end(); + +private: + hw_module_t const* mModule; + hwc_composer_device_t* mHwc; + hwc_layer_list_t* mList; + hwc_display_t mDpy; + hwc_surface_t mSur; +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SF_HWCOMPOSER_H diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 629d993ca007..3720e166992b 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -35,6 +35,7 @@ #include "Layer.h" #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" +#include "DisplayHardware/HWComposer.h" #define DEBUG_RESIZE 0 @@ -177,6 +178,62 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h, return NO_ERROR; } +void Layer::setGeometry(hwc_layer_t* hwcl) +{ + hwcl->compositionType = HWC_FRAMEBUFFER; + hwcl->hints = 0; + hwcl->flags = 0; + hwcl->transform = 0; + hwcl->blending = HWC_BLENDING_NONE; + + // we can't do alpha-fade with the hwc HAL + const State& s(drawingState()); + if (s.alpha < 0xFF) { + hwcl->flags = HWC_SKIP_LAYER; + return; + } + + // we can only handle simple transformation + if (mOrientation & Transform::ROT_INVALID) { + hwcl->flags = HWC_SKIP_LAYER; + return; + } + + hwcl->transform = mOrientation; + + if (needsBlending()) { + hwcl->blending = mPremultipliedAlpha ? + HWC_BLENDING_PREMULT : HWC_BLENDING_COVERAGE; + } + + hwcl->displayFrame.left = mTransformedBounds.left; + hwcl->displayFrame.top = mTransformedBounds.top; + hwcl->displayFrame.right = mTransformedBounds.right; + hwcl->displayFrame.bottom = mTransformedBounds.bottom; + + hwcl->visibleRegionScreen.rects = + reinterpret_cast<hwc_rect_t const *>( + visibleRegionScreen.getArray( + &hwcl->visibleRegionScreen.numRects)); +} + +void Layer::setPerFrameData(hwc_layer_t* hwcl) { + sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); + if (buffer == NULL) { + // this situation can happen if we ran out of memory for instance. + // not much we can do. continue to use whatever texture was bound + // to this context. + hwcl->handle = NULL; + return; + } + hwcl->handle = const_cast<native_handle_t*>(buffer->handle); + // TODO: set the crop value properly + hwcl->sourceCrop.left = 0; + hwcl->sourceCrop.top = 0; + hwcl->sourceCrop.right = buffer->width; + hwcl->sourceCrop.bottom = buffer->height; +} + void Layer::reloadTexture(const Region& dirty) { sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer()); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index e1d283beddb7..188da6a784df 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -68,6 +68,8 @@ public: bool isFixedSize() const; // LayerBase interface + virtual void setGeometry(hwc_layer_t* hwcl); + virtual void setPerFrameData(hwc_layer_t* hwcl); 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 d5aa53f9f178..043d54d96210 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -39,8 +39,11 @@ namespace android { // --------------------------------------------------------------------------- +int32_t LayerBase::sSequence = 1; + LayerBase::LayerBase(SurfaceFlinger* flinger, DisplayID display) : dpy(display), contentDirty(false), + sequence(uint32_t(android_atomic_inc(&sSequence))), mFlinger(flinger), mNeedsFiltering(false), mOrientation(0), @@ -304,22 +307,17 @@ void LayerBase::drawRegion(const Region& reg) const } } -void LayerBase::draw(const Region& inClip) const -{ - // invalidate the region we'll update - Region clip(inClip); // copy-on-write, so no-op most of the time - - // Remove the transparent area from the clipping region - const State& s = drawingState(); - if (LIKELY(!s.transparentRegion.isEmpty())) { - clip.subtract(transparentRegionScreen); - if (clip.isEmpty()) { - // usually this won't happen because this should be taken care of - // by SurfaceFlinger::computeVisibleRegions() - return; - } - } +void LayerBase::setGeometry(hwc_layer_t* hwcl) { + hwcl->flags |= HWC_SKIP_LAYER; +} +void LayerBase::setPerFrameData(hwc_layer_t* hwcl) { + hwcl->compositionType = HWC_FRAMEBUFFER; + hwcl->handle = NULL; +} + +void LayerBase::draw(const Region& clip) const +{ // reset GL state glEnable(GL_SCISSOR_TEST); diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 4288cf79469b..dd1cd05f1b93 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -35,6 +35,8 @@ #include <pixelflinger/pixelflinger.h> +#include <hardware/hwcomposer.h> + #include "Transform.h" namespace android { @@ -53,6 +55,8 @@ class Texture; class LayerBase : public RefBase { + static int32_t sSequence; + public: LayerBase(SurfaceFlinger* flinger, DisplayID display); @@ -61,6 +65,7 @@ public: Region visibleRegionScreen; Region transparentRegionScreen; Region coveredRegionScreen; + int32_t sequence; struct State { uint32_t w; @@ -105,6 +110,10 @@ public: virtual const char* getTypeId() const { return "LayerBase"; } + virtual void setGeometry(hwc_layer_t* hwcl); + + virtual void setPerFrameData(hwc_layer_t* hwcl); + /** * draw - performs some global clipping optimizations * and calls onDraw(). @@ -210,12 +219,6 @@ public: inline const State& currentState() const { return mCurrentState; } inline State& currentState() { return mCurrentState; } - static int compareCurrentStateZ( - sp<LayerBase> const * layerA, - sp<LayerBase> const * layerB) { - return layerA[0]->currentState().z - layerB[0]->currentState().z; - } - int32_t getOrientation() const { return mOrientation; } int tx() const { return mLeft; } int ty() const { return mTop; } diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp index 5f836366c431..e8b2ebf16745 100644 --- a/services/surfaceflinger/LayerBuffer.cpp +++ b/services/surfaceflinger/LayerBuffer.cpp @@ -495,7 +495,7 @@ status_t LayerBuffer::BufferSource::initTempBuffer() const const ISurface::BufferHeap& buffers(mBufferHeap); uint32_t w = mLayer.mTransformedBounds.width(); uint32_t h = mLayer.mTransformedBounds.height(); - if (buffers.w * h != buffers.h * w) { + if (mLayer.getOrientation() & (Transform::ROT_90 | Transform::ROT_270)) { int t = w; w = h; h = t; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3167c4cb7ed1..47bb4c1b1111 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -52,6 +52,7 @@ #include "SurfaceFlinger.h" #include "DisplayHardware/DisplayHardware.h" +#include "DisplayHardware/HWComposer.h" /* ideally AID_GRAPHICS would be in a semi-public header * or there would be a way to map a user/group name to its id @@ -65,95 +66,6 @@ namespace android { // --------------------------------------------------------------------------- -SurfaceFlinger::LayerVector::LayerVector(const SurfaceFlinger::LayerVector& rhs) - : lookup(rhs.lookup), layers(rhs.layers) -{ -} - -ssize_t SurfaceFlinger::LayerVector::indexOf( - const sp<LayerBase>& key, size_t guess) const -{ - if (guess<size() && lookup.keyAt(guess) == key) - return guess; - const ssize_t i = lookup.indexOfKey(key); - if (i>=0) { - const size_t idx = lookup.valueAt(i); - LOGE_IF(layers[idx]!=key, - "LayerVector[%p]: layers[%d]=%p, key=%p", - this, int(idx), layers[idx].get(), key.get()); - return idx; - } - return i; -} - -ssize_t SurfaceFlinger::LayerVector::add( - const sp<LayerBase>& layer, - Vector< sp<LayerBase> >::compar_t cmp) -{ - size_t count = layers.size(); - ssize_t l = 0; - ssize_t h = count-1; - ssize_t mid; - sp<LayerBase> const* a = layers.array(); - while (l <= h) { - mid = l + (h - l)/2; - const int c = cmp(a+mid, &layer); - if (c == 0) { l = mid; break; } - else if (c<0) { l = mid+1; } - else { h = mid-1; } - } - size_t order = l; - while (order<count && !cmp(&layer, a+order)) { - order++; - } - count = lookup.size(); - for (size_t i=0 ; i<count ; i++) { - if (lookup.valueAt(i) >= order) { - lookup.editValueAt(i)++; - } - } - layers.insertAt(layer, order); - lookup.add(layer, order); - return order; -} - -ssize_t SurfaceFlinger::LayerVector::remove(const sp<LayerBase>& layer) -{ - const ssize_t keyIndex = lookup.indexOfKey(layer); - if (keyIndex >= 0) { - const size_t index = lookup.valueAt(keyIndex); - LOGE_IF(layers[index]!=layer, - "LayerVector[%p]: layers[%u]=%p, layer=%p", - this, int(index), layers[index].get(), layer.get()); - layers.removeItemsAt(index); - lookup.removeItemsAt(keyIndex); - const size_t count = lookup.size(); - for (size_t i=0 ; i<count ; i++) { - if (lookup.valueAt(i) >= size_t(index)) { - lookup.editValueAt(i)--; - } - } - return index; - } - return NAME_NOT_FOUND; -} - -ssize_t SurfaceFlinger::LayerVector::reorder( - const sp<LayerBase>& layer, - Vector< sp<LayerBase> >::compar_t cmp) -{ - // XXX: it's a little lame. but oh well... - ssize_t err = remove(layer); - if (err >=0) - err = add(layer, cmp); - return err; -} - -// --------------------------------------------------------------------------- -#if 0 -#pragma mark - -#endif - SurfaceFlinger::SurfaceFlinger() : BnSurfaceComposer(), Thread(false), mTransactionFlags(0), @@ -165,6 +77,7 @@ SurfaceFlinger::SurfaceFlinger() mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"), mDump("android.permission.DUMP"), mVisibleRegionsDirty(false), + mHwWorkListDirty(false), mDeferReleaseConsole(false), mFreezeDisplay(false), mFreezeCount(0), @@ -457,6 +370,11 @@ bool SurfaceFlinger::threadLoop() // post surfaces (if needed) handlePageFlip(); + if (UNLIKELY(mHwWorkListDirty)) { + // build the h/w work list + handleWorkList(); + } + const DisplayHardware& hw(graphicPlane(0).displayHardware()); if (LIKELY(hw.canDraw() && !isFrozen())) { // repaint the framebuffer (if needed) @@ -521,6 +439,10 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) { Vector< sp<LayerBase> > ditchedLayers; + /* + * Perform and commit the transaction + */ + { // scope for the lock Mutex::Autolock _l(mStateLock); const nsecs_t now = systemTime(); @@ -528,9 +450,15 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) handleTransactionLocked(transactionFlags, ditchedLayers); mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; + mHwWorkListDirty = true; + // here the transaction has been committed } - // do this without lock held + /* + * Clean-up all layers that went away + * (do this without the lock held) + */ + const size_t count = ditchedLayers.size(); for (size_t i=0 ; i<count ; i++) { if (ditchedLayers[i] != 0) { @@ -764,8 +692,8 @@ void SurfaceFlinger::commitTransaction() void SurfaceFlinger::handlePageFlip() { bool visibleRegions = mVisibleRegionsDirty; - LayerVector& currentLayers = const_cast<LayerVector&>( - mDrawingState.layersSortedByZ); + LayerVector& currentLayers( + const_cast<LayerVector&>(mDrawingState.layersSortedByZ)); visibleRegions |= lockPageFlip(currentLayers); const DisplayHardware& hw = graphicPlane(0).displayHardware(); @@ -773,8 +701,22 @@ void SurfaceFlinger::handlePageFlip() if (visibleRegions) { Region opaqueRegion; computeVisibleRegions(currentLayers, mDirtyRegion, opaqueRegion); + + /* + * rebuild the visible layer list + */ + mVisibleLayersSortedByZ.clear(); + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); + size_t count = currentLayers.size(); + mVisibleLayersSortedByZ.setCapacity(count); + for (size_t i=0 ; i<count ; i++) { + if (!currentLayers[i]->visibleRegionScreen.isEmpty()) + mVisibleLayersSortedByZ.add(currentLayers[i]); + } + mWormholeRegion = screenRegion.subtract(opaqueRegion); mVisibleRegionsDirty = false; + mHwWorkListDirty = true; } unlockPageFlip(currentLayers); @@ -805,6 +747,21 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) } } +void SurfaceFlinger::handleWorkList() +{ + mHwWorkListDirty = false; + HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer()); + if (hwc.initCheck() == NO_ERROR) { + const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ); + const size_t count = currentLayers.size(); + hwc.createWorkList(count); + HWComposer::iterator cur(hwc.begin()); + HWComposer::iterator last(hwc.end()); + for (size_t i=0 ; (i<count) && (cur!=last) ; ++i, ++cur) { + currentLayers[i]->setGeometry(cur); + } + } +} void SurfaceFlinger::handleRepaint() { @@ -869,18 +826,61 @@ void SurfaceFlinger::composeSurfaces(const Region& dirty) // draw something... drawWormhole(); } - const SurfaceFlinger& flinger(*this); - const LayerVector& drawingLayers(mDrawingState.layersSortedByZ); - const size_t count = drawingLayers.size(); - sp<LayerBase> const* const layers = drawingLayers.array(); + + status_t err = NO_ERROR; + const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ); + const size_t count = layers.size(); + + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + HWComposer& hwc(hw.getHwComposer()); + HWComposer::iterator cur(hwc.begin()); + HWComposer::iterator last(hwc.end()); + + // update the per-frame h/w composer data for each layer + if (cur != last) { + for (size_t i=0 ; i<count && cur!=last ; ++i, ++cur) { + layers[i]->setPerFrameData(cur); + } + err = hwc.prepare(); + LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err)); + } + + // and then, render the layers targeted at the framebuffer + Region transparent(hw.bounds()); for (size_t i=0 ; i<count ; ++i) { - const sp<LayerBase>& layer = layers[i]; - const Region& visibleRegion(layer->visibleRegionScreen); - if (!visibleRegion.isEmpty()) { - const Region clip(dirty.intersect(visibleRegion)); - if (!clip.isEmpty()) { - layer->draw(clip); + + // see if we need to skip this layer + if (!err && cur != last) { + if (!((cur->compositionType == HWC_FRAMEBUFFER) || + (cur->flags & HWC_SKIP_LAYER))) { + ++cur; + continue; } + ++cur; + } + + // draw the layer into the framebuffer + const sp<LayerBase>& layer(layers[i]); + transparent.subtractSelf(layer->visibleRegionScreen); + const Region clip(dirty.intersect(layer->visibleRegionScreen)); + if (!clip.isEmpty()) { + layer->draw(clip); + } + } + + // finally clear everything we didn't draw as a result of calling + // prepare (this leaves the FB transparent). + transparent.andSelf(dirty); + if (!transparent.isEmpty()) { + glClearColor(0,0,0,0); + Region::const_iterator it = transparent.begin(); + Region::const_iterator const end = transparent.end(); + const int32_t height = hw.getHeight(); + while (it != end) { + const Rect& r(*it++); + const GLint sy = height - (r.top + r.height()); + glScissor(r.left, sy, r.width(), r.height()); + glClear(GL_COLOR_BUFFER_BIT); } } } @@ -1029,8 +1029,7 @@ status_t SurfaceFlinger::addLayer(const sp<LayerBase>& layer) status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer) { - ssize_t i = mCurrentState.layersSortedByZ.add( - layer, &LayerBase::compareCurrentStateZ); + ssize_t i = mCurrentState.layersSortedByZ.add(layer); return (i < 0) ? status_t(i) : status_t(NO_ERROR); } @@ -1372,9 +1371,10 @@ status_t SurfaceFlinger::setClientState( flags |= eTraversalNeeded; } if (what & eLayerChanged) { + ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer); if (layer->setLayer(s.z)) { - mCurrentState.layersSortedByZ.reorder( - layer, &Layer::compareCurrentStateZ); + mCurrentState.layersSortedByZ.removeAt(idx); + mCurrentState.layersSortedByZ.add(layer); // we need traversal (state changed) // AND transaction (list changed) flags |= eTransactionNeeded|eTraversalNeeded; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 8821e5c071e2..8e286e505d33 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -40,9 +40,6 @@ #include "MessageQueue.h" -struct copybit_device_t; -struct overlay_device_t; - namespace android { // --------------------------------------------------------------------------- @@ -246,21 +243,19 @@ private: status_t setClientState(const sp<Client>& client, int32_t count, const layer_state_t* states); - - class LayerVector { + class LayerVector : public SortedVector< sp<LayerBase> > { public: - inline LayerVector() { } - LayerVector(const LayerVector&); - inline size_t size() const { return layers.size(); } - inline sp<LayerBase> const* array() const { return layers.array(); } - ssize_t add(const sp<LayerBase>&, Vector< sp<LayerBase> >::compar_t); - ssize_t remove(const sp<LayerBase>&); - ssize_t reorder(const sp<LayerBase>&, Vector< sp<LayerBase> >::compar_t); - ssize_t indexOf(const sp<LayerBase>& key, size_t guess=0) const; - inline sp<LayerBase> operator [] (size_t i) const { return layers[i]; } - private: - KeyedVector< sp<LayerBase> , size_t> lookup; - Vector< sp<LayerBase> > layers; + LayerVector() { } + LayerVector(const LayerVector& rhs) : SortedVector< sp<LayerBase> >(rhs) { } + virtual int do_compare(const void* lhs, const void* rhs) const { + const sp<LayerBase>& l(*reinterpret_cast<const sp<LayerBase>*>(lhs)); + const sp<LayerBase>& r(*reinterpret_cast<const sp<LayerBase>*>(rhs)); + // sort layers by Z order + uint32_t lz = l->currentState().z; + uint32_t rz = r->currentState().z; + // then by sequence, so we get a stable ordering + return (lz != rz) ? (lz - rz) : (l->sequence - r->sequence); + } }; struct State { @@ -301,6 +296,7 @@ private: void handlePageFlip(); bool lockPageFlip(const LayerVector& currentLayers); void unlockPageFlip(const LayerVector& currentLayers); + void handleWorkList(); void handleRepaint(); void postFramebuffer(); void composeSurfaces(const Region& dirty); @@ -375,10 +371,13 @@ private: Region mInvalidRegion; Region mWormholeRegion; bool mVisibleRegionsDirty; + bool mHwWorkListDirty; bool mDeferReleaseConsole; bool mFreezeDisplay; int32_t mFreezeCount; nsecs_t mFreezeDisplayTime; + Vector< sp<LayerBase> > mVisibleLayersSortedByZ; + // don't use a lock for these, we don't care int mDebugRegion; diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java index 1381a2da18b0..2934e6af8258 100644 --- a/telephony/java/com/android/internal/telephony/CallManager.java +++ b/telephony/java/com/android/internal/telephony/CallManager.java @@ -24,6 +24,7 @@ import android.os.RegistrantList; import android.telephony.PhoneStateListener; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -163,6 +164,14 @@ public final class CallManager { } /** + * Returns all the registered phone objects. + * @return all the registered phone objects. + */ + public List<Phone> getAllPhones() { + return Collections.unmodifiableList(mPhones); + } + + /** * Get current coarse-grained voice call state. * If the Call Manager has an active call and call waiting occurs, * then the phone state is RINGING not OFFHOOK @@ -206,7 +215,7 @@ public final class CallManager { * @param phone */ public void unregisterPhone(Phone phone) { - if (phone != null && !mPhones.contains(phone)) { + if (phone != null && mPhones.contains(phone)) { mPhones.remove(phone); mRingingCalls.remove(phone.getRingingCall()); mBackgroundCalls.remove(phone.getBackgroundCall()); diff --git a/telephony/java/com/android/internal/telephony/PhoneFactory.java b/telephony/java/com/android/internal/telephony/PhoneFactory.java index 803b73685587..2e391cb96cb2 100644 --- a/telephony/java/com/android/internal/telephony/PhoneFactory.java +++ b/telephony/java/com/android/internal/telephony/PhoneFactory.java @@ -24,6 +24,8 @@ import android.util.Log; import com.android.internal.telephony.cdma.CDMAPhone; import com.android.internal.telephony.gsm.GSMPhone; +import com.android.internal.telephony.sip.SipPhone; +import com.android.internal.telephony.sip.SipPhoneFactory; /** * {@hide} @@ -175,4 +177,13 @@ public class PhoneFactory { return phone; } } + + /** + * Makes a {@link SipPhone} object. + * @param sipUri the local SIP URI the phone runs on + * @return the {@code SipPhone} object or null if the SIP URI is not valid + */ + public static SipPhone makeSipPhone(String sipUri) { + return SipPhoneFactory.makePhone(sipUri, sContext, sPhoneNotifier); + } } diff --git a/telephony/java/com/android/internal/telephony/cat/AppInterface.java b/telephony/java/com/android/internal/telephony/cat/AppInterface.java index 0ba3e11cabab..2eb6ccb5ee2e 100644 --- a/telephony/java/com/android/internal/telephony/cat/AppInterface.java +++ b/telephony/java/com/android/internal/telephony/cat/AppInterface.java @@ -58,7 +58,8 @@ public interface AppInterface { SET_UP_EVENT_LIST(0x05), SET_UP_IDLE_MODE_TEXT(0x28), SET_UP_MENU(0x25), - SET_UP_CALL(0x10); + SET_UP_CALL(0x10), + PROVIDE_LOCAL_INFORMATION(0x26); private int mValue; diff --git a/telephony/java/com/android/internal/telephony/cat/CatService.java b/telephony/java/com/android/internal/telephony/cat/CatService.java index b916713f6025..1e23e34a3312 100644 --- a/telephony/java/com/android/internal/telephony/cat/CatService.java +++ b/telephony/java/com/android/internal/telephony/cat/CatService.java @@ -22,6 +22,7 @@ import android.os.AsyncResult; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; +import android.os.SystemProperties; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.CommandsInterface; @@ -245,48 +246,46 @@ public class CatService extends Handler implements AppInterface { CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams); switch (cmdParams.getCommandType()) { - case SET_UP_MENU: - if (removeMenu(cmdMsg.getMenu())) { - mMenuCmd = null; - } else { - mMenuCmd = cmdMsg; - } - sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, - null); - break; - case DISPLAY_TEXT: - // when application is not required to respond, send an immediate - // response. - if (!cmdMsg.geTextMessage().responseNeeded) { - sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, - 0, null); - } - break; - case REFRESH: - // ME side only handles refresh commands which meant to remove IDLE - // MODE TEXT. - cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT - .value(); - break; - case SET_UP_IDLE_MODE_TEXT: - sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, - 0, null); - break; - case LAUNCH_BROWSER: - case SELECT_ITEM: - case GET_INPUT: - case GET_INKEY: - case SEND_DTMF: - case SEND_SMS: - case SEND_SS: - case SEND_USSD: - case PLAY_TONE: - case SET_UP_CALL: - // nothing to do on telephony! - break; - default: - CatLog.d(this, "Unsupported command"); - return; + case SET_UP_MENU: + if (removeMenu(cmdMsg.getMenu())) { + mMenuCmd = null; + } else { + mMenuCmd = cmdMsg; + } + sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null); + break; + case DISPLAY_TEXT: + // when application is not required to respond, send an immediate response. + if (!cmdMsg.geTextMessage().responseNeeded) { + sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null); + } + break; + case REFRESH: + // ME side only handles refresh commands which meant to remove IDLE + // MODE TEXT. + cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value(); + break; + case SET_UP_IDLE_MODE_TEXT: + sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null); + break; + case PROVIDE_LOCAL_INFORMATION: + sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null); + return; + case LAUNCH_BROWSER: + case SELECT_ITEM: + case GET_INPUT: + case GET_INKEY: + case SEND_DTMF: + case SEND_SMS: + case SEND_SS: + case SEND_USSD: + case PLAY_TONE: + case SET_UP_CALL: + // nothing to do on telephony! + break; + default: + CatLog.d(this, "Unsupported command"); + return; } mCurrntCmd = cmdMsg; Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); @@ -315,6 +314,11 @@ public class CatService extends Handler implements AppInterface { } ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Input cmdInput = null; + if (mCurrntCmd != null) { + cmdInput = mCurrntCmd.geInput(); + } + // command details int tag = ComprehensionTlvTag.COMMAND_DETAILS.value(); if (cmdDet.compRequired) { @@ -327,7 +331,13 @@ public class CatService extends Handler implements AppInterface { buf.write(cmdDet.commandQualifier); // device identities - tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); + // According to TS102.223/TS31.111 section 6.8 Structure of + // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, + // the ME should set the CR(comprehension required) flag to + // comprehension not required.(CR=0)" + // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, + // the CR flag is not set. + tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value(); buf.write(tag); buf.write(0x02); // length buf.write(DEV_ID_TERMINAL); // source device id @@ -348,6 +358,8 @@ public class CatService extends Handler implements AppInterface { // Fill optional data for each corresponding command if (resp != null) { resp.format(buf); + } else { + encodeOptionalTags(cmdDet, resultCode, cmdInput, buf); } byte[] rawData = buf.toByteArray(); @@ -359,6 +371,52 @@ public class CatService extends Handler implements AppInterface { mCmdIf.sendTerminalResponse(hexString, null); } + private void encodeOptionalTags(CommandDetails cmdDet, + ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) { + switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) { + case GET_INKEY: + // ETSI TS 102 384,27.22.4.2.8.4.2. + // If it is a response for GET_INKEY command and the response timeout + // occured, then add DURATION TLV for variable timeout case. + if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) && + (cmdInput != null) && (cmdInput.duration != null)) { + getInKeyResponse(buf, cmdInput); + } + break; + case PROVIDE_LOCAL_INFORMATION: + if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) && + (resultCode.value() == ResultCode.OK.value())) { + getPliResponse(buf); + } + break; + default: + CatLog.d(this, "encodeOptionalTags() Unsupported Cmd:" + cmdDet.typeOfCommand); + break; + } + } + + private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) { + int tag = ComprehensionTlvTag.DURATION.value(); + + buf.write(tag); + buf.write(0x02); // length + buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds) + buf.write(cmdInput.duration.timeInterval); // Time Duration + } + + private void getPliResponse(ByteArrayOutputStream buf) { + + // Locale Language Setting + String lang = SystemProperties.get("persist.sys.language"); + + if (lang != null) { + // tag + int tag = ComprehensionTlvTag.LANGUAGE.value(); + buf.write(tag); + ResponseData.writeLength(buf, lang.length()); + buf.write(lang.getBytes(), 0, lang.length()); + } + } private void sendMenuSelection(int menuId, boolean helpRequired) { diff --git a/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java index edb2dc899d0f..12204a009024 100644 --- a/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java +++ b/telephony/java/com/android/internal/telephony/cat/CommandParamsFactory.java @@ -52,6 +52,9 @@ class CommandParamsFactory extends Handler { static final int REFRESH_NAA_INIT = 0x03; static final int REFRESH_UICC_RESET = 0x04; + // Command Qualifier values for PLI command + static final int LANGUAGE_SETTING = 0x04; + static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller, IccFileHandler fh) { if (sInstance != null) { @@ -112,7 +115,10 @@ class CommandParamsFactory extends Handler { AppInterface.CommandType cmdType = AppInterface.CommandType .fromInt(cmdDet.typeOfCommand); if (cmdType == null) { - sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD); + // This PROACTIVE COMMAND is presently not handled. Hence set + // result code as BEYOND_TERMINAL_CAPABILITY in TR. + mCmdParams = new CommandParams(cmdDet); + sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY); return; } @@ -155,10 +161,13 @@ class CommandParamsFactory extends Handler { case PLAY_TONE: cmdPending = processPlayTone(cmdDet, ctlvs); break; + case PROVIDE_LOCAL_INFORMATION: + cmdPending = processProvideLocalInfo(cmdDet, ctlvs); + break; default: // unsupported proactive commands mCmdParams = new CommandParams(cmdDet); - sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD); + sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY); return; } } catch (ResultException e) { @@ -380,6 +389,12 @@ class CommandParamsFactory extends Handler { iconId = ValueParser.retrieveIconId(ctlv); } + // parse duration + ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs); + if (ctlv != null) { + input.duration = ValueParser.retrieveDuration(ctlv); + } + input.minLen = 1; input.maxLen = 1; @@ -863,4 +878,20 @@ class CommandParamsFactory extends Handler { } return false; } + + private boolean processProvideLocalInfo(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) + throws ResultException { + CatLog.d(this, "process ProvideLocalInfo"); + switch (cmdDet.commandQualifier) { + case LANGUAGE_SETTING: + CatLog.d(this, "PLI [LANGUAGE_SETTING]"); + mCmdParams = new CommandParams(cmdDet); + break; + default: + CatLog.d(this, "PLI[" + cmdDet.commandQualifier + "] Command Not Supported"); + mCmdParams = new CommandParams(cmdDet); + throw new ResultException(ResultCode.BEYOND_TERMINAL_CAPABILITY); + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/cat/Input.java b/telephony/java/com/android/internal/telephony/cat/Input.java index 8bcaab974b95..13a5ad46cb32 100644 --- a/telephony/java/com/android/internal/telephony/cat/Input.java +++ b/telephony/java/com/android/internal/telephony/cat/Input.java @@ -36,6 +36,7 @@ public class Input implements Parcelable { public boolean echo; public boolean yesNo; public boolean helpAvailable; + public Duration duration; Input() { text = ""; @@ -49,6 +50,7 @@ public class Input implements Parcelable { echo = false; yesNo = false; helpAvailable = false; + duration = null; } private Input(Parcel in) { @@ -63,6 +65,7 @@ public class Input implements Parcelable { echo = in.readInt() == 1 ? true : false; yesNo = in.readInt() == 1 ? true : false; helpAvailable = in.readInt() == 1 ? true : false; + duration = in.readParcelable(null); } public int describeContents() { @@ -81,6 +84,7 @@ public class Input implements Parcelable { dest.writeInt(echo ? 1 : 0); dest.writeInt(yesNo ? 1 : 0); dest.writeInt(helpAvailable ? 1 : 0); + dest.writeParcelable(duration, 0); } public static final Parcelable.Creator<Input> CREATOR = new Parcelable.Creator<Input>() { diff --git a/telephony/java/com/android/internal/telephony/cat/ResponseData.java b/telephony/java/com/android/internal/telephony/cat/ResponseData.java index 84c08f8bb506..677d66bb8265 100644 --- a/telephony/java/com/android/internal/telephony/cat/ResponseData.java +++ b/telephony/java/com/android/internal/telephony/cat/ResponseData.java @@ -28,6 +28,16 @@ abstract class ResponseData { * the ByteArrayOutputStream object. */ public abstract void format(ByteArrayOutputStream buf); + + public static void writeLength(ByteArrayOutputStream buf, int length) { + // As per ETSI 102.220 Sec7.1.2, if the total length is greater + // than 0x7F, it should be coded in two bytes and the first byte + // should be 0x81. + if (length > 0x7F) { + buf.write(0x81); + } + buf.write(length); + } } class SelectItemResponseData extends ResponseData { @@ -120,7 +130,7 @@ class GetInkeyInputResponseData extends ResponseData { } // length - one more for data coding scheme. - buf.write(data.length + 1); + writeLength(buf, data.length + 1); // data coding scheme if (mIsUcs2) { diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index 876c223de6a8..2ae5a3c3ce9d 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -974,7 +974,9 @@ public class GSMPhone extends PhoneBase { } public void getCallWaiting(Message onComplete) { - mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_VOICE, onComplete); + //As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service + //class parameter in call waiting interrogation to network + mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_NONE, onComplete); } public void setCallWaiting(boolean enable, Message onComplete) { diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java index e11bd429903e..b35814c0a100 100755 --- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -73,8 +73,6 @@ public class SipPhone extends SipPhoneBase { private static final String LOG_TAG = "SipPhone"; private static final boolean LOCAL_DEBUG = true; - //private List<SipConnection> connections = new ArrayList<SipConnection>(); - // A call that is ringing or (call) waiting private SipCall ringingCall = new SipCall(); private SipCall foregroundCall = new SipCall(); @@ -111,6 +109,10 @@ public class SipPhone extends SipPhoneBase { return mProfile.getProfileName(); } + public String getSipUri() { + return mProfile.getUriString(); + } + public boolean canTake(Object incomingCall) { synchronized (SipPhone.class) { if (!(incomingCall instanceof SipAudioCall)) return false; diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java index c9e9762a213f..611e3ea020ec 100644 --- a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java @@ -16,7 +16,6 @@ package com.android.internal.telephony.sip; -import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneNotifier; import android.content.Context; @@ -26,42 +25,25 @@ import android.util.Log; import java.text.ParseException; /** - * @hide + * {@hide} */ public class SipPhoneFactory { - private static PhoneNotifier sPhoneNotifier = makeDefaultPhoneNotifier(); - private static Context sContext; - - public static void makeDefaultPhones(Context context) { - makeDefaultPhone(context); - } - - public static void makeDefaultPhone(Context context) { - sContext = context; - SipPhoneProxy.getInstance().setPhone( - makePhone("sip:anonymous@localhost")); - } - - public static Phone getDefaultPhone() { - return SipPhoneProxy.getInstance(); - } - - public static SipPhone makePhone(String sipProfileUri) { + /** + * Makes a {@link SipPhone} object. + * @param sipUri the local SIP URI the phone runs on + * @param context {@code Context} needed to create a Phone object + * @param phoneNotifier {@code PhoneNotifier} needed to create a Phone + * object + * @return the {@code SipPhone} object or null if the SIP URI is not valid + */ + public static SipPhone makePhone(String sipUri, Context context, + PhoneNotifier phoneNotifier) { try { - SipProfile profile = new SipProfile.Builder(sipProfileUri).build(); - return new SipPhone(sContext, sPhoneNotifier, profile); + SipProfile profile = new SipProfile.Builder(sipUri).build(); + return new SipPhone(context, phoneNotifier, profile); } catch (ParseException e) { - Log.v("SipPhoneProxy", "setPhone", e); + Log.w("SipPhoneFactory", "makePhone", e); return null; } } - - private static PhoneNotifier makeDefaultPhoneNotifier() { - try { - return new com.android.internal.telephony.SipPhoneNotifier(); - } catch (Error e) { - Log.e("SipPhoneProxy", "makeDefaultPhoneNotifier", e); - throw e; - } - } } diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java deleted file mode 100644 index 8fa29630a5f4..000000000000 --- a/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java +++ /dev/null @@ -1,763 +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 com.android.internal.telephony.gsm.NetworkInfo; -import com.android.internal.telephony.test.SimulatedRadioControl; - -import android.content.Context; -import android.net.NetworkProperties; -import android.os.Handler; -import android.os.Message; -import android.telephony.CellLocation; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; -import android.util.Log; - -import java.util.List; - -/** - * Temporary. Will be removed after integrating with CallManager. - * (TODO) - * @hide - */ -public class SipPhoneProxy implements Phone { - private static final String LOG_TAG = "PHONE"; - - private static SipPhoneProxy sPhoneProxy = new SipPhoneProxy(); - - public static SipPhoneProxy getInstance() { - return sPhoneProxy; - } - - private SipPhone mActivePhone; - private CallProxy mRingingCall = new CallProxy(); - private CallProxy mForegroundCall = new CallProxy(); - private CallProxy mBackgroundCall = new CallProxy(); - - private SipPhoneProxy() { - } - - public void onNewCall(Object call) { - if (mActivePhone.canTake(call)) { - Log.v("SipPhoneProxy", "onNewCall(): call taken: " + call); - } else { - Log.v("SipPhoneProxy", "onNewCall(): call dropped: " + call); - } - } - - public synchronized void setPhone(SipPhone phone) { - if (phone == null) return; - if (mActivePhone != null) phone.migrateFrom(mActivePhone); - mActivePhone = phone; - mForegroundCall.setTarget(phone.getForegroundCall()); - mBackgroundCall.setTarget(phone.getBackgroundCall()); - mRingingCall.setTarget(phone.getRingingCall()); - } - - public synchronized Call getForegroundCall() { - return mForegroundCall; - } - - public synchronized Call getBackgroundCall() { - return mBackgroundCall; - } - - public synchronized Call getRingingCall() { - return mRingingCall; - } - - - public ServiceState getServiceState() { - return mActivePhone.getServiceState(); - } - - public CellLocation getCellLocation() { - return mActivePhone.getCellLocation(); - } - - public DataState getDataConnectionState() { - return mActivePhone.getDataConnectionState(); - } - - public DataState getDataConnectionState(String apnType) { - return mActivePhone.getDataConnectionState(apnType); - } - - public DataActivityState getDataActivityState() { - return mActivePhone.getDataActivityState(); - } - - public Context getContext() { - return mActivePhone.getContext(); - } - - public void disableDnsCheck(boolean b) { - mActivePhone.disableDnsCheck(b); - } - - public boolean isDnsCheckDisabled() { - return mActivePhone.isDnsCheckDisabled(); - } - - public State getState() { - return mActivePhone.getState(); - } - - public String getPhoneName() { - return mActivePhone.getPhoneName(); - } - - public int getPhoneType() { - return mActivePhone.getPhoneType(); - } - - public String[] getActiveApnTypes() { - return mActivePhone.getActiveApnTypes(); - } - - public String getActiveApn() { - return mActivePhone.getActiveApn(); - } - - public SignalStrength getSignalStrength() { - return mActivePhone.getSignalStrength(); - } - - public void registerForUnknownConnection(Handler h, int what, Object obj) { - mActivePhone.registerForUnknownConnection(h, what, obj); - } - - public void unregisterForUnknownConnection(Handler h) { - mActivePhone.unregisterForUnknownConnection(h); - } - - public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) { - mActivePhone.registerForPreciseCallStateChanged(h, what, obj); - } - - public void unregisterForPreciseCallStateChanged(Handler h) { - mActivePhone.unregisterForPreciseCallStateChanged(h); - } - - public void registerForNewRingingConnection(Handler h, int what, Object obj) { - mActivePhone.registerForNewRingingConnection(h, what, obj); - } - - public void unregisterForNewRingingConnection(Handler h) { - mActivePhone.unregisterForNewRingingConnection(h); - } - - public void registerForIncomingRing(Handler h, int what, Object obj) { - mActivePhone.registerForIncomingRing(h, what, obj); - } - - public void unregisterForIncomingRing(Handler h) { - mActivePhone.unregisterForIncomingRing(h); - } - - public void registerForDisconnect(Handler h, int what, Object obj) { - mActivePhone.registerForDisconnect(h, what, obj); - } - - public void unregisterForDisconnect(Handler h) { - mActivePhone.unregisterForDisconnect(h); - } - - public void registerForMmiInitiate(Handler h, int what, Object obj) { - mActivePhone.registerForMmiInitiate(h, what, obj); - } - - public void unregisterForMmiInitiate(Handler h) { - mActivePhone.unregisterForMmiInitiate(h); - } - - public void registerForMmiComplete(Handler h, int what, Object obj) { - mActivePhone.registerForMmiComplete(h, what, obj); - } - - public void unregisterForMmiComplete(Handler h) { - mActivePhone.unregisterForMmiComplete(h); - } - - public List<? extends MmiCode> getPendingMmiCodes() { - return mActivePhone.getPendingMmiCodes(); - } - - public void sendUssdResponse(String ussdMessge) { - mActivePhone.sendUssdResponse(ussdMessge); - } - - public void registerForServiceStateChanged(Handler h, int what, Object obj) { - mActivePhone.registerForServiceStateChanged(h, what, obj); - } - - public void unregisterForServiceStateChanged(Handler h) { - mActivePhone.unregisterForServiceStateChanged(h); - } - - public void registerForSuppServiceNotification(Handler h, int what, Object obj) { - mActivePhone.registerForSuppServiceNotification(h, what, obj); - } - - public void unregisterForSuppServiceNotification(Handler h) { - mActivePhone.unregisterForSuppServiceNotification(h); - } - - public void registerForSuppServiceFailed(Handler h, int what, Object obj) { - mActivePhone.registerForSuppServiceFailed(h, what, obj); - } - - public void unregisterForSuppServiceFailed(Handler h) { - mActivePhone.unregisterForSuppServiceFailed(h); - } - - public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){ - mActivePhone.registerForInCallVoicePrivacyOn(h,what,obj); - } - - public void unregisterForInCallVoicePrivacyOn(Handler h){ - mActivePhone.unregisterForInCallVoicePrivacyOn(h); - } - - public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){ - mActivePhone.registerForInCallVoicePrivacyOff(h,what,obj); - } - - public void unregisterForInCallVoicePrivacyOff(Handler h){ - mActivePhone.unregisterForInCallVoicePrivacyOff(h); - } - - public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) { - mActivePhone.registerForCdmaOtaStatusChange(h,what,obj); - } - - public void unregisterForCdmaOtaStatusChange(Handler h) { - mActivePhone.unregisterForCdmaOtaStatusChange(h); - } - - public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) { - mActivePhone.registerForSubscriptionInfoReady(h, what, obj); - } - - public void unregisterForSubscriptionInfoReady(Handler h) { - mActivePhone.unregisterForSubscriptionInfoReady(h); - } - - public void registerForEcmTimerReset(Handler h, int what, Object obj) { - mActivePhone.registerForEcmTimerReset(h,what,obj); - } - - public void unregisterForEcmTimerReset(Handler h) { - mActivePhone.unregisterForEcmTimerReset(h); - } - - public void registerForRingbackTone(Handler h, int what, Object obj) { - mActivePhone.registerForRingbackTone(h,what,obj); - } - - public void unregisterForRingbackTone(Handler h) { - mActivePhone.unregisterForRingbackTone(h); - } - - public void registerForResendIncallMute(Handler h, int what, Object obj) { - mActivePhone.registerForResendIncallMute(h,what,obj); - } - - public void unregisterForResendIncallMute(Handler h) { - mActivePhone.unregisterForResendIncallMute(h); - } - - public boolean getIccRecordsLoaded() { - return mActivePhone.getIccRecordsLoaded(); - } - - public IccCard getIccCard() { - return mActivePhone.getIccCard(); - } - - public void acceptCall() throws CallStateException { - mActivePhone.acceptCall(); - } - - public void rejectCall() throws CallStateException { - mActivePhone.rejectCall(); - } - - public void switchHoldingAndActive() throws CallStateException { - mActivePhone.switchHoldingAndActive(); - } - - public boolean canConference() { - return mActivePhone.canConference(); - } - - public void conference() throws CallStateException { - mActivePhone.conference(); - } - - public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) { - mActivePhone.enableEnhancedVoicePrivacy(enable, onComplete); - } - - public void getEnhancedVoicePrivacy(Message onComplete) { - mActivePhone.getEnhancedVoicePrivacy(onComplete); - } - - public boolean canTransfer() { - return mActivePhone.canTransfer(); - } - - public void explicitCallTransfer() throws CallStateException { - mActivePhone.explicitCallTransfer(); - } - - public void clearDisconnected() { - mActivePhone.clearDisconnected(); - } - - public Connection dial(String dialString) throws CallStateException { - return mActivePhone.dial(dialString); - } - - public boolean handlePinMmi(String dialString) { - return mActivePhone.handlePinMmi(dialString); - } - - public boolean handleInCallMmiCommands(String command) throws CallStateException { - return mActivePhone.handleInCallMmiCommands(command); - } - - public void sendDtmf(char c) { - mActivePhone.sendDtmf(c); - } - - public void startDtmf(char c) { - mActivePhone.startDtmf(c); - } - - public void stopDtmf() { - mActivePhone.stopDtmf(); - } - - public void setRadioPower(boolean power) { - mActivePhone.setRadioPower(power); - } - - public boolean getMessageWaitingIndicator() { - return mActivePhone.getMessageWaitingIndicator(); - } - - public boolean getCallForwardingIndicator() { - return mActivePhone.getCallForwardingIndicator(); - } - - public String getLine1Number() { - return mActivePhone.getLine1Number(); - } - - public String getCdmaMin() { - return mActivePhone.getCdmaMin(); - } - - public boolean isMinInfoReady() { - return mActivePhone.isMinInfoReady(); - } - - public String getCdmaPrlVersion() { - return mActivePhone.getCdmaPrlVersion(); - } - - public String getLine1AlphaTag() { - return mActivePhone.getLine1AlphaTag(); - } - - public void setLine1Number(String alphaTag, String number, Message onComplete) { - mActivePhone.setLine1Number(alphaTag, number, onComplete); - } - - public String getVoiceMailNumber() { - return mActivePhone.getVoiceMailNumber(); - } - - /** @hide */ - public int getVoiceMessageCount(){ - return mActivePhone.getVoiceMessageCount(); - } - - public String getVoiceMailAlphaTag() { - return mActivePhone.getVoiceMailAlphaTag(); - } - - public void setVoiceMailNumber(String alphaTag,String voiceMailNumber, - Message onComplete) { - mActivePhone.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete); - } - - public void getCallForwardingOption(int commandInterfaceCFReason, - Message onComplete) { - mActivePhone.getCallForwardingOption(commandInterfaceCFReason, - onComplete); - } - - public void setCallForwardingOption(int commandInterfaceCFReason, - int commandInterfaceCFAction, String dialingNumber, - int timerSeconds, Message onComplete) { - mActivePhone.setCallForwardingOption(commandInterfaceCFReason, - commandInterfaceCFAction, dialingNumber, timerSeconds, onComplete); - } - - public void getOutgoingCallerIdDisplay(Message onComplete) { - mActivePhone.getOutgoingCallerIdDisplay(onComplete); - } - - public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, - Message onComplete) { - mActivePhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode, - onComplete); - } - - public void getCallWaiting(Message onComplete) { - mActivePhone.getCallWaiting(onComplete); - } - - public void setCallWaiting(boolean enable, Message onComplete) { - mActivePhone.setCallWaiting(enable, onComplete); - } - - public void getAvailableNetworks(Message response) { - mActivePhone.getAvailableNetworks(response); - } - - public void setNetworkSelectionModeAutomatic(Message response) { - mActivePhone.setNetworkSelectionModeAutomatic(response); - } - - public void selectNetworkManually(NetworkInfo network, Message response) { - mActivePhone.selectNetworkManually(network, response); - } - - public void setPreferredNetworkType(int networkType, Message response) { - mActivePhone.setPreferredNetworkType(networkType, response); - } - - public void getPreferredNetworkType(Message response) { - mActivePhone.getPreferredNetworkType(response); - } - - public void getNeighboringCids(Message response) { - mActivePhone.getNeighboringCids(response); - } - - public void setOnPostDialCharacter(Handler h, int what, Object obj) { - mActivePhone.setOnPostDialCharacter(h, what, obj); - } - - public void setMute(boolean muted) { - mActivePhone.setMute(muted); - } - - public boolean getMute() { - return mActivePhone.getMute(); - } - - public void invokeOemRilRequestRaw(byte[] data, Message response) { - mActivePhone.invokeOemRilRequestRaw(data, response); - } - - public void invokeOemRilRequestStrings(String[] strings, Message response) { - mActivePhone.invokeOemRilRequestStrings(strings, response); - } - - public void getDataCallList(Message response) { - mActivePhone.getDataCallList(response); - } - - public List<DataConnection> getCurrentDataConnectionList() { - return mActivePhone.getCurrentDataConnectionList(); - } - - public void updateServiceLocation() { - mActivePhone.updateServiceLocation(); - } - - public void enableLocationUpdates() { - mActivePhone.enableLocationUpdates(); - } - - public void disableLocationUpdates() { - mActivePhone.disableLocationUpdates(); - } - - public void setUnitTestMode(boolean f) { - mActivePhone.setUnitTestMode(f); - } - - public boolean getUnitTestMode() { - return mActivePhone.getUnitTestMode(); - } - - public void setBandMode(int bandMode, Message response) { - mActivePhone.setBandMode(bandMode, response); - } - - public void queryAvailableBandMode(Message response) { - mActivePhone.queryAvailableBandMode(response); - } - - public boolean getDataRoamingEnabled() { - return mActivePhone.getDataRoamingEnabled(); - } - - public void setDataRoamingEnabled(boolean enable) { - mActivePhone.setDataRoamingEnabled(enable); - } - - public void queryCdmaRoamingPreference(Message response) { - mActivePhone.queryCdmaRoamingPreference(response); - } - - public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) { - mActivePhone.setCdmaRoamingPreference(cdmaRoamingType, response); - } - - public void setCdmaSubscription(int cdmaSubscriptionType, Message response) { - mActivePhone.setCdmaSubscription(cdmaSubscriptionType, response); - } - - public SimulatedRadioControl getSimulatedRadioControl() { - return mActivePhone.getSimulatedRadioControl(); - } - - public boolean enableDataConnectivity() { - return mActivePhone.enableDataConnectivity(); - } - - public boolean disableDataConnectivity() { - return mActivePhone.disableDataConnectivity(); - } - - public int enableApnType(String type) { - return mActivePhone.enableApnType(type); - } - - public int disableApnType(String type) { - return mActivePhone.disableApnType(type); - } - - public boolean isDataConnectivityEnabled() { - return mActivePhone.isDataConnectivityEnabled(); - } - - public boolean isDataConnectivityPossible() { - return mActivePhone.isDataConnectivityPossible(); - } - - public String getInterfaceName(String apnType) { - return mActivePhone.getInterfaceName(apnType); - } - - public String getIpAddress(String apnType) { - return mActivePhone.getIpAddress(apnType); - } - - public String getGateway(String apnType) { - return mActivePhone.getGateway(apnType); - } - - public String[] getDnsServers(String apnType) { - return mActivePhone.getDnsServers(apnType); - } - - public String getDeviceId() { - return mActivePhone.getDeviceId(); - } - - public String getDeviceSvn() { - return mActivePhone.getDeviceSvn(); - } - - public String getSubscriberId() { - return mActivePhone.getSubscriberId(); - } - - public String getIccSerialNumber() { - return mActivePhone.getIccSerialNumber(); - } - - public String getEsn() { - return mActivePhone.getEsn(); - } - - public String getMeid() { - return mActivePhone.getMeid(); - } - - public PhoneSubInfo getPhoneSubInfo(){ - return mActivePhone.getPhoneSubInfo(); - } - - public IccSmsInterfaceManager getIccSmsInterfaceManager(){ - return mActivePhone.getIccSmsInterfaceManager(); - } - - public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){ - return mActivePhone.getIccPhoneBookInterfaceManager(); - } - - public void setTTYMode(int ttyMode, Message onComplete) { - mActivePhone.setTTYMode(ttyMode, onComplete); - } - - public void queryTTYMode(Message onComplete) { - mActivePhone.queryTTYMode(onComplete); - } - - public void activateCellBroadcastSms(int activate, Message response) { - mActivePhone.activateCellBroadcastSms(activate, response); - } - - public void getCellBroadcastSmsConfig(Message response) { - mActivePhone.getCellBroadcastSmsConfig(response); - } - - public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) { - mActivePhone.setCellBroadcastSmsConfig(configValuesArray, response); - } - - public void notifyDataActivity() { - mActivePhone.notifyDataActivity(); - } - - public void getSmscAddress(Message result) { - mActivePhone.getSmscAddress(result); - } - - public void setSmscAddress(String address, Message result) { - mActivePhone.setSmscAddress(address, result); - } - - public int getCdmaEriIconIndex() { - return mActivePhone.getCdmaEriIconIndex(); - } - - public String getCdmaEriText() { - return mActivePhone.getCdmaEriText(); - } - - public int getCdmaEriIconMode() { - return mActivePhone.getCdmaEriIconMode(); - } - - public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete){ - mActivePhone.sendBurstDtmf(dtmfString, on, off, onComplete); - } - - public void exitEmergencyCallbackMode(){ - mActivePhone.exitEmergencyCallbackMode(); - } - - public boolean isOtaSpNumber(String dialStr){ - return mActivePhone.isOtaSpNumber(dialStr); - } - - public void registerForCallWaiting(Handler h, int what, Object obj){ - mActivePhone.registerForCallWaiting(h,what,obj); - } - - public void unregisterForCallWaiting(Handler h){ - mActivePhone.unregisterForCallWaiting(h); - } - - public void registerForSignalInfo(Handler h, int what, Object obj) { - mActivePhone.registerForSignalInfo(h,what,obj); - } - - public void unregisterForSignalInfo(Handler h) { - mActivePhone.unregisterForSignalInfo(h); - } - - public void registerForDisplayInfo(Handler h, int what, Object obj) { - mActivePhone.registerForDisplayInfo(h,what,obj); - } - - public void unregisterForDisplayInfo(Handler h) { - mActivePhone.unregisterForDisplayInfo(h); - } - - public void registerForNumberInfo(Handler h, int what, Object obj) { - mActivePhone.registerForNumberInfo(h, what, obj); - } - - public void unregisterForNumberInfo(Handler h) { - mActivePhone.unregisterForNumberInfo(h); - } - - public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) { - mActivePhone.registerForRedirectedNumberInfo(h, what, obj); - } - - public void unregisterForRedirectedNumberInfo(Handler h) { - mActivePhone.unregisterForRedirectedNumberInfo(h); - } - - public void registerForLineControlInfo(Handler h, int what, Object obj) { - mActivePhone.registerForLineControlInfo( h, what, obj); - } - - public void unregisterForLineControlInfo(Handler h) { - mActivePhone.unregisterForLineControlInfo(h); - } - - public void registerFoT53ClirlInfo(Handler h, int what, Object obj) { - mActivePhone.registerFoT53ClirlInfo(h, what, obj); - } - - public void unregisterForT53ClirInfo(Handler h) { - mActivePhone.unregisterForT53ClirInfo(h); - } - - public void registerForT53AudioControlInfo(Handler h, int what, Object obj) { - mActivePhone.registerForT53AudioControlInfo( h, what, obj); - } - - public void unregisterForT53AudioControlInfo(Handler h) { - mActivePhone.unregisterForT53AudioControlInfo(h); - } - - public void setOnEcbModeExitResponse(Handler h, int what, Object obj){ - mActivePhone.setOnEcbModeExitResponse(h,what,obj); - } - - public void unsetOnEcbModeExitResponse(Handler h){ - mActivePhone.unsetOnEcbModeExitResponse(h); - } - - public Connection dial(String dialString, UUSInfo uusInfo) - throws CallStateException { - return mActivePhone.dial(dialString, uusInfo); - } - - public boolean needsOtaServiceProvisioning() { - return mActivePhone.needsOtaServiceProvisioning(); - } - - public NetworkProperties getNetworkProperties(String apnType) { - return mActivePhone.getNetworkProperties(apnType); - } -} diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java index 3fd71c8426d0..b63ff3dca8df 100644 --- a/test-runner/src/android/test/mock/MockContentProvider.java +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -127,6 +127,16 @@ public class MockContentProvider extends ContentProvider { throw new UnsupportedOperationException(); } + @SuppressWarnings("unused") + public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { + return MockContentProvider.this.getStreamTypes(url, mimeTypeFilter); + } + + @SuppressWarnings("unused") + public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) + throws RemoteException, FileNotFoundException { + return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts); + } } private final InversionIContentProvider mIContentProvider = new InversionIContentProvider(); @@ -222,6 +232,14 @@ public class MockContentProvider extends ContentProvider { throw new UnsupportedOperationException("unimplemented mock method call"); } + public String[] getStreamTypes(Uri url, String mimeTypeFilter) { + throw new UnsupportedOperationException("unimplemented mock method call"); + } + + public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) { + throw new UnsupportedOperationException("unimplemented mock method call"); + } + /** * Returns IContentProvider which calls back same methods in this class. * By overriding this class, we avoid the mechanism hidden behind ContentProvider diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java index 0be5bea276f3..183be415880c 100644 --- a/test-runner/src/android/test/mock/MockIContentProvider.java +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -32,6 +32,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import java.io.FileNotFoundException; import java.util.ArrayList; /** @@ -102,4 +103,13 @@ public class MockIContentProvider implements IContentProvider { public IBinder asBinder() { throw new UnsupportedOperationException("unimplemented mock method"); } + + public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + + public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) + throws RemoteException, FileNotFoundException { + throw new UnsupportedOperationException("unimplemented mock method"); + } } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java index b62db513c185..740f544c4d97 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java @@ -510,6 +510,14 @@ public class CallbackProxy extends Handler implements EventSender, LayoutTestCon obtainMessage(SET_GEOLOCATION_PERMISSION, allow ? 1 : 0, 0).sendToTarget(); } + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + // Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity + // as we need access to the Webview. + mLayoutTestController.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } + public void overridePreference(String key, boolean value) { Message message = obtainMessage(OVERRIDE_PREFERENCE); message.getData().putString("key", key); diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java index 15288b5ce725..9be2f1c917a5 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java @@ -71,4 +71,8 @@ public interface LayoutTestController { // For XSSAuditor tests public void setXSSAuditorEnabled(boolean flag); + + // For DeviceOrientation tests + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma); } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 8c7254c4c15b..db076daaa1a1 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -150,6 +150,9 @@ public class TestShellActivity extends Activity implements LayoutTestController if (intent != null) { executeIntent(intent); } + + // This is asynchronous, but it gets processed by WebCore before it starts loading pages. + mWebView.useMockDeviceOrientation(); } @Override @@ -494,6 +497,12 @@ public class TestShellActivity extends Activity implements LayoutTestController mGeolocationPermission = allow; } + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } + public void overridePreference(String key, boolean value) { // TODO: We should look up the correct WebView for the frame which // called the layoutTestController method. Currently, we just use the diff --git a/tests/DumpRenderTree2/Android.mk b/tests/DumpRenderTree2/Android.mk index 948ad72cd5df..eddbb4bf7b76 100644 --- a/tests/DumpRenderTree2/Android.mk +++ b/tests/DumpRenderTree2/Android.mk @@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_JAVA_LIBRARIES := android.test.runner + LOCAL_STATIC_JAVA_LIBRARIES := diff_match_patch LOCAL_PACKAGE_NAME := DumpRenderTree2 diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml index 9f6097a960ac..dd0c4e9f416a 100644 --- a/tests/DumpRenderTree2/AndroidManifest.xml +++ b/tests/DumpRenderTree2/AndroidManifest.xml @@ -16,6 +16,8 @@ limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree2"> <application> + <uses-library android:name="android.test.runner" /> + <activity android:name=".ui.DirListActivity" android:label="Dump Render Tree 2" android:configChanges="orientation"> @@ -25,8 +27,15 @@ limitations under the License. </intent-filter> </activity> + <!-- android:launchMode="singleTask" is there so we only have a one instance + of this activity. However, it doesn't seem to work exactly like described in the + documentation, because the behaviour of the application suggest + there is only a single task for all 3 activities. We don't understand + how exactly it all works, but at the moment it works just fine. + It can lead to some weird behaviour in the future. --> <activity android:name=".TestsListActivity" - android:label="Tests' list activity"> + android:label="Tests' list activity" + android:launchMode="singleTask"> </activity> <activity android:name=".LayoutTestsExecutor" @@ -38,6 +47,10 @@ limitations under the License. </service> </application> + <instrumentation android:name="com.android.dumprendertree2.scriptsupport.ScriptTestRunner" + android:targetPackage="com.android.dumprendertree2" + android:label="Layout tests script runner" /> + <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_SDCARD" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py new file mode 100644 index 000000000000..b13d8c9f3d38 --- /dev/null +++ b/tests/DumpRenderTree2/assets/run_layout_tests.py @@ -0,0 +1,65 @@ +#!/usr/bin/python + +"""Run layout tests on the device. + + It runs the specified tests on the device, downloads the summaries to the temporary directory + and opens html details in the default browser. + + Usage: + run_layout_tests.py PATH +""" + +import sys +import os +import subprocess +import logging +import webbrowser +import tempfile + +#TODO: These should not be hardcoded +RESULTS_ABSOLUTE_PATH = "/sdcard/android/LayoutTests-results/" +DETAILS_HTML = "details.html" +SUMMARY_TXT = "summary.txt" + +def main(): + if len(sys.argv) > 1: + path = sys.argv[1] + else: + path = "" + + logging.basicConfig(level=logging.INFO, format='%(message)s') + + tmpdir = tempfile.gettempdir() + + # Run the tests in path + cmd = "adb shell am instrument " + cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests " + cmd += "-e path \"" + path + "\" " + cmd +="-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner" + + logging.info("Running the tests...") + subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + + logging.info("Downloading the summaries...") + + # Download the txt summary to tmp folder + summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT) + cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path + subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + + # Download the html summary to tmp folder + details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML) + cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path + subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + + # Print summary to console + logging.info("All done.\n") + cmd = "cat " + summary_txt_tmp_path + os.system(cmd) + logging.info("") + + # Open the browser with summary + webbrowser.open(details_html_tmp_path) + +if __name__ == "__main__": + main(); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java new file mode 100644 index 000000000000..a793dab3aa95 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/CrashedDummyResult.java @@ -0,0 +1,85 @@ +/* + * 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.dumprendertree2; + +import android.os.Bundle; +import android.os.Message; +import android.webkit.WebView; + +/** + * A dummy class representing test that crashed. + */ +public class CrashedDummyResult extends AbstractResult { + String mRelativePath; + + public CrashedDummyResult(String relativePath) { + mRelativePath = relativePath; + } + + @Override + public byte[] getActualImageResult() { + return null; + } + + @Override + public String getActualTextResult() { + return null; + } + + @Override + public Bundle getBundle() { + /** TODO: */ + return null; + } + + @Override + public String getDiffAsHtml() { + /** TODO: Probably show at least expected results */ + return "Ooops, I crashed..."; + } + + @Override + public String getRelativePath() { + return mRelativePath; + } + + @Override + public ResultCode getResultCode() { + return ResultCode.FAIL_CRASHED; + } + + @Override + public TestType getType() { + return null; + } + + @Override + public void obtainActualResults(WebView webview, Message resultObtainedMsg) { + /** This method is not applicable for this type of result */ + assert false; + } + + @Override + public void setExpectedImageResult(byte[] expectedResult) { + /** TODO */ + } + + @Override + public void setExpectedTextResult(String expectedResult) { + /** TODO */ + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java new file mode 100644 index 000000000000..5b7cbc4fe83a --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java @@ -0,0 +1,110 @@ +/* + * 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.dumprendertree2; + +import android.webkit.WebView; + +/** + * A class that acts as a JS interface for webview to mock various touch events, + * mouse actions and key presses. + * + * The methods here just call corresponding methods on EventSenderImpl + * that contains the logic of how to execute the methods. + */ +public class EventSender { + EventSenderImpl mEventSenderImpl = new EventSenderImpl(); + + public void reset(WebView webView) { + mEventSenderImpl.reset(webView); + } + + public void enableDOMUIEventLogging(int domNode) { + mEventSenderImpl.enableDOMUIEventLogging(domNode); + } + + public void fireKeyboardEventsToElement(int domNode) { + mEventSenderImpl.fireKeyboardEventsToElement(domNode); + } + + public void keyDown(String character, String[] withModifiers) { + mEventSenderImpl.keyDown(character, withModifiers); + } + + public void keyDown(String character) { + keyDown(character, null); + } + + public void leapForward(int milliseconds) { + mEventSenderImpl.leapForward(milliseconds); + } + + public void mouseClick() { + mEventSenderImpl.mouseClick(); + } + + public void mouseDown() { + mEventSenderImpl.mouseDown(); + } + + public void mouseMoveTo(int x, int y) { + mEventSenderImpl.mouseMoveTo(x, y); + } + + public void mouseUp() { + mEventSenderImpl.mouseUp(); + } + + public void touchStart() { + mEventSenderImpl.touchStart(); + } + + public void addTouchPoint(int x, int y) { + mEventSenderImpl.addTouchPoint(x, y); + } + + public void updateTouchPoint(int id, int x, int y) { + mEventSenderImpl.updateTouchPoint(id, x, y); + } + + public void setTouchModifier(String modifier, boolean enabled) { + mEventSenderImpl.setTouchModifier(modifier, enabled); + } + + public void touchMove() { + mEventSenderImpl.touchMove(); + } + + public void releaseTouchPoint(int id) { + mEventSenderImpl.releaseTouchPoint(id); + } + + public void touchEnd() { + mEventSenderImpl.touchEnd(); + } + + public void touchCancel() { + mEventSenderImpl.touchCancel(); + } + + public void clearTouchPoints() { + mEventSenderImpl.clearTouchPoints(); + } + + public void cancelTouchPoint(int id) { + mEventSenderImpl.cancelTouchPoint(id); + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java new file mode 100644 index 000000000000..93e61378b071 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java @@ -0,0 +1,563 @@ +/* + * 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.dumprendertree2; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.webkit.WebView; + +import java.util.LinkedList; +import java.util.List; + +/** + * An implementation of EventSender + */ +public class EventSenderImpl { + private static final String LOG_TAG = "EventSenderImpl"; + + private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0; + private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1; + private static final int MSG_LEAP_FORWARD = 2; + + private static final int MSG_KEY_DOWN = 3; + + private static final int MSG_MOUSE_DOWN = 4; + private static final int MSG_MOUSE_UP = 5; + private static final int MSG_MOUSE_CLICK = 6; + private static final int MSG_MOUSE_MOVE_TO = 7; + + private static final int MSG_ADD_TOUCH_POINT = 8; + private static final int MSG_TOUCH_START = 9; + private static final int MSG_UPDATE_TOUCH_POINT = 10; + private static final int MSG_TOUCH_MOVE = 11; + private static final int MSG_CLEAR_TOUCH_POINTS = 12; + private static final int MSG_TOUCH_CANCEL = 13; + private static final int MSG_RELEASE_TOUCH_POINT = 14; + private static final int MSG_TOUCH_END = 15; + private static final int MSG_SET_TOUCH_MODIFIER = 16; + private static final int MSG_CANCEL_TOUCH_POINT = 17; + + public static class TouchPoint { + WebView mWebView; + private int mX; + private int mY; + private long mDownTime; + private boolean mReleased = false; + private boolean mMoved = false; + private boolean mCancelled = false; + + public TouchPoint(WebView webView, int x, int y) { + mWebView = webView; + mX = scaleX(x); + mY = scaleY(y); + } + + public int getX() { + return mX; + } + + public int getY() { + return mY; + } + + public boolean hasMoved() { + return mMoved; + } + + public void move(int newX, int newY) { + mX = scaleX(newX); + mY = scaleY(newY); + mMoved = true; + } + + public void resetHasMoved() { + mMoved = false; + } + + public long getDownTime() { + return mDownTime; + } + + public void setDownTime(long downTime) { + mDownTime = downTime; + } + + public boolean isReleased() { + return mReleased; + } + + public void release() { + mReleased = true; + } + + public boolean isCancelled() { + return mCancelled; + } + + public void cancel() { + mCancelled = true; + } + + private int scaleX(int x) { + return (int)(x * mWebView.getScale()) - mWebView.getScrollX(); + } + + private int scaleY(int y) { + return (int)(y * mWebView.getScale()) - mWebView.getScrollY(); + } + } + + private List<TouchPoint> mTouchPoints; + private int mTouchMetaState; + private int mMouseX; + private int mMouseY; + + private WebView mWebView; + + private Handler mEventSenderHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + TouchPoint touchPoint; + Bundle bundle; + KeyEvent event; + + switch (msg.what) { + case MSG_ENABLE_DOM_UI_EVENT_LOGGING: + /** TODO: implement */ + break; + + case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT: + /** TODO: implement */ + break; + + case MSG_LEAP_FORWARD: + /** TODO: implement */ + break; + + case MSG_KEY_DOWN: + bundle = (Bundle)msg.obj; + String character = bundle.getString("character"); + String[] withModifiers = bundle.getStringArray("withModifiers"); + + if (withModifiers != null && withModifiers.length > 0) { + for (int i = 0; i < withModifiers.length; i++) { + executeKeyEvent(KeyEvent.ACTION_DOWN, + modifierToKeyCode(withModifiers[i])); + } + } + executeKeyEvent(KeyEvent.ACTION_DOWN, + charToKeyCode(character.toLowerCase().toCharArray()[0])); + break; + + /** MOUSE */ + + case MSG_MOUSE_DOWN: + /** TODO: Implement */ + break; + + case MSG_MOUSE_UP: + /** TODO: Implement */ + break; + + case MSG_MOUSE_CLICK: + /** TODO: Implement */ + break; + + case MSG_MOUSE_MOVE_TO: + int x = msg.arg1; + int y = msg.arg2; + + event = null; + if (x > mMouseX) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); + } else if (x < mMouseX) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); + } + if (event != null) { + mWebView.onKeyDown(event.getKeyCode(), event); + mWebView.onKeyUp(event.getKeyCode(), event); + } + + event = null; + if (y > mMouseY) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN); + } else if (y < mMouseY) { + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); + } + if (event != null) { + mWebView.onKeyDown(event.getKeyCode(), event); + mWebView.onKeyUp(event.getKeyCode(), event); + } + + mMouseX = x; + mMouseY = y; + break; + + /** TOUCH */ + + case MSG_ADD_TOUCH_POINT: + getTouchPoints().add(new TouchPoint(mWebView, + msg.arg1, msg.arg2)); + if (getTouchPoints().size() > 1) { + Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point"); + } + break; + + case MSG_TOUCH_START: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + touchPoint.setDownTime(SystemClock.uptimeMillis()); + executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN); + break; + + case MSG_UPDATE_TOUCH_POINT: + bundle = (Bundle)msg.obj; + + int id = bundle.getInt("id"); + if (id >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: " + + id); + break; + } + + getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y")); + break; + + case MSG_TOUCH_MOVE: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + if (!touchPoint.hasMoved()) { + return; + } + executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE); + touchPoint.resetHasMoved(); + break; + + case MSG_CANCEL_TOUCH_POINT: + if (msg.arg1 >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " + + msg.arg1); + break; + } + + getTouchPoints().get(msg.arg1).cancel(); + break; + + case MSG_TOUCH_CANCEL: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + if (touchPoint.isCancelled()) { + executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL); + } + break; + + case MSG_RELEASE_TOUCH_POINT: + if (msg.arg1 >= getTouchPoints().size()) { + Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: " + + msg.arg1); + break; + } + + getTouchPoints().get(msg.arg1).release(); + break; + + case MSG_TOUCH_END: + /** + * FIXME: At the moment we don't support multi-touch. Hence, we only examine + * the first touch point. In future this method will need rewriting. + */ + if (getTouchPoints().isEmpty()) { + return; + } + touchPoint = getTouchPoints().get(0); + + executeTouchEvent(touchPoint, MotionEvent.ACTION_UP); + if (touchPoint.isReleased()) { + getTouchPoints().remove(0); + touchPoint = null; + } + break; + + case MSG_SET_TOUCH_MODIFIER: + bundle = (Bundle)msg.obj; + String modifier = bundle.getString("modifier"); + boolean enabled = bundle.getBoolean("enabled"); + + int mask = 0; + if ("alt".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_ALT_ON; + } else if ("shift".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_SHIFT_ON; + } else if ("ctrl".equals(modifier.toLowerCase())) { + mask = KeyEvent.META_SYM_ON; + } + + if (enabled) { + mTouchMetaState |= mask; + } else { + mTouchMetaState &= ~mask; + } + + break; + + case MSG_CLEAR_TOUCH_POINTS: + getTouchPoints().clear(); + break; + + default: + break; + } + } + }; + + public void reset(WebView webView) { + mWebView = webView; + mTouchPoints = null; + mTouchMetaState = 0; + mMouseX = 0; + mMouseY = 0; + } + + public void enableDOMUIEventLogging(int domNode) { + Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING); + msg.arg1 = domNode; + msg.sendToTarget(); + } + + public void fireKeyboardEventsToElement(int domNode) { + Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT); + msg.arg1 = domNode; + msg.sendToTarget(); + } + + public void leapForward(int milliseconds) { + Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD); + msg.arg1 = milliseconds; + msg.sendToTarget(); + } + + public void keyDown(String character, String[] withModifiers) { + Bundle bundle = new Bundle(); + bundle.putString("character", character); + bundle.putStringArray("withModifiers", withModifiers); + mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget(); + } + + /** MOUSE */ + + public void mouseDown() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN); + } + + public void mouseUp() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP); + } + + public void mouseClick() { + mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK); + } + + public void mouseMoveTo(int x, int y) { + mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget(); + } + + /** TOUCH */ + + public void addTouchPoint(int x, int y) { + mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget(); + } + + public void touchStart() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START); + } + + public void updateTouchPoint(int id, int x, int y) { + Bundle bundle = new Bundle(); + bundle.putInt("id", id); + bundle.putInt("x", x); + bundle.putInt("y", y); + mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget(); + } + + public void touchMove() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE); + } + + public void cancelTouchPoint(int id) { + Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT); + msg.arg1 = id; + msg.sendToTarget(); + } + + public void touchCancel() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL); + } + + public void releaseTouchPoint(int id) { + Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT); + msg.arg1 = id; + msg.sendToTarget(); + } + + public void touchEnd() { + mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END); + } + + public void setTouchModifier(String modifier, boolean enabled) { + Bundle bundle = new Bundle(); + bundle.putString("modifier", modifier); + bundle.putBoolean("enabled", enabled); + mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget(); + } + + public void clearTouchPoints() { + mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS); + } + + private List<TouchPoint> getTouchPoints() { + if (mTouchPoints == null) { + mTouchPoints = new LinkedList<TouchPoint>(); + } + + return mTouchPoints; + } + + private void executeTouchEvent(TouchPoint touchPoint, int action) { + MotionEvent event = + MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(), + action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState); + mWebView.onTouchEvent(event); + } + + private void executeKeyEvent(int action, int keyCode) { + KeyEvent event = new KeyEvent(action, keyCode); + mWebView.onKeyDown(event.getKeyCode(), event); + } + + /** + * Assumes lowercase chars, case needs to be handled by calling function. + */ + private static int charToKeyCode(char c) { + // handle numbers + if (c >= '0' && c <= '9') { + int offset = c - '0'; + return KeyEvent.KEYCODE_0 + offset; + } + + // handle characters + if (c >= 'a' && c <= 'z') { + int offset = c - 'a'; + return KeyEvent.KEYCODE_A + offset; + } + + // handle all others + switch (c) { + case '*': + return KeyEvent.KEYCODE_STAR; + + case '#': + return KeyEvent.KEYCODE_POUND; + + case ',': + return KeyEvent.KEYCODE_COMMA; + + case '.': + return KeyEvent.KEYCODE_PERIOD; + + case '\t': + return KeyEvent.KEYCODE_TAB; + + case ' ': + return KeyEvent.KEYCODE_SPACE; + + case '\n': + return KeyEvent.KEYCODE_ENTER; + + case '\b': + case 0x7F: + return KeyEvent.KEYCODE_DEL; + + case '~': + return KeyEvent.KEYCODE_GRAVE; + + case '-': + return KeyEvent.KEYCODE_MINUS; + + case '=': + return KeyEvent.KEYCODE_EQUALS; + + case '(': + return KeyEvent.KEYCODE_LEFT_BRACKET; + + case ')': + return KeyEvent.KEYCODE_RIGHT_BRACKET; + + case '\\': + return KeyEvent.KEYCODE_BACKSLASH; + + case ';': + return KeyEvent.KEYCODE_SEMICOLON; + + case '\'': + return KeyEvent.KEYCODE_APOSTROPHE; + + case '/': + return KeyEvent.KEYCODE_SLASH; + + default: + return c; + } + } + + private static int modifierToKeyCode(String modifier) { + if (modifier.equals("ctrlKey")) { + return KeyEvent.KEYCODE_ALT_LEFT; + } else if (modifier.equals("shiftKey")) { + return KeyEvent.KEYCODE_SHIFT_LEFT; + } else if (modifier.equals("altKey")) { + return KeyEvent.KEYCODE_SYM; + } + + return KeyEvent.KEYCODE_UNKNOWN; + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java index 8ff5e630d01f..6db957128560 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java @@ -89,4 +89,12 @@ public class LayoutTestController { Log.w(LOG_TAG + "::setMockGeolocationError", "code: " + code + " message: " + message); MockGeolocation.getInstance().setError(code, message); } -}
\ No newline at end of file + + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + // Configuration is in WebKit, so stay on WebCore thread, but go via LayoutTestsExecutor + // as we need access to the Webview. + mLayoutTestsExecutor.setMockDeviceOrientation( + canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma); + } +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java index 063cb7303c04..8cc4921d9055 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java @@ -82,6 +82,7 @@ public class LayoutTestsExecutor extends Activity { private static final int DEFAULT_TIME_OUT_MS = 15 * 1000; + /** A list of tests that remain to run since last crash */ private List<String> mTestsList; /** @@ -91,6 +92,7 @@ public class LayoutTestsExecutor extends Activity { */ private int mCurrentTestIndex; + /** The total number of tests to run, doesn't reset after crash */ private int mTotalTestCount; private WebView mCurrentWebView; @@ -108,6 +110,8 @@ public class LayoutTestsExecutor extends Activity { private boolean mSetGeolocationPermissionCalled; private boolean mGeolocationPermission; + private EventSender mEventSender = new EventSender(); + private WakeLock mScreenDimLock; /** COMMUNICATION WITH ManagerService */ @@ -119,7 +123,7 @@ public class LayoutTestsExecutor extends Activity { @Override public void onServiceConnected(ComponentName name, IBinder service) { mManagerServiceMessenger = new Messenger(service); - runNextTest(); + startTests(); } @Override @@ -246,7 +250,7 @@ public class LayoutTestsExecutor extends Activity { mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1); mTotalTestCount = mCurrentTestIndex + mTestsList.size(); - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester"); mScreenDimLock.acquire(); @@ -267,6 +271,8 @@ public class LayoutTestsExecutor extends Activity { mCurrentWebView = new WebView(this); setupWebView(mCurrentWebView); + mEventSender.reset(mCurrentWebView); + setContentView(mCurrentWebView); if (previousWebView != null) { Log.d(LOG_TAG + "::reset", "previousWebView != null"); @@ -278,6 +284,7 @@ public class LayoutTestsExecutor extends Activity { webView.setWebViewClient(mWebViewClient); webView.setWebChromeClient(mWebChromeClient); webView.addJavascriptInterface(mLayoutTestController, "layoutTestController"); + webView.addJavascriptInterface(mEventSender, "eventSender"); /** * Setting a touch interval of -1 effectively disables the optimisation in WebView @@ -301,6 +308,29 @@ public class LayoutTestsExecutor extends Activity { webViewSettings.setDomStorageEnabled(true); webViewSettings.setWorkersEnabled(false); webViewSettings.setXSSAuditorEnabled(false); + + // This is asynchronous, but it gets processed by WebCore before it starts loading pages. + mCurrentWebView.useMockDeviceOrientation(); + } + + private void startTests() { + try { + Message serviceMsg = + Message.obtain(null, ManagerService.MSG_FIRST_TEST); + + Bundle bundle = new Bundle(); + if (!mTestsList.isEmpty()) { + bundle.putString("firstTest", mTestsList.get(0)); + bundle.putInt("index", mCurrentTestIndex); + } + + serviceMsg.setData(bundle); + mManagerServiceMessenger.send(serviceMsg); + } catch (RemoteException e) { + Log.e(LOG_TAG + "::startTests", e.getMessage()); + } + + runNextTest(); } private void runNextTest() { @@ -312,6 +342,8 @@ public class LayoutTestsExecutor extends Activity { } mCurrentTestRelativePath = mTestsList.remove(0); + Log.d(LOG_TAG + "::runNextTest", "Start: " + mCurrentTestRelativePath + + "(" + mCurrentTestIndex + ")"); mCurrentTestUri = Uri.fromFile(new File(TESTS_ROOT_DIR_PATH, mCurrentTestRelativePath)).toString(); @@ -386,6 +418,9 @@ public class LayoutTestsExecutor extends Activity { if (mCurrentTestTimedOut) { bundle.putString("resultCode", AbstractResult.ResultCode.FAIL_TIMED_OUT.name()); } + if (!mTestsList.isEmpty()) { + bundle.putString("nextTest", mTestsList.get(0)); + } serviceMsg.setData(bundle); mManagerServiceMessenger.send(serviceMsg); @@ -435,8 +470,7 @@ public class LayoutTestsExecutor extends Activity { Handler mLayoutTestControllerHandler = new Handler() { @Override public void handleMessage(Message msg) { - assert mCurrentState.isRunningState() - : "mCurrentState = " + mCurrentState.name(); + assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name(); switch (msg.what) { case MSG_WAIT_UNTIL_DONE: @@ -538,4 +572,10 @@ public class LayoutTestsExecutor extends Activity { msg.arg1 = allow ? 1 : 0; msg.sendToTarget(); } -}
\ No newline at end of file + + public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, + boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { + mCurrentWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta, + canProvideGamma, gamma); + } +} diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java index 3c77a55d4502..7ec3dd7cf1dc 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java @@ -38,6 +38,10 @@ public class ManagerService extends Service { private static final String LOG_TAG = "ManagerService"; + private static final int MSG_TEST_CRASHED = 0; + + private static final int CRASH_TIMEOUT_MS = 20 * 1000; + /** TODO: make it a setting */ static final String TESTS_ROOT_DIR_PATH = Environment.getExternalStorageDirectory() + @@ -67,11 +71,22 @@ public class ManagerService extends Service { static final int MSG_PROCESS_ACTUAL_RESULTS = 0; static final int MSG_ALL_TESTS_FINISHED = 1; + static final int MSG_FIRST_TEST = 2; + /** + * This handler is purely for IPC. It is used to create mMessenger + * that generates a binder returned in onBind method. + */ private Handler mIncomingHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_FIRST_TEST: + mSummarizer.reset(); + Bundle bundle = msg.getData(); + ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index")); + break; + case MSG_PROCESS_ACTUAL_RESULTS: Log.d(LOG_TAG + ".mIncomingHandler", msg.getData().getString("relativePath")); onActualResultsObtained(msg.getData()); @@ -79,6 +94,11 @@ public class ManagerService extends Service { case MSG_ALL_TESTS_FINISHED: mSummarizer.summarize(); + Intent intent = new Intent(ManagerService.this, TestsListActivity.class); + intent.setAction(Intent.ACTION_SHUTDOWN); + /** This flag is needed because we send the intent from the service */ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); break; } } @@ -86,9 +106,21 @@ public class ManagerService extends Service { private Messenger mMessenger = new Messenger(mIncomingHandler); + private Handler mCrashMessagesHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_TEST_CRASHED) { + onTestCrashed(); + } + } + }; + private FileFilter mFileFilter; private Summarizer mSummarizer; + private String mCurrentlyRunningTest; + private int mCurrentlyRunningTestIndex; + @Override public void onCreate() { super.onCreate(); @@ -98,13 +130,55 @@ public class ManagerService extends Service { } @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } private void onActualResultsObtained(Bundle bundle) { + mCrashMessagesHandler.removeMessages(MSG_TEST_CRASHED); + ensureNextTestSetup(bundle.getString("nextTest"), bundle.getInt("testIndex") + 1); + AbstractResult results = AbstractResult.TestType.valueOf(bundle.getString("type")).createResult(bundle); + + handleResults(results); + } + + private void ensureNextTestSetup(String nextTest, int index) { + if (nextTest == null) { + return; + } + + mCurrentlyRunningTest = nextTest; + mCurrentlyRunningTestIndex = index; + mCrashMessagesHandler.sendEmptyMessageDelayed(MSG_TEST_CRASHED, CRASH_TIMEOUT_MS); + } + + /** + * This sends an intent to TestsListActivity to restart LayoutTestsExecutor. + * The more detailed description of the flow is in the comment of onNewIntent + * method in TestsListActivity. + */ + private void onTestCrashed() { + handleResults(new CrashedDummyResult(mCurrentlyRunningTest)); + + Log.w(LOG_TAG + "::onTestCrashed", mCurrentlyRunningTest + + "(" + mCurrentlyRunningTestIndex + ")"); + + Intent intent = new Intent(this, TestsListActivity.class); + intent.setAction(Intent.ACTION_REBOOT); + /** This flag is needed because we send the intent from the service */ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("crashedTestIndex", mCurrentlyRunningTestIndex); + startActivity(intent); + } + + private void handleResults(AbstractResult results) { String relativePath = results.getRelativePath(); results.setExpectedTextResult(getExpectedTextResult(relativePath)); results.setExpectedImageResult(getExpectedImageResult(relativePath)); @@ -134,7 +208,8 @@ public class ManagerService extends Service { return; } - String resultPath = FileFilter.setPathEnding(testPath, "-actual." + IMAGE_RESULT_EXTENSION); + String resultPath = FileFilter.setPathEnding(testPath, + "-actual." + IMAGE_RESULT_EXTENSION); FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath), actualImageResult, false); } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java index 1b73f97afe5e..e27ecc963380 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java @@ -166,7 +166,8 @@ public class Summarizer { "</script>"; /** TODO: Make it a setting */ - private static final String HTML_SUMMARY_RELATIVE_PATH = "summary.html"; + private static final String HTML_DETAILS_RELATIVE_PATH = "details.html"; + private static final String TXT_SUMMARY_RELATIVE_PATH = "summary.txt"; private int mCrashedTestsCount = 0; private List<AbstractResult> mFailedNotIgnoredTests = new ArrayList<AbstractResult>(); @@ -176,6 +177,8 @@ public class Summarizer { private FileFilter mFileFilter; private String mResultsRootDirPath; + private String mTitleString; + public Summarizer(FileFilter fileFilter, String resultsRootDirPath) { mFileFilter = fileFilter; mResultsRootDirPath = resultsRootDirPath; @@ -198,6 +201,35 @@ public class Summarizer { } public void summarize() { + createHtmlDetails(); + createTxtSummary(); + } + + public void reset() { + mCrashedTestsCount = 0; + mFailedNotIgnoredTests.clear(); + mIgnoredTests.clear(); + mPassedNotIgnoredTests.clear(); + mTitleString = null; + } + + private void createTxtSummary() { + StringBuilder txt = new StringBuilder(); + + txt.append(getTitleString() + "\n"); + if (mCrashedTestsCount > 0) { + txt.append("CRASHED (total among all tests): " + mCrashedTestsCount + "\n"); + txt.append("-------------"); + } + txt.append("FAILED: " + mFailedNotIgnoredTests.size() + "\n"); + txt.append("IGNORED: " + mIgnoredTests.size() + "\n"); + txt.append("PASSED: " + mPassedNotIgnoredTests.size() + "\n"); + + FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH), + txt.toString().getBytes(), false); + } + + private void createHtmlDetails() { StringBuilder html = new StringBuilder(); html.append("<html><head>"); @@ -215,17 +247,24 @@ public class Summarizer { html.append("</body></html>"); - FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_SUMMARY_RELATIVE_PATH), + FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DETAILS_RELATIVE_PATH), html.toString().getBytes(), false); } + private String getTitleString() { + if (mTitleString == null) { + int total = mFailedNotIgnoredTests.size() + + mPassedNotIgnoredTests.size() + + mIgnoredTests.size(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + mTitleString = " - total of " + total + " tests - " + dateFormat.format(new Date()); + } + + return mTitleString; + } + private void createTopSummaryTable(StringBuilder html) { - int total = mFailedNotIgnoredTests.size() + - mPassedNotIgnoredTests.size() + - mIgnoredTests.size(); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - html.append("<h1> - total of " + total + " tests - "); - html.append(dateFormat.format(new Date()) + "</h1>"); + html.append("<h1>" + getTitleString() + "</h1>"); html.append("<table class=\"summary\">"); createSummaryTableRow(html, "CRASHED", mCrashedTestsCount); diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java index a402ae124a40..c95199ffb74a 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java @@ -24,12 +24,14 @@ import android.os.Handler; import android.os.Message; import android.view.Window; +import com.android.dumprendertree2.scriptsupport.OnEverythingFinishedCallback; + import java.util.ArrayList; /** * An Activity that generates a list of tests and sends the intent to * LayoutTestsExecuter to run them. It also restarts the LayoutTestsExecuter - * after it crashes (TODO). + * after it crashes. */ public class TestsListActivity extends Activity { @@ -57,6 +59,9 @@ public class TestsListActivity extends Activity { private ArrayList<String> mTestsList; private int mTotalTestCount; + private OnEverythingFinishedCallback mOnEverythingFinishedCallback; + private boolean mEverythingFinished; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -79,9 +84,68 @@ public class TestsListActivity extends Activity { sProgressDialog.show(); Message doneMsg = Message.obtain(mHandler, MSG_TEST_LIST_PRELOADER_DONE); + Intent serviceIntent = new Intent(this, ManagerService.class); + startService(serviceIntent); + new TestsListPreloaderThread(path, doneMsg).start(); } + @Override + protected void onNewIntent(Intent intent) { + if (intent.getAction().equals(Intent.ACTION_REBOOT)) { + onCrashIntent(intent); + } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) { + onEverythingFinishedIntent(intent); + } + } + + /** + * This method handles an intent that comes from ManageService when crash is detected. + * The intent contains an index in mTestsList of the test that crashed. TestsListActivity + * restarts the LayoutTestsExecutor from the following test in mTestsList, by sending + * an intent to it. This new intent contains a list of remaining tests to run, + * total count of all tests, and the index of the first test to run after restarting. + * LayoutTestExecutor runs then as usual, sending reports to ManagerService. If it + * detects the crash it sends a new intent and the flow repeats. + */ + private void onCrashIntent(Intent intent) { + int nextTestToRun = intent.getIntExtra("crashedTestIndex", -1) + 1; + if (nextTestToRun > 0 && nextTestToRun <= mTotalTestCount) { + restartExecutor(nextTestToRun); + } + } + + public void registerOnEverythingFinishedCallback(OnEverythingFinishedCallback callback) { + mOnEverythingFinishedCallback = callback; + if (mEverythingFinished) { + mOnEverythingFinishedCallback.onFinished(); + } + } + + private void onEverythingFinishedIntent(Intent intent) { + /** TODO: Show some kind of summary to the user */ + mEverythingFinished = true; + if (mOnEverythingFinishedCallback != null) { + mOnEverythingFinishedCallback.onFinished(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putStringArrayList("testsList", mTestsList); + outState.putInt("totalCount", mTotalTestCount); + + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + mTestsList = savedInstanceState.getStringArrayList("testsList"); + mTotalTestCount = savedInstanceState.getInt("totalCount"); + } + /** * (Re)starts the executer activity from the given test number (inclusive, 0-based). * This number is an index in mTestsList, not the sublist passed in the intent. @@ -93,9 +157,16 @@ public class TestsListActivity extends Activity { Intent intent = new Intent(); intent.setClass(this, LayoutTestsExecutor.class); intent.setAction(Intent.ACTION_RUN); - intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST, - new ArrayList<String>(mTestsList.subList(startFrom, mTotalTestCount))); - intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom); + + if (startFrom < mTotalTestCount) { + intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST, + new ArrayList<String>(mTestsList.subList(startFrom, mTotalTestCount))); + intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom); + } else { + intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST, + new ArrayList<String>()); + } + startActivity(intent); } }
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java index f76105d0fc56..2145af77b230 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java @@ -68,15 +68,15 @@ public class TestsListPreloaderThread extends Thread { File file = new File(TESTS_ROOT_DIR_PATH, mRelativePath); if (!file.exists()) { Log.e(LOG_TAG + "::run", "Path does not exist: " + mRelativePath); - return; - } - - /** Populate the tests' list accordingly */ - if (file.isDirectory()) { - preloadTests(mRelativePath); } else { - mTestsList.add(mRelativePath); + /** Populate the tests' list accordingly */ + if (file.isDirectory()) { + preloadTests(mRelativePath); + } else { + mTestsList.add(mRelativePath); + } } + mDoneMsg.obj = mTestsList; mDoneMsg.sendToTarget(); } diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java new file mode 100644 index 000000000000..e1d436447f0f --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java @@ -0,0 +1,25 @@ +/* + * 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.dumprendertree2.scriptsupport; + +/** + * Callback used to inform scriptsupport.Starter that everything is finished and + * we can exit + */ +public interface OnEverythingFinishedCallback { + public void onFinished(); +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java new file mode 100644 index 000000000000..78f58d537e39 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java @@ -0,0 +1,37 @@ +/* + * 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.dumprendertree2.scriptsupport; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; + +/** + * Extends InstrumentationTestRunner to allow the script to pass arguments to the application + */ +public class ScriptTestRunner extends InstrumentationTestRunner { + String mTestsRelativePath; + + @Override + public void onCreate(Bundle arguments) { + mTestsRelativePath = arguments.getString("path"); + super.onCreate(arguments); + } + + public String getTestsRelativePath() { + return mTestsRelativePath; + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java new file mode 100644 index 000000000000..ddfae69c8b79 --- /dev/null +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java @@ -0,0 +1,74 @@ +/* + * 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.dumprendertree2.scriptsupport; + +import android.content.Intent; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +import com.android.dumprendertree2.TestsListActivity; + +/** + * A class which provides methods that can be invoked by a script running on the host machine to + * run the tests. + * + * It starts a TestsListActivity and does not return until all the tests finish executing. + */ +public class Starter extends ActivityInstrumentationTestCase2<TestsListActivity> { + private static final String LOG_TAG = "Starter"; + private boolean mEverythingFinished; + + public Starter() { + super(TestsListActivity.class); + } + + /** + * This method is called from adb to start executing the tests. It doesn't return + * until everything is finished so that the script can wait for the end if it needs + * to. + */ + public void startLayoutTests() { + ScriptTestRunner runner = (ScriptTestRunner)getInstrumentation(); + String relativePath = runner.getTestsRelativePath(); + + Intent intent = new Intent(); + intent.setClassName("com.android.dumprendertree2", "TestsListActivity"); + intent.setAction(Intent.ACTION_RUN); + intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath); + setActivityIntent(intent); + getActivity().registerOnEverythingFinishedCallback(new OnEverythingFinishedCallback() { + /** This method is safe to call on any thread */ + @Override + public void onFinished() { + synchronized (Starter.this) { + mEverythingFinished = true; + Starter.this.notifyAll(); + } + } + }); + + synchronized (this) { + while (!mEverythingFinished) { + try { + this.wait(); + } catch (InterruptedException e) { + Log.e(LOG_TAG + "::startLayoutTests", e.getMessage()); + } + } + } + } +}
\ No newline at end of file diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java index 661a8ec02fb9..af0d7d166cbc 100644 --- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java +++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java @@ -378,6 +378,10 @@ public class DirListActivity extends ListActivity { private ListItem[] getDirList(String dirPath) { File dir = new File(mRootDirPath, dirPath); + if (!dir.exists()) { + return new ListItem[0]; + } + List<ListItem> subDirs = new ArrayList<ListItem>(); List<ListItem> subFiles = new ArrayList<ListItem>(); diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 73994f76264f..181b4c861ef7 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -171,5 +171,23 @@ </intent-filter> </activity> + <activity + android:name="SimplePathsActivity" + android:label="_SimplePaths"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity + android:name="AdvancedBlendActivity" + android:label="_AdvancedBlend"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java new file mode 100644 index 000000000000..6c80a6d9dae3 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java @@ -0,0 +1,138 @@ +/* + * 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.google.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Shader; +import android.os.Bundle; +import android.view.View; + +@SuppressWarnings({"UnusedDeclaration"}) +public class AdvancedBlendActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(new ShadersView(this)); + } + + static class ShadersView extends View { + private BitmapShader mScaledShader; + private int mTexWidth; + private int mTexHeight; + private Paint mPaint; + private float mDrawWidth; + private float mDrawHeight; + private LinearGradient mHorGradient; + private ComposeShader mComposeShader; + private ComposeShader mCompose2Shader; + private ComposeShader mCompose3Shader; + private ComposeShader mCompose4Shader; + private ComposeShader mCompose5Shader; + private ComposeShader mCompose6Shader; + private BitmapShader mScaled2Shader; + + ShadersView(Context c) { + super(c); + + Bitmap texture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1); + mTexWidth = texture.getWidth(); + mTexHeight = texture.getHeight(); + mDrawWidth = mTexWidth * 2.2f; + mDrawHeight = mTexHeight * 1.2f; + + mScaledShader = new BitmapShader(texture, Shader.TileMode.MIRROR, + Shader.TileMode.MIRROR); + Matrix m2 = new Matrix(); + m2.setScale(0.5f, 0.5f); + mScaledShader.setLocalMatrix(m2); + + mScaled2Shader = new BitmapShader(texture, Shader.TileMode.MIRROR, + Shader.TileMode.MIRROR); + Matrix m3 = new Matrix(); + m3.setScale(0.1f, 0.1f); + mScaled2Shader.setLocalMatrix(m3); + + mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f, + Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP); + + mComposeShader = new ComposeShader(mScaledShader, mHorGradient, + PorterDuff.Mode.DARKEN); + mCompose2Shader = new ComposeShader(mScaledShader, mHorGradient, + PorterDuff.Mode.LIGHTEN); + mCompose3Shader = new ComposeShader(mScaledShader, mHorGradient, + PorterDuff.Mode.MULTIPLY); + mCompose4Shader = new ComposeShader(mScaledShader, mHorGradient, + PorterDuff.Mode.SCREEN); + mCompose5Shader = new ComposeShader(mScaledShader, mHorGradient, + PorterDuff.Mode.ADD); + mCompose6Shader = new ComposeShader(mHorGradient, mScaledShader, + PorterDuff.Mode.OVERLAY); + + mPaint = new Paint(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRGB(255, 255, 255); + + canvas.save(); + canvas.translate(40.0f, 40.0f); + + mPaint.setShader(mComposeShader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.translate(0.0f, 40.0f + mDrawHeight); + mPaint.setShader(mCompose2Shader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.translate(0.0f, 40.0f + mDrawHeight); + mPaint.setShader(mCompose3Shader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.restore(); + + canvas.save(); + canvas.translate(40.0f + mDrawWidth + 40.0f, 40.0f); + + mPaint.setShader(mCompose4Shader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.translate(0.0f, 40.0f + mDrawHeight); + mPaint.setShader(mCompose5Shader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.translate(0.0f, 40.0f + mDrawHeight); + mPaint.setShader(mCompose6Shader); + canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint); + + canvas.restore(); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java new file mode 100644 index 000000000000..071a118796aa --- /dev/null +++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java @@ -0,0 +1,40 @@ +/* + * 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.google.android.test.hwui; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Gravity; +import android.widget.EditText; +import android.widget.FrameLayout; + +@SuppressWarnings({"UnusedDeclaration"}) +public class SimplePathsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FrameLayout layout = new FrameLayout(this); + EditText text = new EditText(this); + layout.addView(text, new FrameLayout.LayoutParams(600, 350, Gravity.CENTER)); + text.setText("This is an example of an EditText widget \n" + + "using simple paths to create the selection."); + //text.setSelection(0, text.getText().length()); + + setContentView(layout); + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java index 3b5cf43e1bb4..59f665cad96a 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java +++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java @@ -35,6 +35,7 @@ public class TextActivity extends Activity { static class CustomTextView extends View { private final Paint mMediumPaint; private final Paint mLargePaint; + private final Paint mStrikePaint; CustomTextView(Context c) { super(c); @@ -45,6 +46,11 @@ public class TextActivity extends Activity { mLargePaint = new Paint(); mLargePaint.setAntiAlias(true); mLargePaint.setTextSize(36.0f); + mStrikePaint = new Paint(); + mStrikePaint.setAntiAlias(true); + mStrikePaint.setTextSize(16.0f); + mStrikePaint.setUnderlineText(true); + } @Override @@ -61,6 +67,15 @@ public class TextActivity extends Activity { canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint); canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint); + + canvas.drawText("Hello OpenGL renderer!", 500, 40, mStrikePaint); + mStrikePaint.setStrikeThruText(true); + canvas.drawText("Hello OpenGL renderer!", 500, 70, mStrikePaint); + mStrikePaint.setUnderlineText(false); + canvas.drawText("Hello OpenGL renderer!", 500, 100, mStrikePaint); + mStrikePaint.setStrikeThruText(false); + mStrikePaint.setUnderlineText(true); + canvas.save(); canvas.clipRect(150.0f, 220.0f, 450.0f, 320.0f); canvas.drawText("Hello OpenGL renderer!", 100, 300, mLargePaint); diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index b339a2cd2eb0..094b7db3b4c4 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -41,7 +41,7 @@ LOCAL_STATIC_LIBRARIES := \ libpng ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -lrt +LOCAL_LDLIBS += -lrt -lpthread endif # Statically link libz for MinGW (Win SDK under Linux), diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java index d5d315ec19ef..1e1aba93db8d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java @@ -69,11 +69,6 @@ public class Canvas extends _Original_Canvas { throw new UnsupportedOperationException("Can't create Canvas(int)"); } - public Canvas(javax.microedition.khronos.opengles.GL gl) { - mLogger = null; - throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)"); - } - // custom constructors for our use. public Canvas(int width, int height, ILayoutLog logger) { mLogger = logger; @@ -1174,15 +1169,6 @@ public class Canvas extends _Original_Canvas { } /* (non-Javadoc) - * @see android.graphics.Canvas#getGL() - */ - @Override - public GL getGL() { - // TODO Auto-generated method stub - return super.getGL(); - } - - /* (non-Javadoc) * @see android.graphics.Canvas#getMatrix() */ @Override @@ -1257,15 +1243,6 @@ public class Canvas extends _Original_Canvas { } /* (non-Javadoc) - * @see android.graphics.Canvas#setViewport(int, int) - */ - @Override - public void setViewport(int width, int height) { - // TODO Auto-generated method stub - super.setViewport(width, height); - } - - /* (non-Javadoc) * @see android.graphics.Canvas#skew(float, float) */ @Override diff --git a/tools/localize/Android.mk b/tools/localize/Android.mk index ab79f8dbdcfc..f284e865878a 100644 --- a/tools/localize/Android.mk +++ b/tools/localize/Android.mk @@ -34,7 +34,7 @@ LOCAL_STATIC_LIBRARIES := \ libcutils ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -lrt +LOCAL_LDLIBS += -lrt -lpthread endif diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk new file mode 100644 index 000000000000..b02c1cb13b63 --- /dev/null +++ b/tools/obbtool/Android.mk @@ -0,0 +1,30 @@ +# +# Copyright 2010 The Android Open Source Project +# +# Opaque Binary Blob (OBB) Tool +# + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS),) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + Main.cpp + +#LOCAL_C_INCLUDES += + +LOCAL_STATIC_LIBRARIES := \ + libutils \ + libcutils + +ifeq ($(HOST_OS),linux) +LOCAL_LDLIBS += -lpthread +endif + +LOCAL_MODULE := obbtool + +include $(BUILD_HOST_EXECUTABLE) + +endif # TARGET_BUILD_APPS diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp new file mode 100644 index 000000000000..2a9bf0420102 --- /dev/null +++ b/tools/obbtool/Main.cpp @@ -0,0 +1,225 @@ +/* + * 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 <utils/ObbFile.h> +#include <utils/String8.h> + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> + +using namespace android; + +static const char* gProgName = "obbtool"; +static const char* gProgVersion = "1.0"; + +static int wantUsage = 0; +static int wantVersion = 0; + +#define ADD_OPTS "n:v:f:c:" +static const struct option longopts[] = { + {"help", no_argument, &wantUsage, 1}, + {"version", no_argument, &wantVersion, 1}, + + /* Args for "add" */ + {"name", required_argument, NULL, 'n'}, + {"version", required_argument, NULL, 'v'}, + {"filesystem", required_argument, NULL, 'f'}, + {"crypto", required_argument, NULL, 'c'}, + + {NULL, 0, NULL, '\0'} +}; + +struct package_info_t { + char* packageName; + int packageVersion; + char* filesystem; + char* crypto; +}; + +/* + * Print usage info. + */ +void usage(void) +{ + fprintf(stderr, "Opaque Binary Blob (OBB) Tool\n\n"); + fprintf(stderr, "Usage:\n"); + fprintf(stderr, + " %s a[dd] [ OPTIONS ] FILENAME\n" + " Adds an OBB signature to the file.\n\n", gProgName); + fprintf(stderr, + " %s r[emove] FILENAME\n" + " Removes the OBB signature from the file.\n\n", gProgName); + fprintf(stderr, + " %s i[nfo] FILENAME\n" + " Prints the OBB signature information of a file.\n\n", gProgName); +} + +void doAdd(const char* filename, struct package_info_t* info) { + ObbFile *obb = new ObbFile(); + if (obb->readFrom(filename)) { + fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename); + return; + } + + obb->setPackageName(String8(info->packageName)); + obb->setVersion(info->packageVersion); + + if (!obb->writeTo(filename)) { + fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n", + filename, strerror(errno)); + return; + } + + fprintf(stderr, "OBB signature successfully written\n"); +} + +void doRemove(const char* filename) { + ObbFile *obb = new ObbFile(); + if (!obb->readFrom(filename)) { + fprintf(stderr, "ERROR: %s: no OBB signature present\n", filename); + return; + } + + if (!obb->removeFrom(filename)) { + fprintf(stderr, "ERROR: %s: couldn't remove OBB signature\n", filename); + return; + } + + fprintf(stderr, "OBB signature successfully removed\n"); +} + +void doInfo(const char* filename) { + ObbFile *obb = new ObbFile(); + if (!obb->readFrom(filename)) { + fprintf(stderr, "ERROR: %s: couldn't read OBB signature\n", filename); + return; + } + + printf("OBB info for '%s':\n", filename); + printf("Package name: %s\n", obb->getPackageName().string()); + printf(" Version: %d\n", obb->getVersion()); +} + +/* + * Parse args. + */ +int main(int argc, char* const argv[]) +{ + const char *prog = argv[0]; + struct options *options; + int opt; + int option_index = 0; + struct package_info_t package_info; + + int result = 1; // pessimistically assume an error. + + if (argc < 2) { + wantUsage = 1; + goto bail; + } + + while ((opt = getopt_long(argc, argv, ADD_OPTS, longopts, &option_index)) != -1) { + switch (opt) { + case 0: + if (longopts[option_index].flag) + break; + fprintf(stderr, "'%s' requires an argument\n", longopts[option_index].name); + wantUsage = 1; + goto bail; + case 'n': + package_info.packageName = optarg; + break; + case 'v': + char *end; + package_info.packageVersion = strtol(optarg, &end, 10); + if (*optarg == '\0' || *end != '\0') { + fprintf(stderr, "ERROR: invalid version; should be integer!\n\n"); + wantUsage = 1; + goto bail; + } + break; + case 'f': + package_info.filesystem = optarg; + break; + case 'c': + package_info.crypto = optarg; + break; + case '?': + wantUsage = 1; + goto bail; + } + } + + if (wantVersion) { + fprintf(stderr, "%s %s\n", gProgName, gProgVersion); + } + + if (wantUsage) { + goto bail; + } + +#define CHECK_OP(name) \ + if (strncmp(op, name, opsize)) { \ + fprintf(stderr, "ERROR: unknown function '%s'!\n\n", op); \ + wantUsage = 1; \ + goto bail; \ + } + + if (optind < argc) { + const char* op = argv[optind++]; + const int opsize = strlen(op); + + if (optind >= argc) { + fprintf(stderr, "ERROR: filename required!\n\n"); + wantUsage = 1; + goto bail; + } + + const char* filename = argv[optind++]; + + switch (op[0]) { + case 'a': + CHECK_OP("add"); + if (package_info.packageName == NULL) { + fprintf(stderr, "ERROR: arguments required 'packageName' and 'version'\n"); + goto bail; + } + doAdd(filename, &package_info); + break; + case 'r': + CHECK_OP("remove"); + doRemove(filename); + break; + case 'i': + CHECK_OP("info"); + doInfo(filename); + break; + default: + fprintf(stderr, "ERROR: unknown command '%s'!\n\n", op); + wantUsage = 1; + goto bail; + } + } + +bail: + if (wantUsage) { + usage(); + result = 2; + } + + return result; +} diff --git a/voip/java/android/net/sip/BinderHelper.java b/voip/java/android/net/sip/BinderHelper.java deleted file mode 100644 index bd3da32383c1..000000000000 --- a/voip/java/android/net/sip/BinderHelper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.sip; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.ConditionVariable; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Looper; -import android.util.Log; - -// TODO: throw away this class after moving SIP classes to framework -// This class helps to get IBinder instance of a service in a blocking call. -// The method cannot be called in app's main thread as the ServiceConnection -// callback will. -class BinderHelper<T extends IInterface> { - private Context mContext; - private IBinder mBinder; - private Class<T> mClass; - - BinderHelper(Context context, Class<T> klass) { - mContext = context; - mClass = klass; - } - - void startService() { - mContext.startService(new Intent(mClass.getName())); - } - - void stopService() { - mContext.stopService(new Intent(mClass.getName())); - } - - IBinder getBinder() { - // cannot call this method in app's main thread - if (Looper.getMainLooper().getThread() == Thread.currentThread()) { - throw new RuntimeException( - "This method cannot be called in app's main thread"); - } - - final ConditionVariable cv = new ConditionVariable(); - cv.close(); - ServiceConnection c = new ServiceConnection() { - public synchronized void onServiceConnected( - ComponentName className, IBinder binder) { - Log.v("BinderHelper", "service connected!"); - mBinder = binder; - cv.open(); - mContext.unbindService(this); - } - - public void onServiceDisconnected(ComponentName className) { - cv.open(); - mContext.unbindService(this); - } - }; - if (mContext.bindService(new Intent(mClass.getName()), c, 0)) { - cv.block(4500); - } - return mBinder; - } -} diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java index 57e0bd2790c4..474bc4b198d0 100644 --- a/voip/java/android/net/sip/SipAudioCallImpl.java +++ b/voip/java/android/net/sip/SipAudioCallImpl.java @@ -343,8 +343,11 @@ public class SipAudioCallImpl extends SipSessionAdapter public synchronized void endCall() throws SipException { try { stopRinging(); - if (mSipSession != null) mSipSession.endCall(); stopCall(true); + mInCall = false; + + // perform the above local ops first and then network op + if (mSipSession != null) mSipSession.endCall(); } catch (Throwable e) { throwSipException(e); } diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index f28b41cc5ed7..287a13ad3bd7 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -68,9 +68,6 @@ public class SipManager { private ISipService mSipService; - // Will be removed once the SIP service is integrated into framework - private BinderHelper<ISipService> mBinderHelper; - /** * Creates a manager instance and initializes the background SIP service. * Will be removed once the SIP service is integrated into framework. diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 6e0bc9d04f3f..3426af7019af 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -85,5 +85,13 @@ interface IWifiManager WifiConfiguration getWifiApConfiguration(); void setWifiApConfiguration(in WifiConfiguration wifiConfig); + + void startWifi(); + + void stopWifi(); + + void addToBlacklist(String bssid); + + void clearBlacklist(); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 6fac902bdd0a..339763a3c90d 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -293,6 +293,21 @@ public class WifiManager { public static final String EXTRA_NEW_RSSI = "newRssi"; /** + * Broadcast intent action indicating that the IP configuration + * changed on wifi. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String CONFIG_CHANGED_ACTION = "android.net.wifi.CONFIG_CHANGED"; + /** + * The lookup key for a {@link android.net.NetworkProperties} object associated with the + * Wi-Fi network. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * @hide + */ + public static final String EXTRA_NETWORK_PROPERTIES = "networkProperties"; + + /** * The network IDs of the configured networks could have changed. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -838,6 +853,82 @@ public class WifiManager { } } + /** + * Start the driver and connect to network. + * + * This function will over-ride WifiLock and device idle status. For example, + * even if the device is idle or there is only a scan-only lock held, + * a start wifi would mean that wifi connection is kept active until + * a stopWifi() is sent. + * + * This API is used by WifiStateTracker + * + * @return {@code true} if the operation succeeds else {@code false} + * @hide + */ + public boolean startWifi() { + try { + mService.startWifi(); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** + * Disconnect from a network (if any) and stop the driver. + * + * This function will over-ride WifiLock and device idle status. Wi-Fi + * stays inactive until a startWifi() is issued. + * + * This API is used by WifiStateTracker + * + * @return {@code true} if the operation succeeds else {@code false} + * @hide + */ + public boolean stopWifi() { + try { + mService.stopWifi(); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** + * Add a bssid to the supplicant blacklist + * + * This API is used by WifiWatchdogService + * + * @return {@code true} if the operation succeeds else {@code false} + * @hide + */ + public boolean addToBlacklist(String bssid) { + try { + mService.addToBlacklist(bssid); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** + * Clear the supplicant blacklist + * + * This API is used by WifiWatchdogService + * + * @return {@code true} if the operation succeeds else {@code false} + * @hide + */ + public boolean clearBlacklist() { + try { + mService.clearBlacklist(); + return true; + } catch (RemoteException e) { + return false; + } + } + /** * Allows an application to keep the Wi-Fi radio awake. * Normally the Wi-Fi radio may turn off when the user has not used the device in a while. diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index f2f83438e4ed..af3132ff402a 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -19,14 +19,13 @@ package android.net.wifi; import android.util.Log; import android.util.Config; import android.net.NetworkInfo; -import android.net.NetworkStateTracker; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Listens for events from the wpa_supplicant server, and passes them on - * to the {@link WifiStateTracker} for handling. Runs in its own thread. + * to the {@link WifiStateMachine} for handling. Runs in its own thread. * * @hide */ @@ -117,7 +116,7 @@ public class WifiMonitor { private static Pattern mConnectedEventPattern = Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); - private final WifiStateTracker mWifiStateTracker; + private final WifiStateMachine mWifiStateMachine; /** * This indicates the supplicant connection for the monitor is closed @@ -139,18 +138,14 @@ public class WifiMonitor { */ private static final int MAX_RECV_ERRORS = 10; - public WifiMonitor(WifiStateTracker tracker) { - mWifiStateTracker = tracker; + public WifiMonitor(WifiStateMachine wifiStateMachine) { + mWifiStateMachine = wifiStateMachine; } public void startMonitoring() { new MonitorThread().start(); } - public NetworkStateTracker getNetworkStateTracker() { - return mWifiStateTracker; - } - class MonitorThread extends Thread { public MonitorThread() { super("WifiMonitor"); @@ -161,9 +156,9 @@ public class WifiMonitor { if (connectToSupplicant()) { // Send a message indicating that it is now possible to send commands // to the supplicant - mWifiStateTracker.notifySupplicantConnection(); + mWifiStateMachine.notifySupplicantConnection(); } else { - mWifiStateTracker.notifySupplicantLost(); + mWifiStateMachine.notifySupplicantLost(); return; } @@ -259,7 +254,7 @@ public class WifiMonitor { } // notify and exit - mWifiStateTracker.notifySupplicantLost(); + mWifiStateMachine.notifySupplicantLost(); break; } else { handleEvent(event, eventData); @@ -285,7 +280,7 @@ public class WifiMonitor { } private void handlePasswordKeyMayBeIncorrect() { - mWifiStateTracker.notifyPasswordKeyMayBeIncorrect(); + mWifiStateMachine.notifyPasswordKeyMayBeIncorrect(); } private void handleDriverEvent(String state) { @@ -293,11 +288,11 @@ public class WifiMonitor { return; } if (state.equals("STOPPED")) { - mWifiStateTracker.notifyDriverStopped(); + mWifiStateMachine.notifyDriverStopped(); } else if (state.equals("STARTED")) { - mWifiStateTracker.notifyDriverStarted(); + mWifiStateMachine.notifyDriverStarted(); } else if (state.equals("HANGED")) { - mWifiStateTracker.notifyDriverHung(); + mWifiStateMachine.notifyDriverHung(); } } @@ -318,7 +313,7 @@ public class WifiMonitor { break; case SCAN_RESULTS: - mWifiStateTracker.notifyScanResultsAvailable(); + mWifiStateMachine.notifyScanResultsAvailable(); break; case UNKNOWN: @@ -375,7 +370,7 @@ public class WifiMonitor { if (newSupplicantState == SupplicantState.INVALID) { Log.w(TAG, "Invalid supplicant state: " + newState); } - mWifiStateTracker.notifySupplicantStateChange(networkId, BSSID, newSupplicantState); + mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState); } } @@ -395,7 +390,7 @@ public class WifiMonitor { } } } - mWifiStateTracker.notifyNetworkStateChange(newState, BSSID, networkId); + mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId); } /** diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java new file mode 100644 index 000000000000..845508b4bae1 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -0,0 +1,3571 @@ +/* + * 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.net.wifi; + +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; +import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; + +/** + * TODO: Add soft AP states as part of WIFI_STATE_XXX + * Retain WIFI_STATE_ENABLING that indicates driver is loading + * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started + * and WIFI_STATE_FAILED for failure + * Deprecate WIFI_STATE_UNKNOWN + * + * Doing this will simplify the logic for sending broadcasts + */ +import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; +import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; +import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; +import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; +import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; + +import android.app.ActivityManagerNative; +import android.net.NetworkInfo; +import android.net.DhcpInfo; +import android.net.NetworkUtils; +import android.net.ConnectivityManager; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkProperties; +import android.os.Binder; +import android.os.Message; +import android.os.Parcelable; +import android.os.Handler; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.PowerManager; +import android.os.SystemProperties; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Process; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.app.backup.IBackupManager; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothA2dp; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.Context; +import android.database.ContentObserver; +import com.android.internal.app.IBatteryStats; +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +/** + * Track the state of Wifi connectivity. All event handling is done here, + * and all changes in connectivity state are initiated here. + * + * @hide + */ +//TODO: we still need frequent scanning for the case when +// we issue disconnect but need scan results for open network notification +public class WifiStateMachine extends HierarchicalStateMachine { + + private static final String TAG = "WifiStateMachine"; + private static final String NETWORKTYPE = "WIFI"; + private static final boolean DBG = false; + + /* TODO: fetch a configurable interface */ + private static final String SOFTAP_IFACE = "wl0.1"; + + private WifiMonitor mWifiMonitor; + private INetworkManagementService nwService; + private ConnectivityManager mCm; + + /* Scan results handling */ + private List<ScanResult> mScanResults; + private static final Pattern scanResultPattern = Pattern.compile("\t+"); + private static final int SCAN_RESULT_CACHE_SIZE = 80; + private final LinkedHashMap<String, ScanResult> mScanResultCache; + + private String mInterfaceName; + + private int mNumAllowedChannels = 0; + private int mLastSignalLevel = -1; + private String mLastBssid; + private int mLastNetworkId; + private boolean mEnableRssiPolling = false; + private boolean mPasswordKeyMayBeIncorrect = false; + private boolean mUseStaticIp = false; + private int mReconnectCount = 0; + private boolean mIsScanMode = false; + private boolean mConfigChanged = false; + + /** + * Instance of the bluetooth headset helper. This needs to be created + * early because there is a delay before it actually 'connects', as + * noted by its javadoc. If we check before it is connected, it will be + * in an error state and we will not disable coexistence. + */ + private BluetoothHeadset mBluetoothHeadset; + + private BluetoothA2dp mBluetoothA2dp; + + /** + * Observes the static IP address settings. + */ + private SettingsObserver mSettingsObserver; + private NetworkProperties mNetworkProperties; + + // Held during driver load and unload + private static PowerManager.WakeLock sWakeLock; + + private Context mContext; + + private DhcpInfo mDhcpInfo; + private WifiInfo mWifiInfo; + private NetworkInfo mNetworkInfo; + private SupplicantStateTracker mSupplicantStateTracker; + + // Event log tags (must be in sync with event-log-tags) + private static final int EVENTLOG_WIFI_STATE_CHANGED = 50021; + private static final int EVENTLOG_WIFI_EVENT_HANDLED = 50022; + private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50023; + + /* Load the driver */ + private static final int CMD_LOAD_DRIVER = 1; + /* Unload the driver */ + private static final int CMD_UNLOAD_DRIVER = 2; + /* Indicates driver load succeeded */ + private static final int CMD_LOAD_DRIVER_SUCCESS = 3; + /* Indicates driver load failed */ + private static final int CMD_LOAD_DRIVER_FAILURE = 4; + /* Indicates driver unload succeeded */ + private static final int CMD_UNLOAD_DRIVER_SUCCESS = 5; + /* Indicates driver unload failed */ + private static final int CMD_UNLOAD_DRIVER_FAILURE = 6; + + /* Start the supplicant */ + private static final int CMD_START_SUPPLICANT = 11; + /* Stop the supplicant */ + private static final int CMD_STOP_SUPPLICANT = 12; + /* Start the driver */ + private static final int CMD_START_DRIVER = 13; + /* Start the driver */ + private static final int CMD_STOP_DRIVER = 14; + /* Indicates DHCP succeded */ + private static final int CMD_IP_CONFIG_SUCCESS = 15; + /* Indicates DHCP failed */ + private static final int CMD_IP_CONFIG_FAILURE = 16; + /* Re-configure interface */ + private static final int CMD_RECONFIGURE_IP = 17; + + + /* Start the soft access point */ + private static final int CMD_START_AP = 21; + /* Stop the soft access point */ + private static final int CMD_STOP_AP = 22; + + + /* Supplicant events */ + /* Connection to supplicant established */ + private static final int SUP_CONNECTION_EVENT = 31; + /* Connection to supplicant lost */ + private static final int SUP_DISCONNECTION_EVENT = 32; + /* Driver start completed */ + private static final int DRIVER_START_EVENT = 33; + /* Driver stop completed */ + private static final int DRIVER_STOP_EVENT = 34; + /* Network connection completed */ + private static final int NETWORK_CONNECTION_EVENT = 36; + /* Network disconnection completed */ + private static final int NETWORK_DISCONNECTION_EVENT = 37; + /* Scan results are available */ + private static final int SCAN_RESULTS_EVENT = 38; + /* Supplicate state changed */ + private static final int SUPPLICANT_STATE_CHANGE_EVENT = 39; + /* Password may be incorrect */ + private static final int PASSWORD_MAY_BE_INCORRECT_EVENT = 40; + + /* Supplicant commands */ + /* Is supplicant alive ? */ + private static final int CMD_PING_SUPPLICANT = 51; + /* Add/update a network configuration */ + private static final int CMD_ADD_OR_UPDATE_NETWORK = 52; + /* Delete a network */ + private static final int CMD_REMOVE_NETWORK = 53; + /* Enable a network. The device will attempt a connection to the given network. */ + private static final int CMD_ENABLE_NETWORK = 54; + /* Disable a network. The device does not attempt a connection to the given network. */ + private static final int CMD_DISABLE_NETWORK = 55; + /* Blacklist network. De-prioritizes the given BSSID for connection. */ + private static final int CMD_BLACKLIST_NETWORK = 56; + /* Clear the blacklist network list */ + private static final int CMD_CLEAR_BLACKLIST = 57; + /* Get the configured networks */ + private static final int CMD_GET_NETWORK_CONFIG = 58; + /* Save configuration */ + private static final int CMD_SAVE_CONFIG = 59; + /* Connection status */ + private static final int CMD_CONNECTION_STATUS = 60; + + /* Supplicant commands after driver start*/ + /* Initiate a scan */ + private static final int CMD_START_SCAN = 71; + /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */ + private static final int CMD_SET_SCAN_MODE = 72; + /* Set scan type. SCAN_ACTIVE or SCAN_PASSIVE */ + private static final int CMD_SET_SCAN_TYPE = 73; + /* Disconnect from a network */ + private static final int CMD_DISCONNECT = 74; + /* Reconnect to a network */ + private static final int CMD_RECONNECT = 75; + /* Reassociate to a network */ + private static final int CMD_REASSOCIATE = 76; + /* Set power mode + * POWER_MODE_ACTIVE + * POWER_MODE_AUTO + */ + private static final int CMD_SET_POWER_MODE = 77; + /* Set bluetooth co-existence + * BLUETOOTH_COEXISTENCE_MODE_ENABLED + * BLUETOOTH_COEXISTENCE_MODE_DISABLED + * BLUETOOTH_COEXISTENCE_MODE_SENSE + */ + private static final int CMD_SET_BLUETOOTH_COEXISTENCE = 78; + /* Enable/disable bluetooth scan mode + * true(1) + * false(0) + */ + private static final int CMD_SET_BLUETOOTH_SCAN_MODE = 79; + /* Set number of allowed channels */ + private static final int CMD_SET_NUM_ALLOWED_CHANNELS = 80; + /* Request connectivity manager wake lock before driver stop */ + private static final int CMD_REQUEST_CM_WAKELOCK = 81; + /* Enables RSSI poll */ + private static final int CMD_ENABLE_RSSI_POLL = 82; + /* RSSI poll */ + private static final int CMD_RSSI_POLL = 83; + /* Get current RSSI */ + private static final int CMD_GET_RSSI = 84; + /* Get approx current RSSI */ + private static final int CMD_GET_RSSI_APPROX = 85; + /* Get link speed on connection */ + private static final int CMD_GET_LINK_SPEED = 86; + /* Radio mac address */ + private static final int CMD_GET_MAC_ADDR = 87; + /* Set up packet filtering */ + private static final int CMD_START_PACKET_FILTERING = 88; + /* Clear packet filter */ + private static final int CMD_STOP_PACKET_FILTERING = 89; + + /** + * Interval in milliseconds between polling for connection + * status items that are not sent via asynchronous events. + * An example is RSSI (signal strength). + */ + private static final int POLL_RSSI_INTERVAL_MSECS = 3000; + + private static final int CONNECT_MODE = 1; + private static final int SCAN_ONLY_MODE = 2; + + private static final int SCAN_ACTIVE = 1; + private static final int SCAN_PASSIVE = 2; + + /** + * The maximum number of times we will retry a connection to an access point + * for which we have failed in acquiring an IP address from DHCP. A value of + * N means that we will make N+1 connection attempts in all. + * <p> + * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default + * value if a Settings value is not present. + */ + private static final int DEFAULT_MAX_DHCP_RETRIES = 9; + + private static final int DRIVER_POWER_MODE_ACTIVE = 1; + private static final int DRIVER_POWER_MODE_AUTO = 0; + + /* Default parent state */ + private HierarchicalState mDefaultState = new DefaultState(); + /* Temporary initial state */ + private HierarchicalState mInitialState = new InitialState(); + /* Unloading the driver */ + private HierarchicalState mDriverUnloadingState = new DriverUnloadingState(); + /* Loading the driver */ + private HierarchicalState mDriverUnloadedState = new DriverUnloadedState(); + /* Driver load/unload failed */ + private HierarchicalState mDriverFailedState = new DriverFailedState(); + /* Driver loading */ + private HierarchicalState mDriverLoadingState = new DriverLoadingState(); + /* Driver loaded */ + private HierarchicalState mDriverLoadedState = new DriverLoadedState(); + /* Driver loaded, waiting for supplicant to start */ + private HierarchicalState mWaitForSupState = new WaitForSupState(); + + /* Driver loaded and supplicant ready */ + private HierarchicalState mDriverSupReadyState = new DriverSupReadyState(); + /* Driver start issued, waiting for completed event */ + private HierarchicalState mDriverStartingState = new DriverStartingState(); + /* Driver started */ + private HierarchicalState mDriverStartedState = new DriverStartedState(); + /* Driver stopping */ + private HierarchicalState mDriverStoppingState = new DriverStoppingState(); + /* Driver stopped */ + private HierarchicalState mDriverStoppedState = new DriverStoppedState(); + /* Scan for networks, no connection will be established */ + private HierarchicalState mScanModeState = new ScanModeState(); + /* Connecting to an access point */ + private HierarchicalState mConnectModeState = new ConnectModeState(); + /* Fetching IP after network connection (assoc+auth complete) */ + private HierarchicalState mConnectingState = new ConnectingState(); + /* Connected with IP addr */ + private HierarchicalState mConnectedState = new ConnectedState(); + /* disconnect issued, waiting for network disconnect confirmation */ + private HierarchicalState mDisconnectingState = new DisconnectingState(); + /* Network is not connected, supplicant assoc+auth is not complete */ + private HierarchicalState mDisconnectedState = new DisconnectedState(); + + /* Soft Ap is running */ + private HierarchicalState mSoftApStartedState = new SoftApStartedState(); + + /* Argument for Message object to indicate a synchronous call */ + private static final int SYNCHRONOUS_CALL = 1; + private static final int ASYNCHRONOUS_CALL = 0; + + + /** + * One of {@link WifiManager#WIFI_STATE_DISABLED}, + * {@link WifiManager#WIFI_STATE_DISABLING}, + * {@link WifiManager#WIFI_STATE_ENABLED}, + * {@link WifiManager#WIFI_STATE_ENABLING}, + * {@link WifiManager#WIFI_STATE_UNKNOWN} + * + */ + private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED); + + /** + * One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, + * {@link WifiManager#WIFI_AP_STATE_DISABLING}, + * {@link WifiManager#WIFI_AP_STATE_ENABLED}, + * {@link WifiManager#WIFI_AP_STATE_ENABLING}, + * {@link WifiManager#WIFI_AP_STATE_FAILED} + * + */ + private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED); + + private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid()); + private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid()); + + private final IBatteryStats mBatteryStats; + + public WifiStateMachine(Context context) { + super(TAG); + + mContext = context; + + mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, ""); + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + nwService = INetworkManagementService.Stub.asInterface(b); + + mWifiMonitor = new WifiMonitor(this); + mDhcpInfo = new DhcpInfo(); + mWifiInfo = new WifiInfo(); + mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0"); + mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler()); + + mBluetoothHeadset = new BluetoothHeadset(mContext, null); + mNetworkProperties = new NetworkProperties(); + + mNetworkInfo.setIsAvailable(false); + mNetworkProperties.clear(); + mLastBssid = null; + mLastNetworkId = -1; + mLastSignalLevel = -1; + + mScanResultCache = new LinkedHashMap<String, ScanResult>( + SCAN_RESULT_CACHE_SIZE, 0.75f, true) { + /* + * Limit the cache size by SCAN_RESULT_CACHE_SIZE + * elements + */ + @Override + public boolean removeEldestEntry(Map.Entry eldest) { + return SCAN_RESULT_CACHE_SIZE < this.size(); + } + }; + + mSettingsObserver = new SettingsObserver(new Handler()); + + PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + + addState(mDefaultState); + addState(mInitialState, mDefaultState); + addState(mDriverUnloadingState, mDefaultState); + addState(mDriverUnloadedState, mDefaultState); + addState(mDriverFailedState, mDriverUnloadedState); + addState(mDriverLoadingState, mDefaultState); + addState(mDriverLoadedState, mDefaultState); + addState(mWaitForSupState, mDriverLoadedState); + addState(mDriverSupReadyState, mDefaultState); + addState(mDriverStartingState, mDriverSupReadyState); + addState(mDriverStartedState, mDriverSupReadyState); + addState(mScanModeState, mDriverStartedState); + addState(mConnectModeState, mDriverStartedState); + addState(mConnectingState, mConnectModeState); + addState(mConnectedState, mConnectModeState); + addState(mDisconnectingState, mConnectModeState); + addState(mDisconnectedState, mConnectModeState); + addState(mDriverStoppingState, mDriverSupReadyState); + addState(mDriverStoppedState, mDriverSupReadyState); + addState(mSoftApStartedState, mDefaultState); + + setInitialState(mInitialState); + + if (DBG) setDbg(true); + + //start the state machine + start(); + } + + /********************************************************* + * Methods exposed for public use + ********************************************************/ + + /** + * TODO: doc + */ + public boolean pingSupplicant() { + return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue; + } + + /** + * TODO: doc + */ + public boolean startScan(boolean forceActive) { + return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ? + SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue; + } + + /** + * TODO: doc + */ + public void setWifiEnabled(boolean enable) { + mLastEnableUid.set(Binder.getCallingUid()); + if (enable) { + /* Argument is the state that is entered prior to load */ + sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0)); + sendMessage(CMD_START_SUPPLICANT); + } else { + sendMessage(CMD_STOP_SUPPLICANT); + /* Argument is the state that is entered upon success */ + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0)); + } + } + + /** + * TODO: doc + */ + public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) { + mLastApEnableUid.set(Binder.getCallingUid()); + if (enable) { + /* Argument is the state that is entered prior to load */ + sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0)); + sendMessage(obtainMessage(CMD_START_AP, wifiConfig)); + } else { + sendMessage(CMD_STOP_AP); + /* Argument is the state that is entered upon success */ + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0)); + } + } + + /** + * TODO: doc + */ + public int getWifiState() { + return mWifiState.get(); + } + + /** + * TODO: doc + */ + public String getWifiStateByName() { + switch (mWifiState.get()) { + case WIFI_STATE_DISABLING: + return "disabling"; + case WIFI_STATE_DISABLED: + return "disabled"; + case WIFI_STATE_ENABLING: + return "enabling"; + case WIFI_STATE_ENABLED: + return "enabled"; + case WIFI_STATE_UNKNOWN: + return "unknown state"; + default: + return "[invalid state]"; + } + } + + /** + * TODO: doc + */ + public int getWifiApState() { + return mWifiApState.get(); + } + + /** + * TODO: doc + */ + public String getWifiApStateByName() { + switch (mWifiApState.get()) { + case WIFI_AP_STATE_DISABLING: + return "disabling"; + case WIFI_AP_STATE_DISABLED: + return "disabled"; + case WIFI_AP_STATE_ENABLING: + return "enabling"; + case WIFI_AP_STATE_ENABLED: + return "enabled"; + case WIFI_AP_STATE_FAILED: + return "failed"; + default: + return "[invalid state]"; + } + } + + /** + * Get status information for the current connection, if any. + * @return a {@link WifiInfo} object containing information about the current connection + * + */ + public WifiInfo requestConnectionInfo() { + return mWifiInfo; + } + + public DhcpInfo getDhcpInfo() { + return mDhcpInfo; + } + + /** + * TODO: doc + */ + public void setDriverStart(boolean enable) { + if (enable) { + sendMessage(CMD_START_DRIVER); + } else { + sendMessage(CMD_STOP_DRIVER); + } + } + + /** + * TODO: doc + */ + public void setScanOnlyMode(boolean enable) { + if (enable) { + sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0)); + } else { + sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0)); + } + } + + /** + * TODO: doc + */ + public void setScanType(boolean active) { + if (active) { + sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_ACTIVE, 0)); + } else { + sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_PASSIVE, 0)); + } + } + + /** + * TODO: doc + */ + public List<ScanResult> getScanResultsList() { + return mScanResults; + } + + /** + * Disconnect from Access Point + */ + public boolean disconnectCommand() { + return sendSyncMessage(CMD_DISCONNECT).boolValue; + } + + /** + * Initiate a reconnection to AP + */ + public boolean reconnectCommand() { + return sendSyncMessage(CMD_RECONNECT).boolValue; + } + + /** + * Initiate a re-association to AP + */ + public boolean reassociateCommand() { + return sendSyncMessage(CMD_REASSOCIATE).boolValue; + } + + /** + * Add a network synchronously + * + * @return network id of the new network + */ + public int addOrUpdateNetwork(WifiConfiguration config) { + return sendSyncMessage(CMD_ADD_OR_UPDATE_NETWORK, config).intValue; + } + + public List<WifiConfiguration> getConfiguredNetworks() { + return sendSyncMessage(CMD_GET_NETWORK_CONFIG).configList; + } + + /** + * Delete a network + * + * @param networkId id of the network to be removed + */ + public boolean removeNetwork(int networkId) { + return sendSyncMessage(obtainMessage(CMD_REMOVE_NETWORK, networkId, 0)).boolValue; + } + + private class EnableNetParams { + private int netId; + private boolean disableOthers; + EnableNetParams(int n, boolean b) { + netId = n; + disableOthers = b; + } + } + /** + * Enable a network + * + * @param netId network id of the network + * @param disableOthers true, if all other networks have to be disabled + * @return {@code true} if the operation succeeds, {@code false} otherwise + */ + public boolean enableNetwork(int netId, boolean disableOthers) { + return sendSyncMessage(CMD_ENABLE_NETWORK, + new EnableNetParams(netId, disableOthers)).boolValue; + } + + /** + * Disable a network + * + * @param netId network id of the network + * @return {@code true} if the operation succeeds, {@code false} otherwise + */ + public boolean disableNetwork(int netId) { + return sendSyncMessage(obtainMessage(CMD_DISABLE_NETWORK, netId, 0)).boolValue; + } + + /** + * Blacklist a BSSID. This will avoid the AP if there are + * alternate APs to connect + * + * @param bssid BSSID of the network + */ + public void addToBlacklist(String bssid) { + sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid)); + } + + /** + * Clear the blacklist list + * + */ + public void clearBlacklist() { + sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST)); + } + + /** + * Get detailed status of the connection + * + * @return Example status result + * bssid=aa:bb:cc:dd:ee:ff + * ssid=TestNet + * id=3 + * pairwise_cipher=NONE + * group_cipher=NONE + * key_mgmt=NONE + * wpa_state=COMPLETED + * ip_address=X.X.X.X + */ + public String status() { + return sendSyncMessage(CMD_CONNECTION_STATUS).stringValue; + } + + public void enableRssiPolling(boolean enabled) { + sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0)); + } + /** + * Get RSSI to currently connected network + * + * @return RSSI value, -1 on failure + */ + public int getRssi() { + return sendSyncMessage(CMD_GET_RSSI).intValue; + } + + /** + * Get approx RSSI to currently connected network + * + * @return RSSI value, -1 on failure + */ + public int getRssiApprox() { + return sendSyncMessage(CMD_GET_RSSI_APPROX).intValue; + } + + /** + * Get link speed to currently connected network + * + * @return link speed, -1 on failure + */ + public int getLinkSpeed() { + return sendSyncMessage(CMD_GET_LINK_SPEED).intValue; + } + + /** + * Get MAC address of radio + * + * @return MAC address, null on failure + */ + public String getMacAddress() { + return sendSyncMessage(CMD_GET_MAC_ADDR).stringValue; + } + + /** + * Start packet filtering + */ + public void startPacketFiltering() { + sendMessage(CMD_START_PACKET_FILTERING); + } + + /** + * Stop packet filtering + */ + public void stopPacketFiltering() { + sendMessage(CMD_STOP_PACKET_FILTERING); + } + + /** + * Set power mode + * @param mode + * DRIVER_POWER_MODE_AUTO + * DRIVER_POWER_MODE_ACTIVE + */ + public void setPowerMode(int mode) { + sendMessage(obtainMessage(CMD_SET_POWER_MODE, mode, 0)); + } + + /** + * Set the number of allowed radio frequency channels from the system + * setting value, if any. + */ + public void setNumAllowedChannels() { + try { + setNumAllowedChannels( + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS)); + } catch (Settings.SettingNotFoundException e) { + if (mNumAllowedChannels != 0) { + setNumAllowedChannels(mNumAllowedChannels); + } + // otherwise, use the driver default + } + } + + /** + * Set the number of radio frequency channels that are allowed to be used + * in the current regulatory domain. + * @param numChannels the number of allowed channels. Must be greater than 0 + * and less than or equal to 16. + */ + public void setNumAllowedChannels(int numChannels) { + sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0)); + } + + /** + * Get number of allowed channels + * + * @return channel count, -1 on failure + * + * TODO: this is not a public API and needs to be removed in favor + * of asynchronous reporting. unused for now. + */ + public int getNumAllowedChannels() { + return -1; + } + + /** + * Set bluetooth coex mode: + * + * @param mode + * BLUETOOTH_COEXISTENCE_MODE_ENABLED + * BLUETOOTH_COEXISTENCE_MODE_DISABLED + * BLUETOOTH_COEXISTENCE_MODE_SENSE + */ + public void setBluetoothCoexistenceMode(int mode) { + sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0)); + } + + /** + * Enable or disable Bluetooth coexistence scan mode. When this mode is on, + * some of the low-level scan parameters used by the driver are changed to + * reduce interference with A2DP streaming. + * + * @param isBluetoothPlaying whether to enable or disable this mode + */ + public void setBluetoothScanMode(boolean isBluetoothPlaying) { + sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0)); + } + + /** + * Save configuration on supplicant + * + * @return {@code true} if the operation succeeds, {@code false} otherwise + * + * TODO: deprecate this + */ + public boolean saveConfig() { + return sendSyncMessage(CMD_SAVE_CONFIG).boolValue; + } + + /** + * TODO: doc + */ + public void requestCmWakeLock() { + sendMessage(CMD_REQUEST_CM_WAKELOCK); + } + + /********************************************************* + * Internal private functions + ********************************************************/ + + class SyncReturn { + boolean boolValue; + int intValue; + String stringValue; + Object objValue; + List<WifiConfiguration> configList; + } + + class SyncParams { + Object mParameter; + SyncReturn mSyncReturn; + SyncParams() { + mSyncReturn = new SyncReturn(); + } + SyncParams(Object p) { + mParameter = p; + mSyncReturn = new SyncReturn(); + } + } + + /** + * message.arg2 is reserved to indicate synchronized + * message.obj is used to store SyncParams + */ + private SyncReturn syncedSend(Message msg) { + SyncParams syncParams = (SyncParams) msg.obj; + msg.arg2 = SYNCHRONOUS_CALL; + synchronized(syncParams) { + if (DBG) Log.d(TAG, "syncedSend " + msg); + sendMessage(msg); + try { + syncParams.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "sendSyncMessage: unexpected interruption of wait()"); + return null; + } + } + return syncParams.mSyncReturn; + } + + private SyncReturn sendSyncMessage(Message msg) { + SyncParams syncParams = new SyncParams(); + msg.obj = syncParams; + return syncedSend(msg); + } + + private SyncReturn sendSyncMessage(int what, Object param) { + SyncParams syncParams = new SyncParams(param); + Message msg = obtainMessage(what, syncParams); + return syncedSend(msg); + } + + + private SyncReturn sendSyncMessage(int what) { + return sendSyncMessage(obtainMessage(what)); + } + + private void notifyOnMsgObject(Message msg) { + SyncParams syncParams = (SyncParams) msg.obj; + if (syncParams != null) { + synchronized(syncParams) { + if (DBG) Log.d(TAG, "notifyOnMsgObject " + msg); + syncParams.notify(); + } + } + else { + Log.e(TAG, "Error! syncParams in notifyOnMsgObject is null"); + } + } + + private void setWifiState(int wifiState) { + final int previousWifiState = mWifiState.get(); + + try { + if (wifiState == WIFI_STATE_ENABLED) { + mBatteryStats.noteWifiOn(mLastEnableUid.get()); + } else if (wifiState == WIFI_STATE_DISABLED) { + mBatteryStats.noteWifiOff(mLastEnableUid.get()); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to note battery stats in wifi"); + } + + mWifiState.set(wifiState); + + if (DBG) Log.d(TAG, "setWifiState: " + getWifiStateByName()); + + final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState); + intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState); + mContext.sendStickyBroadcast(intent); + } + + private void setWifiApState(int wifiApState) { + final int previousWifiApState = mWifiApState.get(); + + try { + if (wifiApState == WIFI_AP_STATE_ENABLED) { + mBatteryStats.noteWifiOn(mLastApEnableUid.get()); + } else if (wifiApState == WIFI_AP_STATE_DISABLED) { + mBatteryStats.noteWifiOff(mLastApEnableUid.get()); + } + } catch (RemoteException e) { + Log.d(TAG, "Failed to note battery stats in wifi"); + } + + // Update state + mWifiApState.set(wifiApState); + + if (DBG) Log.d(TAG, "setWifiApState: " + getWifiApStateByName()); + + final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState); + intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState); + mContext.sendStickyBroadcast(intent); + } + + /** + * Parse the scan result line passed to us by wpa_supplicant (helper). + * @param line the line to parse + * @return the {@link ScanResult} object + */ + private ScanResult parseScanResult(String line) { + ScanResult scanResult = null; + if (line != null) { + /* + * Cache implementation (LinkedHashMap) is not synchronized, thus, + * must synchronized here! + */ + synchronized (mScanResultCache) { + String[] result = scanResultPattern.split(line); + if (3 <= result.length && result.length <= 5) { + String bssid = result[0]; + // bssid | frequency | level | flags | ssid + int frequency; + int level; + try { + frequency = Integer.parseInt(result[1]); + level = Integer.parseInt(result[2]); + /* some implementations avoid negative values by adding 256 + * so we need to adjust for that here. + */ + if (level > 0) level -= 256; + } catch (NumberFormatException e) { + frequency = 0; + level = 0; + } + + /* + * The formatting of the results returned by + * wpa_supplicant is intended to make the fields + * line up nicely when printed, + * not to make them easy to parse. So we have to + * apply some heuristics to figure out which field + * is the SSID and which field is the flags. + */ + String ssid; + String flags; + if (result.length == 4) { + if (result[3].charAt(0) == '[') { + flags = result[3]; + ssid = ""; + } else { + flags = ""; + ssid = result[3]; + } + } else if (result.length == 5) { + flags = result[3]; + ssid = result[4]; + } else { + // Here, we must have 3 fields: no flags and ssid + // set + flags = ""; + ssid = ""; + } + + // bssid + ssid is the hash key + String key = bssid + ssid; + scanResult = mScanResultCache.get(key); + if (scanResult != null) { + scanResult.level = level; + scanResult.SSID = ssid; + scanResult.capabilities = flags; + scanResult.frequency = frequency; + } else { + // Do not add scan results that have no SSID set + if (0 < ssid.trim().length()) { + scanResult = + new ScanResult( + ssid, bssid, flags, level, frequency); + mScanResultCache.put(key, scanResult); + } + } + } else { + Log.w(TAG, "Misformatted scan result text with " + + result.length + " fields: " + line); + } + } + } + + return scanResult; + } + + /** + * scanResults input format + * 00:bb:cc:dd:cc:ee 2427 166 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net1 + * 00:bb:cc:dd:cc:ff 2412 165 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net2 + */ + private void setScanResults(String scanResults) { + if (scanResults == null) { + return; + } + + List<ScanResult> scanList = new ArrayList<ScanResult>(); + + int lineCount = 0; + + int scanResultsLen = scanResults.length(); + // Parse the result string, keeping in mind that the last line does + // not end with a newline. + for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) { + if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') { + ++lineCount; + + if (lineCount == 1) { + lineBeg = lineEnd + 1; + continue; + } + if (lineEnd > lineBeg) { + String line = scanResults.substring(lineBeg, lineEnd); + ScanResult scanResult = parseScanResult(line); + if (scanResult != null) { + scanList.add(scanResult); + } else { + Log.w(TAG, "misformatted scan result for: " + line); + } + } + lineBeg = lineEnd + 1; + } + } + + mScanResults = scanList; + } + + private void configureNetworkProperties() { + try { + mNetworkProperties.setInterface(NetworkInterface.getByName(mInterfaceName)); + } catch (SocketException e) { + Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName + + ". e=" + e); + return; + } catch (NullPointerException e) { + Log.e(TAG, "NPE creating NetworkInterface. e=" + e); + return; + } + // TODO - fix this for v6 + try { + mNetworkProperties.addAddress(InetAddress.getByAddress( + NetworkUtils.v4IntToArray(mDhcpInfo.ipAddress))); + } catch (UnknownHostException e) { + Log.e(TAG, "Exception setting IpAddress using " + mDhcpInfo + ", e=" + e); + } + + try { + mNetworkProperties.setGateway(InetAddress.getByAddress(NetworkUtils.v4IntToArray( + mDhcpInfo.gateway))); + } catch (UnknownHostException e) { + Log.e(TAG, "Exception setting Gateway using " + mDhcpInfo + ", e=" + e); + } + + try { + mNetworkProperties.addDns(InetAddress.getByAddress( + NetworkUtils.v4IntToArray(mDhcpInfo.dns1))); + } catch (UnknownHostException e) { + Log.e(TAG, "Exception setting Dns1 using " + mDhcpInfo + ", e=" + e); + } + try { + mNetworkProperties.addDns(InetAddress.getByAddress( + NetworkUtils.v4IntToArray(mDhcpInfo.dns2))); + + } catch (UnknownHostException e) { + Log.e(TAG, "Exception setting Dns2 using " + mDhcpInfo + ", e=" + e); + } + // TODO - add proxy info + } + + + private void checkUseStaticIp() { + mUseStaticIp = false; + final ContentResolver cr = mContext.getContentResolver(); + try { + if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) { + return; + } + } catch (Settings.SettingNotFoundException e) { + return; + } + + try { + String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP); + if (addr != null) { + mDhcpInfo.ipAddress = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY); + if (addr != null) { + mDhcpInfo.gateway = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK); + if (addr != null) { + mDhcpInfo.netmask = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1); + if (addr != null) { + mDhcpInfo.dns1 = stringToIpAddr(addr); + } else { + return; + } + addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2); + if (addr != null) { + mDhcpInfo.dns2 = stringToIpAddr(addr); + } else { + mDhcpInfo.dns2 = 0; + } + } catch (UnknownHostException e) { + return; + } + mUseStaticIp = true; + } + + private static int stringToIpAddr(String addrString) throws UnknownHostException { + try { + String[] parts = addrString.split("\\."); + if (parts.length != 4) { + throw new UnknownHostException(addrString); + } + + int a = Integer.parseInt(parts[0]) ; + int b = Integer.parseInt(parts[1]) << 8; + int c = Integer.parseInt(parts[2]) << 16; + int d = Integer.parseInt(parts[3]) << 24; + + return a | b | c | d; + } catch (NumberFormatException ex) { + throw new UnknownHostException(addrString); + } + } + + private int getMaxDhcpRetries() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT, + DEFAULT_MAX_DHCP_RETRIES); + } + + private class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_USE_STATIC_IP), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_IP), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_GATEWAY), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_NETMASK), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_DNS1), false, this); + cr.registerContentObserver(Settings.System.getUriFor( + Settings.System.WIFI_STATIC_DNS2), false, this); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + boolean wasStaticIp = mUseStaticIp; + int oIp, oGw, oMsk, oDns1, oDns2; + oIp = oGw = oMsk = oDns1 = oDns2 = 0; + if (wasStaticIp) { + oIp = mDhcpInfo.ipAddress; + oGw = mDhcpInfo.gateway; + oMsk = mDhcpInfo.netmask; + oDns1 = mDhcpInfo.dns1; + oDns2 = mDhcpInfo.dns2; + } + checkUseStaticIp(); + + if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) { + return; + } + + boolean changed = + (wasStaticIp != mUseStaticIp) || + (wasStaticIp && ( + oIp != mDhcpInfo.ipAddress || + oGw != mDhcpInfo.gateway || + oMsk != mDhcpInfo.netmask || + oDns1 != mDhcpInfo.dns1 || + oDns2 != mDhcpInfo.dns2)); + + if (changed) { + sendMessage(CMD_RECONFIGURE_IP); + mConfigChanged = true; + } + } + } + + /** + * Whether to disable coexistence mode while obtaining IP address. This + * logic will return true only if the current bluetooth + * headset/handsfree state is disconnected. This means if it is in an + * error state, we will NOT disable coexistence mode to err on the side + * of safety. + * + * @return Whether to disable coexistence mode. + */ + private boolean shouldDisableCoexistenceMode() { + int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset()); + return state == BluetoothHeadset.STATE_DISCONNECTED; + } + + private void checkIsBluetoothPlaying() { + boolean isBluetoothPlaying = false; + Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks(); + + for (BluetoothDevice device : connected) { + if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) { + isBluetoothPlaying = true; + break; + } + } + setBluetoothScanMode(isBluetoothPlaying); + } + + private void sendScanResultsAvailableBroadcast() { + if (!ActivityManagerNative.isSystemReady()) return; + + mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + } + + private void sendRssiChangeBroadcast(final int newRssi) { + if (!ActivityManagerNative.isSystemReady()) return; + + Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi); + mContext.sendBroadcast(intent); + } + + private void sendNetworkStateChangeBroadcast(String bssid) { + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); + intent.putExtra(WifiManager.EXTRA_NETWORK_PROPERTIES, mNetworkProperties); + if (bssid != null) + intent.putExtra(WifiManager.EXTRA_BSSID, bssid); + mContext.sendStickyBroadcast(intent); + } + + private void sendConfigChangeBroadcast() { + Intent intent = new Intent(WifiManager.CONFIG_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_PROPERTIES, mNetworkProperties); + mContext.sendBroadcast(intent); + } + + private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) { + Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state); + if (failedAuth) { + intent.putExtra( + WifiManager.EXTRA_SUPPLICANT_ERROR, + WifiManager.ERROR_AUTHENTICATING); + } + mContext.sendStickyBroadcast(intent); + } + + private void sendSupplicantConnectionChangedBroadcast(boolean connected) { + if (!ActivityManagerNative.isSystemReady()) return; + + Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected); + mContext.sendBroadcast(intent); + } + + /** + * Record the detailed state of a network. + * @param state the new @{code DetailedState} + */ + private void setDetailedState(NetworkInfo.DetailedState state) { + Log.d(TAG, "setDetailed state, old =" + + mNetworkInfo.getDetailedState() + " and new state=" + state); + if (state != mNetworkInfo.getDetailedState()) { + mNetworkInfo.setDetailedState(state, null, null); + } + } + + private static String removeDoubleQuotes(String string) { + if (string.length() <= 2) return ""; + return string.substring(1, string.length() - 1); + } + + private static String convertToQuotedString(String string) { + return "\"" + string + "\""; + } + + private static String makeString(BitSet set, String[] strings) { + StringBuffer buf = new StringBuffer(); + int nextSetBit = -1; + + /* Make sure all set bits are in [0, strings.length) to avoid + * going out of bounds on strings. (Shouldn't happen, but...) */ + set = set.get(0, strings.length); + + while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) { + buf.append(strings[nextSetBit].replace('_', '-')).append(' '); + } + + // remove trailing space + if (set.cardinality() > 0) { + buf.setLength(buf.length() - 1); + } + + return buf.toString(); + } + + private static int lookupString(String string, String[] strings) { + int size = strings.length; + + string = string.replace('-', '_'); + + for (int i = 0; i < size; i++) + if (string.equals(strings[i])) + return i; + + // if we ever get here, we should probably add the + // value to WifiConfiguration to reflect that it's + // supported by the WPA supplicant + Log.w(TAG, "Failed to look-up a string: " + string); + + return -1; + } + + private int addOrUpdateNetworkNative(WifiConfiguration config) { + /* + * If the supplied networkId is -1, we create a new empty + * network configuration. Otherwise, the networkId should + * refer to an existing configuration. + */ + int netId = config.networkId; + boolean newNetwork = netId == -1; + // networkId of -1 means we want to create a new network + + if (newNetwork) { + netId = WifiNative.addNetworkCommand(); + if (netId < 0) { + Log.e(TAG, "Failed to add a network!"); + return -1; + } + } + + setVariables: { + + if (config.SSID != null && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.ssidVarName, + config.SSID)) { + Log.d(TAG, "failed to set SSID: "+config.SSID); + break setVariables; + } + + if (config.BSSID != null && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.bssidVarName, + config.BSSID)) { + Log.d(TAG, "failed to set BSSID: "+config.BSSID); + break setVariables; + } + + String allowedKeyManagementString = + makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); + if (config.allowedKeyManagement.cardinality() != 0 && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.KeyMgmt.varName, + allowedKeyManagementString)) { + Log.d(TAG, "failed to set key_mgmt: "+ + allowedKeyManagementString); + break setVariables; + } + + String allowedProtocolsString = + makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings); + if (config.allowedProtocols.cardinality() != 0 && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.Protocol.varName, + allowedProtocolsString)) { + Log.d(TAG, "failed to set proto: "+ + allowedProtocolsString); + break setVariables; + } + + String allowedAuthAlgorithmsString = + makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings); + if (config.allowedAuthAlgorithms.cardinality() != 0 && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.AuthAlgorithm.varName, + allowedAuthAlgorithmsString)) { + Log.d(TAG, "failed to set auth_alg: "+ + allowedAuthAlgorithmsString); + break setVariables; + } + + String allowedPairwiseCiphersString = + makeString(config.allowedPairwiseCiphers, + WifiConfiguration.PairwiseCipher.strings); + if (config.allowedPairwiseCiphers.cardinality() != 0 && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.PairwiseCipher.varName, + allowedPairwiseCiphersString)) { + Log.d(TAG, "failed to set pairwise: "+ + allowedPairwiseCiphersString); + break setVariables; + } + + String allowedGroupCiphersString = + makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings); + if (config.allowedGroupCiphers.cardinality() != 0 && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.GroupCipher.varName, + allowedGroupCiphersString)) { + Log.d(TAG, "failed to set group: "+ + allowedGroupCiphersString); + break setVariables; + } + + // Prevent client screw-up by passing in a WifiConfiguration we gave it + // by preventing "*" as a key. + if (config.preSharedKey != null && !config.preSharedKey.equals("*") && + !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.pskVarName, + config.preSharedKey)) { + Log.d(TAG, "failed to set psk: "+config.preSharedKey); + break setVariables; + } + + boolean hasSetKey = false; + if (config.wepKeys != null) { + for (int i = 0; i < config.wepKeys.length; i++) { + // Prevent client screw-up by passing in a WifiConfiguration we gave it + // by preventing "*" as a key. + if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) { + if (!WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.wepKeyVarNames[i], + config.wepKeys[i])) { + Log.d(TAG, + "failed to set wep_key"+i+": " + + config.wepKeys[i]); + break setVariables; + } + hasSetKey = true; + } + } + } + + if (hasSetKey) { + if (!WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.wepTxKeyIdxVarName, + Integer.toString(config.wepTxKeyIndex))) { + Log.d(TAG, + "failed to set wep_tx_keyidx: "+ + config.wepTxKeyIndex); + break setVariables; + } + } + + if (!WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.priorityVarName, + Integer.toString(config.priority))) { + Log.d(TAG, config.SSID + ": failed to set priority: " + +config.priority); + break setVariables; + } + + if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand( + netId, + WifiConfiguration.hiddenSSIDVarName, + Integer.toString(config.hiddenSSID ? 1 : 0))) { + Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+ + config.hiddenSSID); + break setVariables; + } + + for (WifiConfiguration.EnterpriseField field + : config.enterpriseFields) { + String varName = field.varName(); + String value = field.value(); + if (value != null) { + if (field != config.eap) { + value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); + } + if (!WifiNative.setNetworkVariableCommand( + netId, + varName, + value)) { + Log.d(TAG, config.SSID + ": failed to set " + varName + + ": " + value); + break setVariables; + } + } + } + return netId; + } + + if (newNetwork) { + WifiNative.removeNetworkCommand(netId); + Log.d(TAG, + "Failed to set a network variable, removed network: " + + netId); + } + + return -1; + } + + private List<WifiConfiguration> getConfiguredNetworksNative() { + String listStr = WifiNative.listNetworksCommand(); + + List<WifiConfiguration> networks = + new ArrayList<WifiConfiguration>(); + if (listStr == null) + return networks; + + String[] lines = listStr.split("\n"); + // Skip the first line, which is a header + for (int i = 1; i < lines.length; i++) { + String[] result = lines[i].split("\t"); + // network-id | ssid | bssid | flags + WifiConfiguration config = new WifiConfiguration(); + try { + config.networkId = Integer.parseInt(result[0]); + } catch(NumberFormatException e) { + continue; + } + if (result.length > 3) { + if (result[3].indexOf("[CURRENT]") != -1) + config.status = WifiConfiguration.Status.CURRENT; + else if (result[3].indexOf("[DISABLED]") != -1) + config.status = WifiConfiguration.Status.DISABLED; + else + config.status = WifiConfiguration.Status.ENABLED; + } else { + config.status = WifiConfiguration.Status.ENABLED; + } + readNetworkVariables(config); + networks.add(config); + } + return networks; + } + + /** + * Read the variables from the supplicant daemon that are needed to + * fill in the WifiConfiguration object. + * + * @param config the {@link WifiConfiguration} object to be filled in. + */ + private void readNetworkVariables(WifiConfiguration config) { + + int netId = config.networkId; + if (netId < 0) + return; + + /* + * TODO: maybe should have a native method that takes an array of + * variable names and returns an array of values. But we'd still + * be doing a round trip to the supplicant daemon for each variable. + */ + String value; + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName); + if (!TextUtils.isEmpty(value)) { + config.SSID = removeDoubleQuotes(value); + } else { + config.SSID = null; + } + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName); + if (!TextUtils.isEmpty(value)) { + config.BSSID = value; + } else { + config.BSSID = null; + } + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName); + config.priority = -1; + if (!TextUtils.isEmpty(value)) { + try { + config.priority = Integer.parseInt(value); + } catch (NumberFormatException ignore) { + } + } + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName); + config.hiddenSSID = false; + if (!TextUtils.isEmpty(value)) { + try { + config.hiddenSSID = Integer.parseInt(value) != 0; + } catch (NumberFormatException ignore) { + } + } + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName); + config.wepTxKeyIndex = -1; + if (!TextUtils.isEmpty(value)) { + try { + config.wepTxKeyIndex = Integer.parseInt(value); + } catch (NumberFormatException ignore) { + } + } + + for (int i = 0; i < 4; i++) { + value = WifiNative.getNetworkVariableCommand(netId, + WifiConfiguration.wepKeyVarNames[i]); + if (!TextUtils.isEmpty(value)) { + config.wepKeys[i] = value; + } else { + config.wepKeys[i] = null; + } + } + + value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName); + if (!TextUtils.isEmpty(value)) { + config.preSharedKey = value; + } else { + config.preSharedKey = null; + } + + value = WifiNative.getNetworkVariableCommand(config.networkId, + WifiConfiguration.Protocol.varName); + if (!TextUtils.isEmpty(value)) { + String vals[] = value.split(" "); + for (String val : vals) { + int index = + lookupString(val, WifiConfiguration.Protocol.strings); + if (0 <= index) { + config.allowedProtocols.set(index); + } + } + } + + value = WifiNative.getNetworkVariableCommand(config.networkId, + WifiConfiguration.KeyMgmt.varName); + if (!TextUtils.isEmpty(value)) { + String vals[] = value.split(" "); + for (String val : vals) { + int index = + lookupString(val, WifiConfiguration.KeyMgmt.strings); + if (0 <= index) { + config.allowedKeyManagement.set(index); + } + } + } + + value = WifiNative.getNetworkVariableCommand(config.networkId, + WifiConfiguration.AuthAlgorithm.varName); + if (!TextUtils.isEmpty(value)) { + String vals[] = value.split(" "); + for (String val : vals) { + int index = + lookupString(val, WifiConfiguration.AuthAlgorithm.strings); + if (0 <= index) { + config.allowedAuthAlgorithms.set(index); + } + } + } + + value = WifiNative.getNetworkVariableCommand(config.networkId, + WifiConfiguration.PairwiseCipher.varName); + if (!TextUtils.isEmpty(value)) { + String vals[] = value.split(" "); + for (String val : vals) { + int index = + lookupString(val, WifiConfiguration.PairwiseCipher.strings); + if (0 <= index) { + config.allowedPairwiseCiphers.set(index); + } + } + } + + value = WifiNative.getNetworkVariableCommand(config.networkId, + WifiConfiguration.GroupCipher.varName); + if (!TextUtils.isEmpty(value)) { + String vals[] = value.split(" "); + for (String val : vals) { + int index = + lookupString(val, WifiConfiguration.GroupCipher.strings); + if (0 <= index) { + config.allowedGroupCiphers.set(index); + } + } + } + + for (WifiConfiguration.EnterpriseField field : + config.enterpriseFields) { + value = WifiNative.getNetworkVariableCommand(netId, + field.varName()); + if (!TextUtils.isEmpty(value)) { + if (field != config.eap) value = removeDoubleQuotes(value); + field.setValue(value); + } + } + + } + + /** + * Poll for info not reported via events + * RSSI & Linkspeed + */ + private void requestPolledInfo() { + int newRssi = WifiNative.getRssiCommand(); + if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values + /* some implementations avoid negative values by adding 256 + * so we need to adjust for that here. + */ + if (newRssi > 0) newRssi -= 256; + mWifiInfo.setRssi(newRssi); + /* + * Rather then sending the raw RSSI out every time it + * changes, we precalculate the signal level that would + * be displayed in the status bar, and only send the + * broadcast if that much more coarse-grained number + * changes. This cuts down greatly on the number of + * broadcasts, at the cost of not mWifiInforming others + * interested in RSSI of all the changes in signal + * level. + */ + // TODO: The second arg to the call below needs to be a symbol somewhere, but + // it's actually the size of an array of icons that's private + // to StatusBar Policy. + int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); + if (newSignalLevel != mLastSignalLevel) { + sendRssiChangeBroadcast(newRssi); + } + mLastSignalLevel = newSignalLevel; + } else { + mWifiInfo.setRssi(-200); + } + int newLinkSpeed = WifiNative.getLinkSpeedCommand(); + if (newLinkSpeed != -1) { + mWifiInfo.setLinkSpeed(newLinkSpeed); + } + } + + /** + * Resets the Wi-Fi Connections by clearing any state, resetting any sockets + * using the interface, stopping DHCP & disabling interface + */ + private void handleNetworkDisconnect() { + Log.d(TAG, "Reset connections and stopping DHCP"); + + /* + * Reset connections & stop DHCP + */ + NetworkUtils.resetConnections(mInterfaceName); + + if (!NetworkUtils.stopDhcp(mInterfaceName)) { + Log.e(TAG, "Could not stop DHCP"); + } + + /* Disable interface */ + NetworkUtils.disableInterface(mInterfaceName); + + /* send event to CM & network change broadcast */ + setDetailedState(DetailedState.DISCONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + + /* Reset data structures */ + mWifiInfo.setIpAddress(0); + mWifiInfo.setBSSID(null); + mWifiInfo.setSSID(null); + mWifiInfo.setNetworkId(-1); + + /* Clear network properties */ + mNetworkProperties.clear(); + + mLastBssid= null; + mLastNetworkId = -1; + + } + + + /********************************************************* + * Notifications from WifiMonitor + ********************************************************/ + + /** + * A structure for supplying information about a supplicant state + * change in the STATE_CHANGE event message that comes from the + * WifiMonitor + * thread. + */ + private static class StateChangeResult { + StateChangeResult(int networkId, String BSSID, Object state) { + this.state = state; + this.BSSID = BSSID; + this.networkId = networkId; + } + int networkId; + String BSSID; + Object state; + } + + /** + * Send the tracker a notification that a user-entered password key + * may be incorrect (i.e., caused authentication to fail). + */ + void notifyPasswordKeyMayBeIncorrect() { + sendMessage(PASSWORD_MAY_BE_INCORRECT_EVENT); + } + + /** + * Send the tracker a notification that a connection to the supplicant + * daemon has been established. + */ + void notifySupplicantConnection() { + sendMessage(SUP_CONNECTION_EVENT); + } + + /** + * Send the tracker a notification that a connection to the supplicant + * daemon has been established. + */ + void notifySupplicantLost() { + sendMessage(SUP_DISCONNECTION_EVENT); + } + + /** + * Send the tracker a notification that the state of Wifi connectivity + * has changed. + * @param networkId the configured network on which the state change occurred + * @param newState the new network state + * @param BSSID when the new state is {@link DetailedState#CONNECTED + * NetworkInfo.DetailedState.CONNECTED}, + * this is the MAC address of the access point. Otherwise, it + * is {@code null}. + */ + void notifyNetworkStateChange(DetailedState newState, String BSSID, int networkId) { + if (newState == NetworkInfo.DetailedState.CONNECTED) { + sendMessage(obtainMessage(NETWORK_CONNECTION_EVENT, + new StateChangeResult(networkId, BSSID, newState))); + } else { + sendMessage(obtainMessage(NETWORK_DISCONNECTION_EVENT, + new StateChangeResult(networkId, BSSID, newState))); + } + } + + /** + * Send the tracker a notification that the state of the supplicant + * has changed. + * @param networkId the configured network on which the state change occurred + * @param newState the new {@code SupplicantState} + */ + void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) { + sendMessage(obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT, + new StateChangeResult(networkId, BSSID, newState))); + } + + /** + * Send the tracker a notification that a scan has completed, and results + * are available. + */ + void notifyScanResultsAvailable() { + /** + * Switch scan mode over to passive. + * Turning off scan-only mode happens only in "Connect" mode + */ + setScanType(false); + sendMessage(SCAN_RESULTS_EVENT); + } + + void notifyDriverStarted() { + sendMessage(DRIVER_START_EVENT); + } + + void notifyDriverStopped() { + sendMessage(DRIVER_STOP_EVENT); + } + + void notifyDriverHung() { + setWifiEnabled(false); + setWifiEnabled(true); + } + + + /******************************************************** + * HSM states + *******************************************************/ + + class DefaultState extends HierarchicalState { + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + SyncParams syncParams; + switch (message.what) { + /* Synchronous call returns */ + case CMD_PING_SUPPLICANT: + case CMD_START_SCAN: + case CMD_DISCONNECT: + case CMD_RECONNECT: + case CMD_REASSOCIATE: + case CMD_REMOVE_NETWORK: + case CMD_ENABLE_NETWORK: + case CMD_DISABLE_NETWORK: + case CMD_ADD_OR_UPDATE_NETWORK: + case CMD_GET_RSSI: + case CMD_GET_RSSI_APPROX: + case CMD_GET_LINK_SPEED: + case CMD_GET_MAC_ADDR: + case CMD_SAVE_CONFIG: + case CMD_CONNECTION_STATUS: + case CMD_GET_NETWORK_CONFIG: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = false; + syncParams.mSyncReturn.intValue = -1; + syncParams.mSyncReturn.stringValue = null; + syncParams.mSyncReturn.configList = null; + notifyOnMsgObject(message); + } + break; + case CMD_ENABLE_RSSI_POLL: + mEnableRssiPolling = (message.arg1 == 1); + mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL); + break; + /* Discard */ + case CMD_LOAD_DRIVER: + case CMD_UNLOAD_DRIVER: + case CMD_START_SUPPLICANT: + case CMD_STOP_SUPPLICANT: + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_START_AP: + case CMD_STOP_AP: + case CMD_RECONFIGURE_IP: + case SUP_CONNECTION_EVENT: + case SUP_DISCONNECTION_EVENT: + case DRIVER_START_EVENT: + case DRIVER_STOP_EVENT: + case NETWORK_CONNECTION_EVENT: + case NETWORK_DISCONNECTION_EVENT: + case SCAN_RESULTS_EVENT: + case SUPPLICANT_STATE_CHANGE_EVENT: + case PASSWORD_MAY_BE_INCORRECT_EVENT: + case CMD_BLACKLIST_NETWORK: + case CMD_CLEAR_BLACKLIST: + case CMD_SET_SCAN_MODE: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_REQUEST_CM_WAKELOCK: + break; + default: + Log.e(TAG, "Error! unhandled message" + message); + break; + } + return HANDLED; + } + } + + class InitialState extends HierarchicalState { + @Override + //TODO: could move logging into a common class + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + // [31-8] Reserved for future use + // [7 - 0] HSM state change + // 50021 wifi_state_changed (custom|1|5) + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + if (WifiNative.isDriverLoaded()) { + transitionTo(mDriverLoadedState); + } + else { + transitionTo(mDriverUnloadedState); + } + } + } + + class DriverLoadingState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + final Message message = new Message(); + message.copyFrom(getCurrentMessage()); + /* TODO: add a timeout to fail when driver load is hung. + * Similarly for driver unload. + */ + new Thread(new Runnable() { + public void run() { + sWakeLock.acquire(); + //enabling state + switch(message.arg1) { + case WIFI_STATE_ENABLING: + setWifiState(WIFI_STATE_ENABLING); + break; + case WIFI_AP_STATE_ENABLING: + setWifiApState(WIFI_AP_STATE_ENABLING); + break; + } + + if(WifiNative.loadDriver()) { + Log.d(TAG, "Driver load successful"); + sendMessage(CMD_LOAD_DRIVER_SUCCESS); + } else { + Log.e(TAG, "Failed to load driver!"); + switch(message.arg1) { + case WIFI_STATE_ENABLING: + setWifiState(WIFI_STATE_UNKNOWN); + break; + case WIFI_AP_STATE_ENABLING: + setWifiApState(WIFI_AP_STATE_FAILED); + break; + } + sendMessage(CMD_LOAD_DRIVER_FAILURE); + } + sWakeLock.release(); + } + }).start(); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_LOAD_DRIVER_SUCCESS: + transitionTo(mDriverLoadedState); + break; + case CMD_LOAD_DRIVER_FAILURE: + transitionTo(mDriverFailedState); + break; + case CMD_LOAD_DRIVER: + case CMD_UNLOAD_DRIVER: + case CMD_START_SUPPLICANT: + case CMD_STOP_SUPPLICANT: + case CMD_START_AP: + case CMD_STOP_AP: + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_SET_SCAN_MODE: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverLoadedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case CMD_UNLOAD_DRIVER: + transitionTo(mDriverUnloadingState); + break; + case CMD_START_SUPPLICANT: + if(WifiNative.startSupplicant()) { + Log.d(TAG, "Supplicant start successful"); + mWifiMonitor.startMonitoring(); + setWifiState(WIFI_STATE_ENABLED); + transitionTo(mWaitForSupState); + } else { + Log.e(TAG, "Failed to start supplicant!"); + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0)); + } + break; + case CMD_START_AP: + try { + nwService.startAccessPoint((WifiConfiguration) message.obj, + mInterfaceName, + SOFTAP_IFACE); + } catch(Exception e) { + Log.e(TAG, "Exception in startAccessPoint()"); + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0)); + break; + } + Log.d(TAG, "Soft AP start successful"); + setWifiApState(WIFI_AP_STATE_ENABLED); + transitionTo(mSoftApStartedState); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverUnloadingState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + final Message message = new Message(); + message.copyFrom(getCurrentMessage()); + new Thread(new Runnable() { + public void run() { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + sWakeLock.acquire(); + if(WifiNative.unloadDriver()) { + Log.d(TAG, "Driver unload successful"); + sendMessage(CMD_UNLOAD_DRIVER_SUCCESS); + + switch(message.arg1) { + case WIFI_STATE_DISABLED: + case WIFI_STATE_UNKNOWN: + setWifiState(message.arg1); + break; + case WIFI_AP_STATE_DISABLED: + case WIFI_AP_STATE_FAILED: + setWifiApState(message.arg1); + break; + } + } else { + Log.e(TAG, "Failed to unload driver!"); + sendMessage(CMD_UNLOAD_DRIVER_FAILURE); + + switch(message.arg1) { + case WIFI_STATE_DISABLED: + case WIFI_STATE_UNKNOWN: + setWifiState(WIFI_STATE_UNKNOWN); + break; + case WIFI_AP_STATE_DISABLED: + case WIFI_AP_STATE_FAILED: + setWifiApState(WIFI_AP_STATE_FAILED); + break; + } + } + sWakeLock.release(); + } + }).start(); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_UNLOAD_DRIVER_SUCCESS: + transitionTo(mDriverUnloadedState); + break; + case CMD_UNLOAD_DRIVER_FAILURE: + transitionTo(mDriverFailedState); + break; + case CMD_LOAD_DRIVER: + case CMD_UNLOAD_DRIVER: + case CMD_START_SUPPLICANT: + case CMD_STOP_SUPPLICANT: + case CMD_START_AP: + case CMD_STOP_AP: + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_SET_SCAN_MODE: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverUnloadedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_LOAD_DRIVER: + transitionTo(mDriverLoadingState); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverFailedState extends HierarchicalState { + @Override + public void enter() { + Log.e(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + return NOT_HANDLED; + } + } + + + class WaitForSupState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case SUP_CONNECTION_EVENT: + Log.d(TAG, "Supplicant connection established"); + mSupplicantStateTracker.resetSupplicantState(); + /* Initialize data structures */ + mLastBssid = null; + mLastNetworkId = -1; + mLastSignalLevel = -1; + + mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand()); + + //TODO: initialize and fix multicast filtering + //mWM.initializeMulticastFiltering(); + + if (mBluetoothA2dp == null) { + mBluetoothA2dp = new BluetoothA2dp(mContext); + } + checkIsBluetoothPlaying(); + + checkUseStaticIp(); + sendSupplicantConnectionChangedBroadcast(true); + transitionTo(mDriverSupReadyState); + break; + case CMD_STOP_SUPPLICANT: + Log.d(TAG, "Stop supplicant received"); + WifiNative.stopSupplicant(); + transitionTo(mDriverLoadedState); + break; + /* Fail soft ap when waiting for supplicant start */ + case CMD_START_AP: + Log.d(TAG, "Failed to start soft AP with a running supplicant"); + setWifiApState(WIFI_AP_STATE_FAILED); + break; + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_SET_SCAN_MODE: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + case CMD_STOP_AP: + case CMD_START_SUPPLICANT: + case CMD_UNLOAD_DRIVER: + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverSupReadyState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + /* Initialize for connect mode operation at start */ + mIsScanMode = false; + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + SyncParams syncParams; + switch(message.what) { + case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */ + Log.d(TAG, "Stop supplicant received"); + WifiNative.stopSupplicant(); + //$FALL-THROUGH$ + case SUP_DISCONNECTION_EVENT: /* Supplicant died */ + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + WifiNative.closeSupplicantConnection(); + handleNetworkDisconnect(); + sendSupplicantConnectionChangedBroadcast(false); + mSupplicantStateTracker.resetSupplicantState(); + transitionTo(mDriverLoadedState); + + /* When supplicant dies, unload driver and enter failed state */ + //TODO: consider bringing up supplicant again + if (message.what == SUP_DISCONNECTION_EVENT) { + Log.d(TAG, "Supplicant died, unloading driver"); + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0)); + } + break; + case CMD_START_DRIVER: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + WifiNative.startDriverCommand(); + transitionTo(mDriverStartingState); + break; + case SCAN_RESULTS_EVENT: + setScanResults(WifiNative.scanResultsCommand()); + sendScanResultsAvailableBroadcast(); + break; + case CMD_PING_SUPPLICANT: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.pingCommand(); + notifyOnMsgObject(message); + break; + case CMD_ADD_OR_UPDATE_NETWORK: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + syncParams = (SyncParams) message.obj; + WifiConfiguration config = (WifiConfiguration) syncParams.mParameter; + syncParams.mSyncReturn.intValue = addOrUpdateNetworkNative(config); + notifyOnMsgObject(message); + break; + case CMD_REMOVE_NETWORK: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.removeNetworkCommand( + message.arg1); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.removeNetworkCommand(message.arg1); + } + break; + case CMD_ENABLE_NETWORK: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + EnableNetParams enableNetParams = (EnableNetParams) syncParams.mParameter; + syncParams.mSyncReturn.boolValue = WifiNative.enableNetworkCommand( + enableNetParams.netId, enableNetParams.disableOthers); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.enableNetworkCommand(message.arg1, message.arg2 == 1); + } + break; + case CMD_DISABLE_NETWORK: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.disableNetworkCommand( + message.arg1); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.disableNetworkCommand(message.arg1); + } + break; + case CMD_BLACKLIST_NETWORK: + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + WifiNative.addToBlacklistCommand((String)message.obj); + break; + case CMD_CLEAR_BLACKLIST: + WifiNative.clearBlacklistCommand(); + break; + case CMD_GET_NETWORK_CONFIG: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.configList = getConfiguredNetworksNative(); + notifyOnMsgObject(message); + break; + case CMD_SAVE_CONFIG: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.saveConfigCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.saveConfigCommand(); + } + // Inform the backup manager about a data change + IBackupManager ibm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (ibm != null) { + try { + ibm.dataChanged("com.android.providers.settings"); + } catch (Exception e) { + // Try again later + } + } + break; + case CMD_CONNECTION_STATUS: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.stringValue = WifiNative.statusCommand(); + notifyOnMsgObject(message); + break; + case CMD_GET_MAC_ADDR: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.stringValue = WifiNative.getMacAddressCommand(); + notifyOnMsgObject(message); + break; + /* Cannot start soft AP while in client mode */ + case CMD_START_AP: + Log.d(TAG, "Failed to start soft AP with a running supplicant"); + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + setWifiApState(WIFI_AP_STATE_FAILED); + break; + case CMD_SET_SCAN_MODE: + mIsScanMode = (message.arg1 == SCAN_ONLY_MODE); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class DriverStartingState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case DRIVER_START_EVENT: + transitionTo(mDriverStartedState); + break; + /* Queue driver commands & connection events */ + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case SUPPLICANT_STATE_CHANGE_EVENT: + case NETWORK_CONNECTION_EVENT: + case NETWORK_DISCONNECTION_EVENT: + case PASSWORD_MAY_BE_INCORRECT_EVENT: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + /* Queue the asynchronous version of these commands */ + case CMD_START_SCAN: + case CMD_DISCONNECT: + case CMD_REASSOCIATE: + case CMD_RECONNECT: + if (message.arg2 != SYNCHRONOUS_CALL) { + deferMessage(message); + } + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverStartedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + try { + mBatteryStats.noteWifiRunning(); + } catch (RemoteException ignore) {} + + /* Initialize channel count */ + setNumAllowedChannels(); + + if (mIsScanMode) { + WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); + WifiNative.disconnectCommand(); + transitionTo(mScanModeState); + } else { + WifiNative.setScanResultHandlingCommand(CONNECT_MODE); + /* If supplicant has already connected, before we could finish establishing + * the control channel connection, we miss all the supplicant events. + * Disconnect and reconnect when driver has started to ensure we receive + * all supplicant events. + * + * TODO: This is a bit unclean, ideally the supplicant should never + * connect until told to do so by the framework + */ + WifiNative.disconnectCommand(); + WifiNative.reconnectCommand(); + transitionTo(mConnectModeState); + } + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + SyncParams syncParams; + switch(message.what) { + case CMD_SET_SCAN_TYPE: + if (message.arg1 == SCAN_ACTIVE) { + WifiNative.setScanModeCommand(true); + } else { + WifiNative.setScanModeCommand(false); + } + break; + case CMD_SET_POWER_MODE: + WifiNative.setPowerModeCommand(message.arg1); + break; + case CMD_SET_BLUETOOTH_COEXISTENCE: + WifiNative.setBluetoothCoexistenceModeCommand(message.arg1); + break; + case CMD_SET_BLUETOOTH_SCAN_MODE: + WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1); + break; + case CMD_SET_NUM_ALLOWED_CHANNELS: + mNumAllowedChannels = message.arg1; + WifiNative.setNumAllowedChannelsCommand(message.arg1); + break; + case CMD_START_DRIVER: + /* Ignore another driver start */ + break; + case CMD_STOP_DRIVER: + WifiNative.stopDriverCommand(); + transitionTo(mDriverStoppingState); + break; + case CMD_REQUEST_CM_WAKELOCK: + if (mCm == null) { + mCm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + } + mCm.requestNetworkTransitionWakelock(TAG); + break; + case CMD_START_PACKET_FILTERING: + WifiNative.startPacketFiltering(); + break; + case CMD_STOP_PACKET_FILTERING: + WifiNative.stopPacketFiltering(); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + @Override + public void exit() { + if (DBG) Log.d(TAG, getName() + "\n"); + try { + mBatteryStats.noteWifiStopped(); + } catch (RemoteException ignore) { } + } + } + + class DriverStoppingState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case DRIVER_STOP_EVENT: + transitionTo(mDriverStoppedState); + break; + /* Queue driver commands */ + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_SET_SCAN_TYPE: + case CMD_SET_POWER_MODE: + case CMD_SET_BLUETOOTH_COEXISTENCE: + case CMD_SET_BLUETOOTH_SCAN_MODE: + case CMD_SET_NUM_ALLOWED_CHANNELS: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + /* Queue the asynchronous version of these commands */ + case CMD_START_SCAN: + case CMD_DISCONNECT: + case CMD_REASSOCIATE: + case CMD_RECONNECT: + if (message.arg2 != SYNCHRONOUS_CALL) { + deferMessage(message); + } + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DriverStoppedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + return NOT_HANDLED; + } + } + + class ScanModeState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + SyncParams syncParams; + switch(message.what) { + case CMD_SET_SCAN_MODE: + if (message.arg1 == SCAN_ONLY_MODE) { + /* Ignore */ + return HANDLED; + } else { + WifiNative.setScanResultHandlingCommand(message.arg1); + WifiNative.reconnectCommand(); + mIsScanMode = false; + transitionTo(mDisconnectedState); + } + break; + case CMD_START_SCAN: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.scanCommand( + message.arg1 == SCAN_ACTIVE); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + } + break; + /* Ignore */ + case CMD_DISCONNECT: + case CMD_RECONNECT: + case CMD_REASSOCIATE: + case SUPPLICANT_STATE_CHANGE_EVENT: + case NETWORK_CONNECTION_EVENT: + case NETWORK_DISCONNECTION_EVENT: + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class ConnectModeState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + SyncParams syncParams; + StateChangeResult stateChangeResult; + switch(message.what) { + case PASSWORD_MAY_BE_INCORRECT_EVENT: + mPasswordKeyMayBeIncorrect = true; + break; + case SUPPLICANT_STATE_CHANGE_EVENT: + stateChangeResult = (StateChangeResult) message.obj; + mSupplicantStateTracker.handleEvent(stateChangeResult); + break; + case CMD_START_SCAN: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = true; + notifyOnMsgObject(message); + } + /* We need to set scan type in completed state */ + Message newMsg = obtainMessage(); + newMsg.copyFrom(message); + mSupplicantStateTracker.sendMessage(newMsg); + break; + /* Do a redundant disconnect without transition */ + case CMD_DISCONNECT: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.disconnectCommand(); + } + break; + case CMD_RECONNECT: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.reconnectCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.reconnectCommand(); + } + break; + case CMD_REASSOCIATE: + if (message.arg2 == SYNCHRONOUS_CALL) { + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.reassociateCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.reassociateCommand(); + } + break; + case SCAN_RESULTS_EVENT: + /* Set the scan setting back to "connect" mode */ + WifiNative.setScanResultHandlingCommand(CONNECT_MODE); + /* Handle scan results */ + return NOT_HANDLED; + case NETWORK_CONNECTION_EVENT: + Log.d(TAG,"Network connection established"); + stateChangeResult = (StateChangeResult) message.obj; + + mWifiInfo.setBSSID(mLastBssid = stateChangeResult.BSSID); + mWifiInfo.setNetworkId(stateChangeResult.networkId); + mLastNetworkId = stateChangeResult.networkId; + + /* send event to CM & network change broadcast */ + setDetailedState(DetailedState.OBTAINING_IPADDR); + sendNetworkStateChangeBroadcast(mLastBssid); + + transitionTo(mConnectingState); + break; + case NETWORK_DISCONNECTION_EVENT: + Log.d(TAG,"Network connection lost"); + handleNetworkDisconnect(); + transitionTo(mDisconnectedState); + break; + case CMD_GET_RSSI: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.intValue = WifiNative.getRssiCommand(); + notifyOnMsgObject(message); + break; + case CMD_GET_RSSI_APPROX: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.intValue = WifiNative.getRssiApproxCommand(); + notifyOnMsgObject(message); + break; + case CMD_GET_LINK_SPEED: + syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.intValue = WifiNative.getLinkSpeedCommand(); + notifyOnMsgObject(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class ConnectingState extends HierarchicalState { + boolean modifiedBluetoothCoexistenceMode; + int powerMode; + Thread mDhcpThread; + + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + if (!mUseStaticIp) { + + mDhcpThread = null; + modifiedBluetoothCoexistenceMode = false; + powerMode = DRIVER_POWER_MODE_AUTO; + + if (shouldDisableCoexistenceMode()) { + /* + * There are problems setting the Wi-Fi driver's power + * mode to active when bluetooth coexistence mode is + * enabled or sense. + * <p> + * We set Wi-Fi to active mode when + * obtaining an IP address because we've found + * compatibility issues with some routers with low power + * mode. + * <p> + * In order for this active power mode to properly be set, + * we disable coexistence mode until we're done with + * obtaining an IP address. One exception is if we + * are currently connected to a headset, since disabling + * coexistence would interrupt that connection. + */ + modifiedBluetoothCoexistenceMode = true; + + // Disable the coexistence mode + WifiNative.setBluetoothCoexistenceModeCommand( + WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED); + } + + powerMode = WifiNative.getPowerModeCommand(); + if (powerMode < 0) { + // Handle the case where supplicant driver does not support + // getPowerModeCommand. + powerMode = DRIVER_POWER_MODE_AUTO; + } + if (powerMode != DRIVER_POWER_MODE_ACTIVE) { + WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE); + } + + Log.d(TAG, "DHCP request started"); + mDhcpThread = new Thread(new Runnable() { + public void run() { + if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) { + Log.d(TAG, "DHCP request succeeded"); + sendMessage(CMD_IP_CONFIG_SUCCESS); + } else { + Log.d(TAG, "DHCP request failed: " + + NetworkUtils.getDhcpError()); + sendMessage(CMD_IP_CONFIG_FAILURE); + } + } + }); + mDhcpThread.start(); + } else { + if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) { + Log.v(TAG, "Static IP configuration succeeded"); + sendMessage(CMD_IP_CONFIG_SUCCESS); + } else { + Log.v(TAG, "Static IP configuration failed"); + sendMessage(CMD_IP_CONFIG_FAILURE); + } + } + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + + switch(message.what) { + case CMD_IP_CONFIG_SUCCESS: + mReconnectCount = 0; + mLastSignalLevel = -1; // force update of signal strength + mWifiInfo.setIpAddress(mDhcpInfo.ipAddress); + Log.d(TAG, "IP configuration: " + mDhcpInfo); + configureNetworkProperties(); + setDetailedState(DetailedState.CONNECTED); + sendNetworkStateChangeBroadcast(mLastBssid); + //TODO: we could also detect an IP config change + // from a DHCP renewal and send out a config change + // broadcast + if (mConfigChanged) { + sendConfigChangeBroadcast(); + mConfigChanged = false; + } + transitionTo(mConnectedState); + break; + case CMD_IP_CONFIG_FAILURE: + mWifiInfo.setIpAddress(0); + + Log.e(TAG, "IP configuration failed"); + /** + * If we've exceeded the maximum number of retries for DHCP + * to a given network, disable the network + */ + if (++mReconnectCount > getMaxDhcpRetries()) { + Log.e(TAG, "Failed " + + mReconnectCount + " times, Disabling " + mLastNetworkId); + WifiNative.disableNetworkCommand(mLastNetworkId); + } + + /* DHCP times out after about 30 seconds, we do a + * disconnect and an immediate reconnect to try again + */ + WifiNative.disconnectCommand(); + WifiNative.reconnectCommand(); + transitionTo(mDisconnectingState); + break; + case CMD_DISCONNECT: + if (message.arg2 == SYNCHRONOUS_CALL) { + SyncParams syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.disconnectCommand(); + } + transitionTo(mDisconnectingState); + break; + /* Ignore */ + case NETWORK_CONNECTION_EVENT: + break; + case CMD_STOP_DRIVER: + sendMessage(CMD_DISCONNECT); + deferMessage(message); + break; + case CMD_SET_SCAN_MODE: + if (message.arg1 == SCAN_ONLY_MODE) { + sendMessage(CMD_DISCONNECT); + deferMessage(message); + } + break; + case CMD_RECONFIGURE_IP: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + + @Override + public void exit() { + /* reset power state & bluetooth coexistence if on DHCP */ + if (!mUseStaticIp) { + if (powerMode != DRIVER_POWER_MODE_ACTIVE) { + WifiNative.setPowerModeCommand(powerMode); + } + + if (modifiedBluetoothCoexistenceMode) { + // Set the coexistence mode back to its default value + WifiNative.setBluetoothCoexistenceModeCommand( + WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE); + } + } + + } + } + + class ConnectedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_DISCONNECT: + if (message.arg2 == SYNCHRONOUS_CALL) { + SyncParams syncParams = (SyncParams) message.obj; + syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); + notifyOnMsgObject(message); + } else { + /* asynchronous handling */ + WifiNative.disconnectCommand(); + } + transitionTo(mDisconnectingState); + break; + case CMD_RECONFIGURE_IP: + Log.d(TAG,"Reconfiguring IP on connection"); + NetworkUtils.resetConnections(mInterfaceName); + transitionTo(mConnectingState); + break; + case CMD_STOP_DRIVER: + sendMessage(CMD_DISCONNECT); + deferMessage(message); + break; + case CMD_SET_SCAN_MODE: + if (message.arg1 == SCAN_ONLY_MODE) { + sendMessage(CMD_DISCONNECT); + deferMessage(message); + } + break; + /* Ignore */ + case NETWORK_CONNECTION_EVENT: + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DisconnectingState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_STOP_DRIVER: /* Stop driver only after disconnect handled */ + deferMessage(message); + break; + case CMD_SET_SCAN_MODE: + if (message.arg1 == SCAN_ONLY_MODE) { + deferMessage(message); + } + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class DisconnectedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_SET_SCAN_MODE: + if (message.arg1 == SCAN_ONLY_MODE) { + WifiNative.setScanResultHandlingCommand(message.arg1); + //Supplicant disconnect to prevent further connects + WifiNative.disconnectCommand(); + mIsScanMode = true; + transitionTo(mScanModeState); + } + break; + /* Ignore network disconnect */ + case NETWORK_DISCONNECTION_EVENT: + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class SoftApStartedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case CMD_STOP_AP: + Log.d(TAG,"Stopping Soft AP"); + setWifiApState(WIFI_AP_STATE_DISABLING); + try { + nwService.stopAccessPoint(); + } catch(Exception e) { + Log.e(TAG, "Exception in stopAccessPoint()"); + } + transitionTo(mDriverLoadedState); + break; + case CMD_START_AP: + Log.d(TAG,"SoftAP set on a running access point"); + try { + nwService.setAccessPoint((WifiConfiguration) message.obj, + mInterfaceName, + SOFTAP_IFACE); + } catch(Exception e) { + Log.e(TAG, "Exception in nwService during soft AP set"); + try { + nwService.stopAccessPoint(); + } catch (Exception ee) { + Slog.e(TAG, "Could not stop AP, :" + ee); + } + sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0)); + } + break; + /* Fail client mode operation when soft AP is enabled */ + case CMD_START_SUPPLICANT: + Log.e(TAG,"Cannot start supplicant with a running soft AP"); + setWifiState(WIFI_STATE_UNKNOWN); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + + class SupplicantStateTracker extends HierarchicalStateMachine { + + private int mRssiPollToken = 0; + + /** + * The max number of the WPA supplicant loop iterations before we + * decide that the loop should be terminated: + */ + private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; + private int mLoopDetectIndex = 0; + private int mLoopDetectCount = 0; + + /** + * Supplicant state change commands follow + * the ordinal values defined in SupplicantState.java + */ + private static final int DISCONNECTED = 0; + private static final int INACTIVE = 1; + private static final int SCANNING = 2; + private static final int ASSOCIATING = 3; + private static final int ASSOCIATED = 4; + private static final int FOUR_WAY_HANDSHAKE = 5; + private static final int GROUP_HANDSHAKE = 6; + private static final int COMPLETED = 7; + private static final int DORMANT = 8; + private static final int UNINITIALIZED = 9; + private static final int INVALID = 10; + + private HierarchicalState mUninitializedState = new UninitializedState(); + private HierarchicalState mInitializedState = new InitializedState();; + private HierarchicalState mInactiveState = new InactiveState(); + private HierarchicalState mDisconnectState = new DisconnectedState(); + private HierarchicalState mScanState = new ScanState(); + private HierarchicalState mConnectState = new ConnectState(); + private HierarchicalState mHandshakeState = new HandshakeState(); + private HierarchicalState mCompletedState = new CompletedState(); + private HierarchicalState mDormantState = new DormantState(); + + public SupplicantStateTracker(Context context, Handler target) { + super(TAG, target.getLooper()); + + addState(mUninitializedState); + addState(mInitializedState); + addState(mInactiveState, mInitializedState); + addState(mDisconnectState, mInitializedState); + addState(mScanState, mInitializedState); + addState(mConnectState, mInitializedState); + addState(mHandshakeState, mConnectState); + addState(mCompletedState, mConnectState); + addState(mDormantState, mInitializedState); + + setInitialState(mUninitializedState); + + //start the state machine + start(); + } + + public void handleEvent(StateChangeResult stateChangeResult) { + SupplicantState newState = (SupplicantState) stateChangeResult.state; + + // Supplicant state change + // [31-13] Reserved for future use + // [8 - 0] Supplicant state (as defined in SupplicantState.java) + // 50023 supplicant_state_changed (custom|1|5) + EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal()); + + sendMessage(obtainMessage(newState.ordinal(), stateChangeResult)); + } + + public void resetSupplicantState() { + transitionTo(mUninitializedState); + } + + private void resetLoopDetection() { + mLoopDetectCount = 0; + mLoopDetectIndex = 0; + } + + private boolean handleTransition(Message msg) { + if (DBG) Log.d(TAG, getName() + msg.toString() + "\n"); + switch (msg.what) { + case DISCONNECTED: + transitionTo(mDisconnectState); + break; + case SCANNING: + transitionTo(mScanState); + break; + case ASSOCIATING: + StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; + /* BSSID is valid only in ASSOCIATING state */ + mWifiInfo.setBSSID(stateChangeResult.BSSID); + //$FALL-THROUGH$ + case ASSOCIATED: + case FOUR_WAY_HANDSHAKE: + case GROUP_HANDSHAKE: + transitionTo(mHandshakeState); + break; + case COMPLETED: + transitionTo(mCompletedState); + break; + case DORMANT: + transitionTo(mDormantState); + break; + case INACTIVE: + transitionTo(mInactiveState); + break; + case UNINITIALIZED: + case INVALID: + transitionTo(mUninitializedState); + break; + default: + return NOT_HANDLED; + } + StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; + SupplicantState supState = (SupplicantState) stateChangeResult.state; + setDetailedState(WifiInfo.getDetailedStateOf(supState)); + mWifiInfo.setSupplicantState(supState); + mWifiInfo.setNetworkId(stateChangeResult.networkId); + //TODO: Modify WifiMonitor to report SSID on events + //mWifiInfo.setSSID() + return HANDLED; + } + + /******************************************************** + * HSM states + *******************************************************/ + + class InitializedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case CMD_START_SCAN: + WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + break; + default: + if (DBG) Log.w(TAG, "Ignoring " + message); + break; + } + return HANDLED; + } + } + + class UninitializedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + mNetworkInfo.setIsAvailable(false); + resetLoopDetection(); + mPasswordKeyMayBeIncorrect = false; + } + @Override + public boolean processMessage(Message message) { + switch(message.what) { + default: + if (!handleTransition(message)) { + if (DBG) Log.w(TAG, "Ignoring " + message); + } + break; + } + return HANDLED; + } + @Override + public void exit() { + mNetworkInfo.setIsAvailable(true); + } + } + + class InactiveState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + mNetworkInfo.setIsAvailable(false); + resetLoopDetection(); + mPasswordKeyMayBeIncorrect = false; + + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + } + @Override + public boolean processMessage(Message message) { + return handleTransition(message); + } + @Override + public void exit() { + mNetworkInfo.setIsAvailable(true); + } + } + + + class DisconnectedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + resetLoopDetection(); + + /* If a disconnect event happens after a password key failure + * event, disable the network + */ + if (mPasswordKeyMayBeIncorrect) { + Log.d(TAG, "Failed to authenticate, disabling network " + + mWifiInfo.getNetworkId()); + WifiNative.disableNetworkCommand(mWifiInfo.getNetworkId()); + mPasswordKeyMayBeIncorrect = false; + sendSupplicantStateChangedBroadcast(stateChangeResult, true); + } + else { + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + } + } + @Override + public boolean processMessage(Message message) { + return handleTransition(message); + } + } + + class ScanState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + mPasswordKeyMayBeIncorrect = false; + resetLoopDetection(); + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + } + @Override + public boolean processMessage(Message message) { + return handleTransition(message); + } + } + + class ConnectState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + } + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_START_SCAN: + WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); + WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class HandshakeState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + final Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + if (mLoopDetectIndex > message.what) { + mLoopDetectCount++; + } + if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { + WifiNative.disableNetworkCommand(stateChangeResult.networkId); + mLoopDetectCount = 0; + } + + mLoopDetectIndex = message.what; + + mPasswordKeyMayBeIncorrect = false; + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + } + @Override + public boolean processMessage(Message message) { + return handleTransition(message); + } + } + + class CompletedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + mRssiPollToken++; + if (mEnableRssiPolling) { + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), + POLL_RSSI_INTERVAL_MSECS); + } + + resetLoopDetection(); + + mPasswordKeyMayBeIncorrect = false; + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + } + @Override + public boolean processMessage(Message message) { + switch(message.what) { + case ASSOCIATING: + case ASSOCIATED: + case FOUR_WAY_HANDSHAKE: + case GROUP_HANDSHAKE: + case COMPLETED: + break; + case CMD_RSSI_POLL: + if (message.arg1 == mRssiPollToken) { + // Get Info and continue polling + requestPolledInfo(); + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), + POLL_RSSI_INTERVAL_MSECS); + } else { + // Polling has completed + } + break; + case CMD_ENABLE_RSSI_POLL: + mRssiPollToken++; + if (mEnableRssiPolling) { + // first poll + requestPolledInfo(); + sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), + POLL_RSSI_INTERVAL_MSECS); + } + break; + default: + return handleTransition(message); + } + return HANDLED; + } + } + + class DormantState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + resetLoopDetection(); + mPasswordKeyMayBeIncorrect = false; + + sendSupplicantStateChangedBroadcast(stateChangeResult, false); + + /* TODO: reconnect is now being handled at DHCP failure handling + * If we run into issues with staying in Dormant state, might + * need a reconnect here + */ + } + @Override + public boolean processMessage(Message message) { + return handleTransition(message); + } + } + } +} diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index 388beead9022..8e1b23697d14 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -16,524 +16,59 @@ package android.net.wifi; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; -/** - * TODO: Add soft AP states as part of WIFI_STATE_XXX - * Retain WIFI_STATE_ENABLING that indicates driver is loading - * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started - * and WIFI_STATE_FAILED for failure - * Deprecate WIFI_STATE_UNKNOWN - * - * Doing this will simplify the logic for sending broadcasts - */ -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; -import android.app.ActivityManagerNative; -import android.net.NetworkInfo; -import android.net.NetworkStateTracker; -import android.net.DhcpInfo; -import android.net.NetworkUtils; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.net.ConnectivityManager; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; +import android.net.NetworkInfo; import android.net.NetworkProperties; -import android.os.Binder; -import android.os.Message; -import android.os.Parcelable; +import android.net.NetworkStateTracker; import android.os.Handler; -import android.os.IBinder; -import android.os.INetworkManagementService; -import android.os.PowerManager; -import android.os.SystemProperties; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.Process; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.EventLog; -import android.util.Log; -import android.util.Slog; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.backup.IBackupManager; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothA2dp; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.Context; -import android.database.ContentObserver; -import com.android.internal.app.IBatteryStats; -import com.android.internal.util.HierarchicalState; -import com.android.internal.util.HierarchicalStateMachine; +import android.os.Message; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; /** - * Track the state of Wifi connectivity. All event handling is done here, - * and all changes in connectivity state are initiated here. + * Track the state of wifi for connectivity service. * * @hide */ -//TODO: we still need frequent scanning for the case when -// we issue disconnect but need scan results for open network notification -public class WifiStateTracker extends HierarchicalStateMachine implements NetworkStateTracker { +public class WifiStateTracker implements NetworkStateTracker { - private static final String TAG = "WifiStateTracker"; private static final String NETWORKTYPE = "WIFI"; - private static final boolean DBG = false; - - /* TODO: fetch a configurable interface */ - private static final String SOFTAP_IFACE = "wl0.1"; - - private WifiMonitor mWifiMonitor; - private INetworkManagementService nwService; - private ConnectivityManager mCm; - - /* Scan results handling */ - private List<ScanResult> mScanResults; - private static final Pattern scanResultPattern = Pattern.compile("\t+"); - private static final int SCAN_RESULT_CACHE_SIZE = 80; - private final LinkedHashMap<String, ScanResult> mScanResultCache; + private static final String TAG = "WifiStateTracker"; - private String mInterfaceName; private AtomicBoolean mTeardownRequested = new AtomicBoolean(false); private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false); private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0); private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false); - private int mNumAllowedChannels = 0; - private int mLastSignalLevel = -1; - private String mLastBssid; - private int mLastNetworkId; - private boolean mEnableRssiPolling = false; - private boolean mPasswordKeyMayBeIncorrect = false; - private boolean mUseStaticIp = false; - private int mReconnectCount = 0; - private boolean mIsScanMode = false; - - /** - * Instance of the bluetooth headset helper. This needs to be created - * early because there is a delay before it actually 'connects', as - * noted by its javadoc. If we check before it is connected, it will be - * in an error state and we will not disable coexistence. - */ - private BluetoothHeadset mBluetoothHeadset; - - private BluetoothA2dp mBluetoothA2dp; - - /** - * Observes the static IP address settings. - */ - private SettingsObserver mSettingsObserver; private NetworkProperties mNetworkProperties; - - - // Variables relating to the 'available networks' notification - /** - * The icon to show in the 'available networks' notification. This will also - * be the ID of the Notification given to the NotificationManager. - */ - private static final int ICON_NETWORKS_AVAILABLE = - com.android.internal.R.drawable.stat_notify_wifi_in_range; - /** - * When a notification is shown, we wait this amount before possibly showing it again. - */ - private final long NOTIFICATION_REPEAT_DELAY_MS; - /** - * Whether the user has set the setting to show the 'available networks' notification. - */ - private boolean mNotificationEnabled; - /** - * Observes the user setting to keep {@link #mNotificationEnabled} in sync. - */ - private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; - /** - * The {@link System#currentTimeMillis()} must be at least this value for us - * to show the notification again. - */ - private long mNotificationRepeatTime; - /** - * The Notification object given to the NotificationManager. - */ - private Notification mNotification; - /** - * Whether the notification is being shown, as set by us. That is, if the - * user cancels the notification, we will not receive the callback so this - * will still be true. We only guarantee if this is false, then the - * notification is not showing. - */ - private boolean mNotificationShown; - /** - * The number of continuous scans that must occur before consider the - * supplicant in a scanning state. This allows supplicant to associate with - * remembered networks that are in the scan results. - */ - private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3; - /** - * The number of scans since the last network state change. When this - * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the - * supplicant to actually be scanning. When the network state changes to - * something other than scanning, we reset this to 0. - */ - private int mNumScansSinceNetworkStateChange; - - // Held during driver load and unload - private static PowerManager.WakeLock sWakeLock; + private NetworkInfo mNetworkInfo; /* For sending events to connectivity service handler */ private Handler mCsHandler; private Context mContext; - - private DhcpInfo mDhcpInfo; - private WifiInfo mWifiInfo; - private NetworkInfo mNetworkInfo; - private SupplicantStateTracker mSupplicantStateTracker; - - // Event log tags (must be in sync with event-log-tags) - private static final int EVENTLOG_WIFI_STATE_CHANGED = 50021; - private static final int EVENTLOG_WIFI_EVENT_HANDLED = 50022; - private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50023; - - /* Load the driver */ - private static final int CMD_LOAD_DRIVER = 1; - /* Unload the driver */ - private static final int CMD_UNLOAD_DRIVER = 2; - /* Indicates driver load succeeded */ - private static final int CMD_LOAD_DRIVER_SUCCESS = 3; - /* Indicates driver load failed */ - private static final int CMD_LOAD_DRIVER_FAILURE = 4; - /* Indicates driver unload succeeded */ - private static final int CMD_UNLOAD_DRIVER_SUCCESS = 5; - /* Indicates driver unload failed */ - private static final int CMD_UNLOAD_DRIVER_FAILURE = 6; - - /* Start the supplicant */ - private static final int CMD_START_SUPPLICANT = 11; - /* Stop the supplicant */ - private static final int CMD_STOP_SUPPLICANT = 12; - /* Start the driver */ - private static final int CMD_START_DRIVER = 13; - /* Start the driver */ - private static final int CMD_STOP_DRIVER = 14; - /* Indicates DHCP succeded */ - private static final int CMD_IP_CONFIG_SUCCESS = 15; - /* Indicates DHCP failed */ - private static final int CMD_IP_CONFIG_FAILURE = 16; - /* Re-configure interface */ - private static final int CMD_RECONFIGURE_IP = 17; - - - /* Start the soft access point */ - private static final int CMD_START_AP = 21; - /* Stop the soft access point */ - private static final int CMD_STOP_AP = 22; - - - /* Supplicant events */ - /* Connection to supplicant established */ - private static final int SUP_CONNECTION_EVENT = 31; - /* Connection to supplicant lost */ - private static final int SUP_DISCONNECTION_EVENT = 32; - /* Driver start completed */ - private static final int DRIVER_START_EVENT = 33; - /* Driver stop completed */ - private static final int DRIVER_STOP_EVENT = 34; - /* Network connection completed */ - private static final int NETWORK_CONNECTION_EVENT = 36; - /* Network disconnection completed */ - private static final int NETWORK_DISCONNECTION_EVENT = 37; - /* Scan results are available */ - private static final int SCAN_RESULTS_EVENT = 38; - /* Supplicate state changed */ - private static final int SUPPLICANT_STATE_CHANGE_EVENT = 39; - /* Password may be incorrect */ - private static final int PASSWORD_MAY_BE_INCORRECT_EVENT = 40; - - /* Supplicant commands */ - /* Is supplicant alive ? */ - private static final int CMD_PING_SUPPLICANT = 51; - /* Add/update a network configuration */ - private static final int CMD_ADD_OR_UPDATE_NETWORK = 52; - /* Delete a network */ - private static final int CMD_REMOVE_NETWORK = 53; - /* Enable a network. The device will attempt a connection to the given network. */ - private static final int CMD_ENABLE_NETWORK = 54; - /* Disable a network. The device does not attempt a connection to the given network. */ - private static final int CMD_DISABLE_NETWORK = 55; - /* Blacklist network. De-prioritizes the given BSSID for connection. */ - private static final int CMD_BLACKLIST_NETWORK = 56; - /* Clear the blacklist network list */ - private static final int CMD_CLEAR_BLACKLIST = 57; - /* Get the configured networks */ - private static final int CMD_GET_NETWORK_CONFIG = 58; - /* Save configuration */ - private static final int CMD_SAVE_CONFIG = 59; - /* Connection status */ - private static final int CMD_CONNECTION_STATUS = 60; - - /* Supplicant commands after driver start*/ - /* Initiate a scan */ - private static final int CMD_START_SCAN = 71; - /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */ - private static final int CMD_SET_SCAN_MODE = 72; - /* Set scan type. SCAN_ACTIVE or SCAN_PASSIVE */ - private static final int CMD_SET_SCAN_TYPE = 73; - /* Disconnect from a network */ - private static final int CMD_DISCONNECT = 74; - /* Reconnect to a network */ - private static final int CMD_RECONNECT = 75; - /* Reassociate to a network */ - private static final int CMD_REASSOCIATE = 76; - /* Set power mode - * POWER_MODE_ACTIVE - * POWER_MODE_AUTO - */ - private static final int CMD_SET_POWER_MODE = 77; - /* Set bluetooth co-existence - * BLUETOOTH_COEXISTENCE_MODE_ENABLED - * BLUETOOTH_COEXISTENCE_MODE_DISABLED - * BLUETOOTH_COEXISTENCE_MODE_SENSE - */ - private static final int CMD_SET_BLUETOOTH_COEXISTENCE = 78; - /* Enable/disable bluetooth scan mode - * true(1) - * false(0) - */ - private static final int CMD_SET_BLUETOOTH_SCAN_MODE = 79; - /* Set number of allowed channels */ - private static final int CMD_SET_NUM_ALLOWED_CHANNELS = 80; - /* Request connectivity manager wake lock before driver stop */ - private static final int CMD_REQUEST_CM_WAKELOCK = 81; - /* Enables RSSI poll */ - private static final int CMD_ENABLE_RSSI_POLL = 82; - /* RSSI poll */ - private static final int CMD_RSSI_POLL = 83; - /* Get current RSSI */ - private static final int CMD_GET_RSSI = 84; - /* Get approx current RSSI */ - private static final int CMD_GET_RSSI_APPROX = 85; - /* Get link speed on connection */ - private static final int CMD_GET_LINK_SPEED = 86; - /* Radio mac address */ - private static final int CMD_GET_MAC_ADDR = 87; - /* Set up packet filtering */ - private static final int CMD_START_PACKET_FILTERING = 88; - /* Clear packet filter */ - private static final int CMD_STOP_PACKET_FILTERING = 89; - - /* Connectivity service commands */ - /* Bring down wifi connection */ - private static final int CM_CMD_TEARDOWN = 110; - /* Reconnect to wifi */ - private static final int CM_CMD_RECONNECT = 111; - - /** - * Interval in milliseconds between polling for connection - * status items that are not sent via asynchronous events. - * An example is RSSI (signal strength). - */ - private static final int POLL_RSSI_INTERVAL_MSECS = 3000; - - private static final int CONNECT_MODE = 1; - private static final int SCAN_ONLY_MODE = 2; - - private static final int SCAN_ACTIVE = 1; - private static final int SCAN_PASSIVE = 2; - - /** - * The maximum number of times we will retry a connection to an access point - * for which we have failed in acquiring an IP address from DHCP. A value of - * N means that we will make N+1 connection attempts in all. - * <p> - * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default - * value if a Settings value is not present. - */ - private static final int DEFAULT_MAX_DHCP_RETRIES = 9; - - private static final int DRIVER_POWER_MODE_ACTIVE = 1; - private static final int DRIVER_POWER_MODE_AUTO = 0; - - /* Default parent state */ - private HierarchicalState mDefaultState = new DefaultState(); - /* Temporary initial state */ - private HierarchicalState mInitialState = new InitialState(); - /* Unloading the driver */ - private HierarchicalState mDriverUnloadingState = new DriverUnloadingState(); - /* Loading the driver */ - private HierarchicalState mDriverUnloadedState = new DriverUnloadedState(); - /* Driver load/unload failed */ - private HierarchicalState mDriverFailedState = new DriverFailedState(); - /* Driver loading */ - private HierarchicalState mDriverLoadingState = new DriverLoadingState(); - /* Driver loaded */ - private HierarchicalState mDriverLoadedState = new DriverLoadedState(); - /* Driver loaded, waiting for supplicant to start */ - private HierarchicalState mWaitForSupState = new WaitForSupState(); - - /* Driver loaded and supplicant ready */ - private HierarchicalState mDriverSupReadyState = new DriverSupReadyState(); - /* Driver start issued, waiting for completed event */ - private HierarchicalState mDriverStartingState = new DriverStartingState(); - /* Driver started */ - private HierarchicalState mDriverStartedState = new DriverStartedState(); - /* Driver stopping */ - private HierarchicalState mDriverStoppingState = new DriverStoppingState(); - /* Driver stopped */ - private HierarchicalState mDriverStoppedState = new DriverStoppedState(); - /* Scan for networks, no connection will be established */ - private HierarchicalState mScanModeState = new ScanModeState(); - /* Connecting to an access point */ - private HierarchicalState mConnectModeState = new ConnectModeState(); - /* Fetching IP after network connection (assoc+auth complete) */ - private HierarchicalState mConnectingState = new ConnectingState(); - /* Connected with IP addr */ - private HierarchicalState mConnectedState = new ConnectedState(); - /* disconnect issued, waiting for network disconnect confirmation */ - private HierarchicalState mDisconnectingState = new DisconnectingState(); - /* Network is not connected, supplicant assoc+auth is not complete */ - private HierarchicalState mDisconnectedState = new DisconnectedState(); - - /* Soft Ap is running */ - private HierarchicalState mSoftApStartedState = new SoftApStartedState(); - - /* Argument for Message object to indicate a synchronous call */ - private static final int SYNCHRONOUS_CALL = 1; - private static final int ASYNCHRONOUS_CALL = 0; - - - /** - * One of {@link WifiManager#WIFI_STATE_DISABLED}, - * {@link WifiManager#WIFI_STATE_DISABLING}, - * {@link WifiManager#WIFI_STATE_ENABLED}, - * {@link WifiManager#WIFI_STATE_ENABLING}, - * {@link WifiManager#WIFI_STATE_UNKNOWN} - * - */ - private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED); - - /** - * One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, - * {@link WifiManager#WIFI_AP_STATE_DISABLING}, - * {@link WifiManager#WIFI_AP_STATE_ENABLED}, - * {@link WifiManager#WIFI_AP_STATE_ENABLING}, - * {@link WifiManager#WIFI_AP_STATE_FAILED} - * - */ - private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED); - - private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid()); - private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid()); - - private final IBatteryStats mBatteryStats; + private BroadcastReceiver mWifiStateReceiver; + private WifiManager mWifiManager; public WifiStateTracker(Context context, Handler target) { - super(NETWORKTYPE); - mCsHandler = target; mContext = context; mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, ""); - mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); - - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - nwService = INetworkManagementService.Stub.asInterface(b); - - mWifiMonitor = new WifiMonitor(this); - mDhcpInfo = new DhcpInfo(); - mWifiInfo = new WifiInfo(); - mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0"); - mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler()); - - mBluetoothHeadset = new BluetoothHeadset(mContext, null); mNetworkProperties = new NetworkProperties(); mNetworkInfo.setIsAvailable(false); mNetworkProperties.clear(); - resetNotificationTimer(); setTeardownRequested(false); - mLastBssid = null; - mLastNetworkId = -1; - mLastSignalLevel = -1; - - mScanResultCache = new LinkedHashMap<String, ScanResult>( - SCAN_RESULT_CACHE_SIZE, 0.75f, true) { - /* - * Limit the cache size by SCAN_RESULT_CACHE_SIZE - * elements - */ - @Override - public boolean removeEldestEntry(Map.Entry eldest) { - return SCAN_RESULT_CACHE_SIZE < this.size(); - } - }; - - // Setting is in seconds - NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; - mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(target); - mNotificationEnabledSettingObserver.register(); - - mSettingsObserver = new SettingsObserver(new Handler()); - - PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - - addState(mDefaultState); - addState(mInitialState, mDefaultState); - addState(mDriverUnloadingState, mDefaultState); - addState(mDriverUnloadedState, mDefaultState); - addState(mDriverFailedState, mDriverUnloadedState); - addState(mDriverLoadingState, mDefaultState); - addState(mDriverLoadedState, mDefaultState); - addState(mWaitForSupState, mDriverLoadedState); - addState(mDriverSupReadyState, mDefaultState); - addState(mDriverStartingState, mDriverSupReadyState); - addState(mDriverStartedState, mDriverSupReadyState); - addState(mScanModeState, mDriverStartedState); - addState(mConnectModeState, mDriverStartedState); - addState(mConnectingState, mConnectModeState); - addState(mConnectedState, mConnectModeState); - addState(mDisconnectingState, mConnectModeState); - addState(mDisconnectedState, mConnectModeState); - addState(mDriverStoppingState, mDriverSupReadyState); - addState(mDriverStoppedState, mDriverSupReadyState); - addState(mSoftApStartedState, mDefaultState); - - setInitialState(mInitialState); - - if (DBG) setDbg(true); - - //start the state machine - start(); } - /********************************************************* - * NetworkStateTracker interface implementation - ********************************************************/ - public void setTeardownRequested(boolean isRequested) { mTeardownRequested.set(isRequested); } @@ -544,11 +79,17 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ /** * Begin monitoring wifi connectivity - * @deprecated - * - * TODO: remove this from callers */ public void startMonitoring() { + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.CONFIG_CHANGED_ACTION); + filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + + mWifiStateReceiver = new WifiStateReceiver(); + mContext.registerReceiver(mWifiStateReceiver, filter); } /** @@ -556,7 +97,8 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ * TODO: do away with return value after making MobileDataStateTracker async */ public boolean teardown() { - sendMessage(CM_CMD_TEARDOWN); + mTeardownRequested.set(true); + mWifiManager.stopWifi(); return true; } @@ -565,7 +107,8 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ * TODO: do away with return value after making MobileDataStateTracker async */ public boolean reconnect() { - sendMessage(CM_CMD_RECONNECT); + mTeardownRequested.set(false); + mWifiManager.startWifi(); return true; } @@ -575,7 +118,7 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ * TODO: do away with return value after making MobileDataStateTracker async */ public boolean setRadio(boolean turnOn) { - setWifiEnabled(turnOn); + mWifiManager.setWifiEnabled(turnOn); return true; } @@ -626,21 +169,6 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ return -1; } - /* TODO: will go away. - * Notifications are directly handled in WifiStateTracker at checkAndSetNotification() - */ - public void interpretScanResultsAvailable() { - - } - - /** - * Return the name of our WLAN network interface. - * @return the name of our interface. - */ - public String getInterfaceName() { - return mInterfaceName; - } - /** * Check if private DNS route is set for the network */ @@ -698,3279 +226,23 @@ public class WifiStateTracker extends HierarchicalStateMachine implements Networ return "net.tcp.buffersize.wifi"; } - - /********************************************************* - * Methods exposed for public use - ********************************************************/ - - /** - * TODO: doc - */ - public boolean pingSupplicant() { - return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue; - } - - /** - * TODO: doc - */ - public boolean startScan(boolean forceActive) { - return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ? - SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue; - } - - /** - * TODO: doc - */ - public void setWifiEnabled(boolean enable) { - mLastEnableUid.set(Binder.getCallingUid()); - if (enable) { - /* Argument is the state that is entered prior to load */ - sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0)); - sendMessage(CMD_START_SUPPLICANT); - } else { - sendMessage(CMD_STOP_SUPPLICANT); - /* Argument is the state that is entered upon success */ - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0)); - } - } - - /** - * TODO: doc - */ - public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) { - mLastApEnableUid.set(Binder.getCallingUid()); - if (enable) { - /* Argument is the state that is entered prior to load */ - sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0)); - sendMessage(obtainMessage(CMD_START_AP, wifiConfig)); - } else { - sendMessage(CMD_STOP_AP); - /* Argument is the state that is entered upon success */ - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0)); - } - } - - /** - * TODO: doc - */ - public int getWifiState() { - return mWifiState.get(); - } - - /** - * TODO: doc - */ - public String getWifiStateByName() { - switch (mWifiState.get()) { - case WIFI_STATE_DISABLING: - return "disabling"; - case WIFI_STATE_DISABLED: - return "disabled"; - case WIFI_STATE_ENABLING: - return "enabling"; - case WIFI_STATE_ENABLED: - return "enabled"; - case WIFI_STATE_UNKNOWN: - return "unknown state"; - default: - return "[invalid state]"; - } - } - - /** - * TODO: doc - */ - public int getWifiApState() { - return mWifiApState.get(); - } - - /** - * TODO: doc - */ - public String getWifiApStateByName() { - switch (mWifiApState.get()) { - case WIFI_AP_STATE_DISABLING: - return "disabling"; - case WIFI_AP_STATE_DISABLED: - return "disabled"; - case WIFI_AP_STATE_ENABLING: - return "enabling"; - case WIFI_AP_STATE_ENABLED: - return "enabled"; - case WIFI_AP_STATE_FAILED: - return "failed"; - default: - return "[invalid state]"; - } - } - - /** - * Get status information for the current connection, if any. - * @return a {@link WifiInfo} object containing information about the current connection - * - */ - public WifiInfo requestConnectionInfo() { - return mWifiInfo; - } - - public DhcpInfo getDhcpInfo() { - return mDhcpInfo; - } - - /** - * TODO: doc - */ - public void startWifi(boolean enable) { - if (enable) { - sendMessage(CMD_START_DRIVER); - } else { - sendMessage(CMD_STOP_DRIVER); - } - } - - /** - * TODO: doc - */ - public void disconnectAndStop() { - sendMessage(CMD_DISCONNECT); - sendMessage(CMD_STOP_DRIVER); - } - - /** - * TODO: doc - */ - public void setScanOnlyMode(boolean enable) { - if (enable) { - sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0)); - } else { - sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0)); - } - } - - /** - * TODO: doc - */ - public void setScanType(boolean active) { - if (active) { - sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_ACTIVE, 0)); - } else { - sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_PASSIVE, 0)); - } - } - - /** - * TODO: doc - */ - public List<ScanResult> getScanResultsList() { - return mScanResults; - } - - /** - * Disconnect from Access Point - */ - public boolean disconnectCommand() { - return sendSyncMessage(CMD_DISCONNECT).boolValue; - } - - /** - * Initiate a reconnection to AP - */ - public boolean reconnectCommand() { - return sendSyncMessage(CMD_RECONNECT).boolValue; - } - - /** - * Initiate a re-association to AP - */ - public boolean reassociateCommand() { - return sendSyncMessage(CMD_REASSOCIATE).boolValue; - } - - /** - * Add a network synchronously - * - * @return network id of the new network - */ - public int addOrUpdateNetwork(WifiConfiguration config) { - return sendSyncMessage(CMD_ADD_OR_UPDATE_NETWORK, config).intValue; - } - - public List<WifiConfiguration> getConfiguredNetworks() { - return sendSyncMessage(CMD_GET_NETWORK_CONFIG).configList; - } - - /** - * Delete a network - * - * @param networkId id of the network to be removed - */ - public boolean removeNetwork(int networkId) { - return sendSyncMessage(obtainMessage(CMD_REMOVE_NETWORK, networkId, 0)).boolValue; - } - - private class EnableNetParams { - private int netId; - private boolean disableOthers; - EnableNetParams(int n, boolean b) { - netId = n; - disableOthers = b; - } - } - /** - * Enable a network - * - * @param netId network id of the network - * @param disableOthers true, if all other networks have to be disabled - * @return {@code true} if the operation succeeds, {@code false} otherwise - */ - public boolean enableNetwork(int netId, boolean disableOthers) { - return sendSyncMessage(CMD_ENABLE_NETWORK, - new EnableNetParams(netId, disableOthers)).boolValue; - } - - /** - * Disable a network - * - * @param netId network id of the network - * @return {@code true} if the operation succeeds, {@code false} otherwise - */ - public boolean disableNetwork(int netId) { - return sendSyncMessage(obtainMessage(CMD_DISABLE_NETWORK, netId, 0)).boolValue; - } - - /** - * Blacklist a BSSID. This will avoid the AP if there are - * alternate APs to connect - * - * @param bssid BSSID of the network - */ - public void addToBlacklist(String bssid) { - sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid)); - } - - /** - * Clear the blacklist list - * - */ - public void clearBlacklist() { - sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST)); - } - - /** - * Get detailed status of the connection - * - * @return Example status result - * bssid=aa:bb:cc:dd:ee:ff - * ssid=TestNet - * id=3 - * pairwise_cipher=NONE - * group_cipher=NONE - * key_mgmt=NONE - * wpa_state=COMPLETED - * ip_address=X.X.X.X - */ - public String status() { - return sendSyncMessage(CMD_CONNECTION_STATUS).stringValue; - } - - public void enableRssiPolling(boolean enabled) { - sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0)); - } - /** - * Get RSSI to currently connected network - * - * @return RSSI value, -1 on failure - */ - public int getRssi() { - return sendSyncMessage(CMD_GET_RSSI).intValue; - } - - /** - * Get approx RSSI to currently connected network - * - * @return RSSI value, -1 on failure - */ - public int getRssiApprox() { - return sendSyncMessage(CMD_GET_RSSI_APPROX).intValue; - } - - /** - * Get link speed to currently connected network - * - * @return link speed, -1 on failure - */ - public int getLinkSpeed() { - return sendSyncMessage(CMD_GET_LINK_SPEED).intValue; - } - - /** - * Get MAC address of radio - * - * @return MAC address, null on failure - */ - public String getMacAddress() { - return sendSyncMessage(CMD_GET_MAC_ADDR).stringValue; - } - - /** - * Start packet filtering - */ - public void startPacketFiltering() { - sendMessage(CMD_START_PACKET_FILTERING); - } - - /** - * Stop packet filtering - */ - public void stopPacketFiltering() { - sendMessage(CMD_STOP_PACKET_FILTERING); - } - - /** - * Set power mode - * @param mode - * DRIVER_POWER_MODE_AUTO - * DRIVER_POWER_MODE_ACTIVE - */ - public void setPowerMode(int mode) { - sendMessage(obtainMessage(CMD_SET_POWER_MODE, mode, 0)); - } - - /** - * Set the number of allowed radio frequency channels from the system - * setting value, if any. - */ - public void setNumAllowedChannels() { - try { - setNumAllowedChannels( - Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS)); - } catch (Settings.SettingNotFoundException e) { - if (mNumAllowedChannels != 0) { - setNumAllowedChannels(mNumAllowedChannels); - } - // otherwise, use the driver default - } - } - - /** - * Set the number of radio frequency channels that are allowed to be used - * in the current regulatory domain. - * @param numChannels the number of allowed channels. Must be greater than 0 - * and less than or equal to 16. - */ - public void setNumAllowedChannels(int numChannels) { - sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0)); - } - - /** - * Get number of allowed channels - * - * @return channel count, -1 on failure - * - * TODO: this is not a public API and needs to be removed in favor - * of asynchronous reporting. unused for now. - */ - public int getNumAllowedChannels() { - return -1; - } - - /** - * Set bluetooth coex mode: - * - * @param mode - * BLUETOOTH_COEXISTENCE_MODE_ENABLED - * BLUETOOTH_COEXISTENCE_MODE_DISABLED - * BLUETOOTH_COEXISTENCE_MODE_SENSE - */ - public void setBluetoothCoexistenceMode(int mode) { - sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0)); - } - - /** - * Enable or disable Bluetooth coexistence scan mode. When this mode is on, - * some of the low-level scan parameters used by the driver are changed to - * reduce interference with A2DP streaming. - * - * @param isBluetoothPlaying whether to enable or disable this mode - */ - public void setBluetoothScanMode(boolean isBluetoothPlaying) { - sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0)); - } - - /** - * Save configuration on supplicant - * - * @return {@code true} if the operation succeeds, {@code false} otherwise - * - * TODO: deprecate this - */ - public boolean saveConfig() { - return sendSyncMessage(CMD_SAVE_CONFIG).boolValue; - } - - /** - * TODO: doc - */ - public void requestCmWakeLock() { - sendMessage(CMD_REQUEST_CM_WAKELOCK); - } - - /********************************************************* - * Internal private functions - ********************************************************/ - - class SyncReturn { - boolean boolValue; - int intValue; - String stringValue; - Object objValue; - List<WifiConfiguration> configList; - } - - class SyncParams { - Object mParameter; - SyncReturn mSyncReturn; - SyncParams() { - mSyncReturn = new SyncReturn(); - } - SyncParams(Object p) { - mParameter = p; - mSyncReturn = new SyncReturn(); - } - } - - /** - * message.arg2 is reserved to indicate synchronized - * message.obj is used to store SyncParams - */ - private SyncReturn syncedSend(Message msg) { - SyncParams syncParams = (SyncParams) msg.obj; - msg.arg2 = SYNCHRONOUS_CALL; - synchronized(syncParams) { - if (DBG) Log.d(TAG, "syncedSend " + msg); - sendMessage(msg); - try { - syncParams.wait(); - } catch (InterruptedException e) { - Log.e(TAG, "sendSyncMessage: unexpected interruption of wait()"); - return null; - } - } - return syncParams.mSyncReturn; - } - - private SyncReturn sendSyncMessage(Message msg) { - SyncParams syncParams = new SyncParams(); - msg.obj = syncParams; - return syncedSend(msg); - } - - private SyncReturn sendSyncMessage(int what, Object param) { - SyncParams syncParams = new SyncParams(param); - Message msg = obtainMessage(what, syncParams); - return syncedSend(msg); - } - - - private SyncReturn sendSyncMessage(int what) { - return sendSyncMessage(obtainMessage(what)); - } - - private void notifyOnMsgObject(Message msg) { - SyncParams syncParams = (SyncParams) msg.obj; - if (syncParams != null) { - synchronized(syncParams) { - if (DBG) Log.d(TAG, "notifyOnMsgObject " + msg); - syncParams.notify(); - } - } - else { - Log.e(TAG, "Error! syncParams in notifyOnMsgObject is null"); - } - } - - private void setWifiState(int wifiState) { - final int previousWifiState = mWifiState.get(); - - try { - if (wifiState == WIFI_STATE_ENABLED) { - mBatteryStats.noteWifiOn(mLastEnableUid.get()); - } else if (wifiState == WIFI_STATE_DISABLED) { - mBatteryStats.noteWifiOff(mLastEnableUid.get()); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to note battery stats in wifi"); - } - - mWifiState.set(wifiState); - - if (DBG) Log.d(TAG, "setWifiState: " + getWifiStateByName()); - - final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState); - mContext.sendStickyBroadcast(intent); - } - - private void setWifiApState(int wifiApState) { - final int previousWifiApState = mWifiApState.get(); - - try { - if (wifiApState == WIFI_AP_STATE_ENABLED) { - mBatteryStats.noteWifiOn(mLastApEnableUid.get()); - } else if (wifiApState == WIFI_AP_STATE_DISABLED) { - mBatteryStats.noteWifiOff(mLastApEnableUid.get()); - } - } catch (RemoteException e) { - Log.d(TAG, "Failed to note battery stats in wifi"); - } - - // Update state - mWifiApState.set(wifiApState); - - if (DBG) Log.d(TAG, "setWifiApState: " + getWifiApStateByName()); - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState); - mContext.sendStickyBroadcast(intent); - } - - /** - * Parse the scan result line passed to us by wpa_supplicant (helper). - * @param line the line to parse - * @return the {@link ScanResult} object - */ - private ScanResult parseScanResult(String line) { - ScanResult scanResult = null; - if (line != null) { - /* - * Cache implementation (LinkedHashMap) is not synchronized, thus, - * must synchronized here! - */ - synchronized (mScanResultCache) { - String[] result = scanResultPattern.split(line); - if (3 <= result.length && result.length <= 5) { - String bssid = result[0]; - // bssid | frequency | level | flags | ssid - int frequency; - int level; - try { - frequency = Integer.parseInt(result[1]); - level = Integer.parseInt(result[2]); - /* some implementations avoid negative values by adding 256 - * so we need to adjust for that here. - */ - if (level > 0) level -= 256; - } catch (NumberFormatException e) { - frequency = 0; - level = 0; - } - - /* - * The formatting of the results returned by - * wpa_supplicant is intended to make the fields - * line up nicely when printed, - * not to make them easy to parse. So we have to - * apply some heuristics to figure out which field - * is the SSID and which field is the flags. - */ - String ssid; - String flags; - if (result.length == 4) { - if (result[3].charAt(0) == '[') { - flags = result[3]; - ssid = ""; - } else { - flags = ""; - ssid = result[3]; - } - } else if (result.length == 5) { - flags = result[3]; - ssid = result[4]; - } else { - // Here, we must have 3 fields: no flags and ssid - // set - flags = ""; - ssid = ""; - } - - // bssid + ssid is the hash key - String key = bssid + ssid; - scanResult = mScanResultCache.get(key); - if (scanResult != null) { - scanResult.level = level; - scanResult.SSID = ssid; - scanResult.capabilities = flags; - scanResult.frequency = frequency; - } else { - // Do not add scan results that have no SSID set - if (0 < ssid.trim().length()) { - scanResult = - new ScanResult( - ssid, bssid, flags, level, frequency); - mScanResultCache.put(key, scanResult); - } - } - } else { - Log.w(TAG, "Misformatted scan result text with " + - result.length + " fields: " + line); - } - } - } - - return scanResult; - } - - /** - * scanResults input format - * 00:bb:cc:dd:cc:ee 2427 166 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net1 - * 00:bb:cc:dd:cc:ff 2412 165 [WPA-EAP-TKIP][WPA2-EAP-CCMP] Net2 - */ - private void setScanResults(String scanResults) { - if (scanResults == null) { - return; - } - - List<ScanResult> scanList = new ArrayList<ScanResult>(); - - int lineCount = 0; - - int scanResultsLen = scanResults.length(); - // Parse the result string, keeping in mind that the last line does - // not end with a newline. - for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) { - if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') { - ++lineCount; - - if (lineCount == 1) { - lineBeg = lineEnd + 1; - continue; - } - if (lineEnd > lineBeg) { - String line = scanResults.substring(lineBeg, lineEnd); - ScanResult scanResult = parseScanResult(line); - if (scanResult != null) { - scanList.add(scanResult); - } else { - Log.w(TAG, "misformatted scan result for: " + line); - } - } - lineBeg = lineEnd + 1; - } - } - - mScanResults = scanList; - } - - private void checkAndSetNotification() { - // If we shouldn't place a notification on available networks, then - // don't bother doing any of the following - if (!mNotificationEnabled) return; - - State state = mNetworkInfo.getState(); - if ((state == NetworkInfo.State.DISCONNECTED) - || (state == NetworkInfo.State.UNKNOWN)) { - // Look for an open network - List<ScanResult> scanResults = mScanResults; - if (scanResults != null) { - int numOpenNetworks = 0; - for (int i = scanResults.size() - 1; i >= 0; i--) { - ScanResult scanResult = scanResults.get(i); - - if (TextUtils.isEmpty(scanResult.capabilities)) { - numOpenNetworks++; - } - } - - if (numOpenNetworks > 0) { - if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { - /* - * We've scanned continuously at least - * NUM_SCANS_BEFORE_NOTIFICATION times. The user - * probably does not have a remembered network in range, - * since otherwise supplicant would have tried to - * associate and thus resetting this counter. - */ - setNotificationVisible(true, numOpenNetworks, false, 0); - } - return; - } - } - } - - // No open networks in range, remove the notification - setNotificationVisible(false, 0, false, 0); - } - - /** - * Display or don't display a notification that there are open Wi-Fi networks. - * @param visible {@code true} if notification should be visible, {@code false} otherwise - * @param numNetworks the number networks seen - * @param force {@code true} to force notification to be shown/not-shown, - * even if it is already shown/not-shown. - * @param delay time in milliseconds after which the notification should be made - * visible or invisible. - */ - private void setNotificationVisible(boolean visible, int numNetworks, boolean force, - int delay) { - - // Since we use auto cancel on the notification, when the - // mNetworksAvailableNotificationShown is true, the notification may - // have actually been canceled. However, when it is false we know - // for sure that it is not being shown (it will not be shown any other - // place than here) - - // If it should be hidden and it is already hidden, then noop - if (!visible && !mNotificationShown && !force) { - return; - } - - Message message; - if (visible) { - - // Not enough time has passed to show the notification again - if (System.currentTimeMillis() < mNotificationRepeatTime) { - return; - } - - if (mNotification == null) { - // Cache the Notification mainly so we can remove the - // EVENT_NOTIFICATION_CHANGED message with this Notification from - // the queue later - mNotification = new Notification(); - mNotification.when = 0; - mNotification.icon = ICON_NETWORKS_AVAILABLE; - mNotification.flags = Notification.FLAG_AUTO_CANCEL; - mNotification.contentIntent = PendingIntent.getActivity(mContext, 0, - new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); - } - - CharSequence title = mContext.getResources().getQuantityText( - com.android.internal.R.plurals.wifi_available, numNetworks); - CharSequence details = mContext.getResources().getQuantityText( - com.android.internal.R.plurals.wifi_available_detailed, numNetworks); - mNotification.tickerText = title; - mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent); - - mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS; - - message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1, - ICON_NETWORKS_AVAILABLE, mNotification); - - } else { - - // Remove any pending messages to show the notification - mCsHandler.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification); - - message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, - ICON_NETWORKS_AVAILABLE); - } - - mCsHandler.sendMessageDelayed(message, delay); - - mNotificationShown = visible; - } - - private void configureNetworkProperties() { - try { - mNetworkProperties.setInterface(NetworkInterface.getByName(mInterfaceName)); - } catch (SocketException e) { - Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName + - ". e=" + e); - return; - } catch (NullPointerException e) { - Log.e(TAG, "NPE creating NetworkInterface. e=" + e); - return; - } - // TODO - fix this for v6 - try { - mNetworkProperties.addAddress(InetAddress.getByAddress( - NetworkUtils.v4IntToArray(mDhcpInfo.ipAddress))); - } catch (UnknownHostException e) { - Log.e(TAG, "Exception setting IpAddress using " + mDhcpInfo + ", e=" + e); - } - - try { - mNetworkProperties.setGateway(InetAddress.getByAddress(NetworkUtils.v4IntToArray( - mDhcpInfo.gateway))); - } catch (UnknownHostException e) { - Log.e(TAG, "Exception setting Gateway using " + mDhcpInfo + ", e=" + e); - } - - try { - mNetworkProperties.addDns(InetAddress.getByAddress( - NetworkUtils.v4IntToArray(mDhcpInfo.dns1))); - } catch (UnknownHostException e) { - Log.e(TAG, "Exception setting Dns1 using " + mDhcpInfo + ", e=" + e); - } - try { - mNetworkProperties.addDns(InetAddress.getByAddress( - NetworkUtils.v4IntToArray(mDhcpInfo.dns2))); - - } catch (UnknownHostException e) { - Log.e(TAG, "Exception setting Dns2 using " + mDhcpInfo + ", e=" + e); - } - // TODO - add proxy info - } - - - private void checkUseStaticIp() { - mUseStaticIp = false; - final ContentResolver cr = mContext.getContentResolver(); - try { - if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) { - return; - } - } catch (Settings.SettingNotFoundException e) { - return; - } - - try { - String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP); - if (addr != null) { - mDhcpInfo.ipAddress = stringToIpAddr(addr); - } else { - return; - } - addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY); - if (addr != null) { - mDhcpInfo.gateway = stringToIpAddr(addr); - } else { - return; - } - addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK); - if (addr != null) { - mDhcpInfo.netmask = stringToIpAddr(addr); - } else { - return; - } - addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1); - if (addr != null) { - mDhcpInfo.dns1 = stringToIpAddr(addr); - } else { - return; - } - addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2); - if (addr != null) { - mDhcpInfo.dns2 = stringToIpAddr(addr); - } else { - mDhcpInfo.dns2 = 0; - } - } catch (UnknownHostException e) { - return; - } - mUseStaticIp = true; - } - - private static int stringToIpAddr(String addrString) throws UnknownHostException { - try { - String[] parts = addrString.split("\\."); - if (parts.length != 4) { - throw new UnknownHostException(addrString); - } - - int a = Integer.parseInt(parts[0]) ; - int b = Integer.parseInt(parts[1]) << 8; - int c = Integer.parseInt(parts[2]) << 16; - int d = Integer.parseInt(parts[3]) << 24; - - return a | b | c | d; - } catch (NumberFormatException ex) { - throw new UnknownHostException(addrString); - } - } - - private int getMaxDhcpRetries() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT, - DEFAULT_MAX_DHCP_RETRIES); - } - - private class SettingsObserver extends ContentObserver { - public SettingsObserver(Handler handler) { - super(handler); - ContentResolver cr = mContext.getContentResolver(); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_USE_STATIC_IP), false, this); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_STATIC_IP), false, this); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_STATIC_GATEWAY), false, this); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_STATIC_NETMASK), false, this); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_STATIC_DNS1), false, this); - cr.registerContentObserver(Settings.System.getUriFor( - Settings.System.WIFI_STATIC_DNS2), false, this); - } - + private class WifiStateReceiver extends BroadcastReceiver { @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - - boolean wasStaticIp = mUseStaticIp; - int oIp, oGw, oMsk, oDns1, oDns2; - oIp = oGw = oMsk = oDns1 = oDns2 = 0; - if (wasStaticIp) { - oIp = mDhcpInfo.ipAddress; - oGw = mDhcpInfo.gateway; - oMsk = mDhcpInfo.netmask; - oDns1 = mDhcpInfo.dns1; - oDns2 = mDhcpInfo.dns2; - } - checkUseStaticIp(); - - if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) { - return; - } - - boolean changed = - (wasStaticIp != mUseStaticIp) || - (wasStaticIp && ( - oIp != mDhcpInfo.ipAddress || - oGw != mDhcpInfo.gateway || - oMsk != mDhcpInfo.netmask || - oDns1 != mDhcpInfo.dns1 || - oDns2 != mDhcpInfo.dns2)); - - if (changed) { - sendMessage(CMD_RECONFIGURE_IP); - if (mUseStaticIp) { - mCsHandler.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED); - } + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + mNetworkProperties = (NetworkProperties) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_PROPERTIES); + Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); + msg.sendToTarget(); + } else if (intent.getAction().equals(WifiManager.CONFIG_CHANGED_ACTION)) { + mNetworkProperties = (NetworkProperties) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_PROPERTIES); + Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo); + msg.sendToTarget(); } } } - - /** - * Clears variables related to tracking whether a notification has been - * shown recently. - * <p> - * After calling this method, the timer that prevents notifications from - * being shown too often will be cleared. - */ - private void resetNotificationTimer() { - mNotificationRepeatTime = 0; - mNumScansSinceNetworkStateChange = 0; - } - - - /** - * Whether to disable coexistence mode while obtaining IP address. This - * logic will return true only if the current bluetooth - * headset/handsfree state is disconnected. This means if it is in an - * error state, we will NOT disable coexistence mode to err on the side - * of safety. - * - * @return Whether to disable coexistence mode. - */ - private boolean shouldDisableCoexistenceMode() { - int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset()); - return state == BluetoothHeadset.STATE_DISCONNECTED; - } - - private void checkIsBluetoothPlaying() { - boolean isBluetoothPlaying = false; - Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks(); - - for (BluetoothDevice device : connected) { - if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) { - isBluetoothPlaying = true; - break; - } - } - setBluetoothScanMode(isBluetoothPlaying); - } - - private void sendScanResultsAvailableBroadcast() { - if (!ActivityManagerNative.isSystemReady()) return; - - mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); - } - - private void sendRssiChangeBroadcast(final int newRssi) { - if (!ActivityManagerNative.isSystemReady()) return; - - Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION); - intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi); - mContext.sendBroadcast(intent); - } - - private void sendNetworkStateChangeBroadcast(String bssid) { - Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo); - if (bssid != null) - intent.putExtra(WifiManager.EXTRA_BSSID, bssid); - mContext.sendStickyBroadcast(intent); - } - - private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) { - Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state); - if (failedAuth) { - intent.putExtra( - WifiManager.EXTRA_SUPPLICANT_ERROR, - WifiManager.ERROR_AUTHENTICATING); - } - mContext.sendStickyBroadcast(intent); - } - - private void sendSupplicantConnectionChangedBroadcast(boolean connected) { - if (!ActivityManagerNative.isSystemReady()) return; - - Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); - intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected); - mContext.sendBroadcast(intent); - } - - /** - * Record the detailed state of a network, and if it is a - * change from the previous state, send a notification to - * any listeners. - * @param state the new @{code DetailedState} - */ - private void setDetailedState(NetworkInfo.DetailedState state) { - Log.d(TAG, "setDetailed state, old =" - + mNetworkInfo.getDetailedState() + " and new state=" + state); - if (state != mNetworkInfo.getDetailedState()) { - mNetworkInfo.setDetailedState(state, null, null); - Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); - msg.sendToTarget(); - } - } - - private static String removeDoubleQuotes(String string) { - if (string.length() <= 2) return ""; - return string.substring(1, string.length() - 1); - } - - private static String convertToQuotedString(String string) { - return "\"" + string + "\""; - } - - private static String makeString(BitSet set, String[] strings) { - StringBuffer buf = new StringBuffer(); - int nextSetBit = -1; - - /* Make sure all set bits are in [0, strings.length) to avoid - * going out of bounds on strings. (Shouldn't happen, but...) */ - set = set.get(0, strings.length); - - while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) { - buf.append(strings[nextSetBit].replace('_', '-')).append(' '); - } - - // remove trailing space - if (set.cardinality() > 0) { - buf.setLength(buf.length() - 1); - } - - return buf.toString(); - } - - private static int lookupString(String string, String[] strings) { - int size = strings.length; - - string = string.replace('-', '_'); - - for (int i = 0; i < size; i++) - if (string.equals(strings[i])) - return i; - - // if we ever get here, we should probably add the - // value to WifiConfiguration to reflect that it's - // supported by the WPA supplicant - Log.w(TAG, "Failed to look-up a string: " + string); - - return -1; - } - - private int addOrUpdateNetworkNative(WifiConfiguration config) { - /* - * If the supplied networkId is -1, we create a new empty - * network configuration. Otherwise, the networkId should - * refer to an existing configuration. - */ - int netId = config.networkId; - boolean newNetwork = netId == -1; - // networkId of -1 means we want to create a new network - - if (newNetwork) { - netId = WifiNative.addNetworkCommand(); - if (netId < 0) { - Log.e(TAG, "Failed to add a network!"); - return -1; - } - } - - setVariables: { - - if (config.SSID != null && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.ssidVarName, - config.SSID)) { - Log.d(TAG, "failed to set SSID: "+config.SSID); - break setVariables; - } - - if (config.BSSID != null && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.bssidVarName, - config.BSSID)) { - Log.d(TAG, "failed to set BSSID: "+config.BSSID); - break setVariables; - } - - String allowedKeyManagementString = - makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); - if (config.allowedKeyManagement.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.KeyMgmt.varName, - allowedKeyManagementString)) { - Log.d(TAG, "failed to set key_mgmt: "+ - allowedKeyManagementString); - break setVariables; - } - - String allowedProtocolsString = - makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings); - if (config.allowedProtocols.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.Protocol.varName, - allowedProtocolsString)) { - Log.d(TAG, "failed to set proto: "+ - allowedProtocolsString); - break setVariables; - } - - String allowedAuthAlgorithmsString = - makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings); - if (config.allowedAuthAlgorithms.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.AuthAlgorithm.varName, - allowedAuthAlgorithmsString)) { - Log.d(TAG, "failed to set auth_alg: "+ - allowedAuthAlgorithmsString); - break setVariables; - } - - String allowedPairwiseCiphersString = - makeString(config.allowedPairwiseCiphers, - WifiConfiguration.PairwiseCipher.strings); - if (config.allowedPairwiseCiphers.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.PairwiseCipher.varName, - allowedPairwiseCiphersString)) { - Log.d(TAG, "failed to set pairwise: "+ - allowedPairwiseCiphersString); - break setVariables; - } - - String allowedGroupCiphersString = - makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings); - if (config.allowedGroupCiphers.cardinality() != 0 && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.GroupCipher.varName, - allowedGroupCiphersString)) { - Log.d(TAG, "failed to set group: "+ - allowedGroupCiphersString); - break setVariables; - } - - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.preSharedKey != null && !config.preSharedKey.equals("*") && - !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.pskVarName, - config.preSharedKey)) { - Log.d(TAG, "failed to set psk: "+config.preSharedKey); - break setVariables; - } - - boolean hasSetKey = false; - if (config.wepKeys != null) { - for (int i = 0; i < config.wepKeys.length; i++) { - // Prevent client screw-up by passing in a WifiConfiguration we gave it - // by preventing "*" as a key. - if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) { - if (!WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.wepKeyVarNames[i], - config.wepKeys[i])) { - Log.d(TAG, - "failed to set wep_key"+i+": " + - config.wepKeys[i]); - break setVariables; - } - hasSetKey = true; - } - } - } - - if (hasSetKey) { - if (!WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.wepTxKeyIdxVarName, - Integer.toString(config.wepTxKeyIndex))) { - Log.d(TAG, - "failed to set wep_tx_keyidx: "+ - config.wepTxKeyIndex); - break setVariables; - } - } - - if (!WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.priorityVarName, - Integer.toString(config.priority))) { - Log.d(TAG, config.SSID + ": failed to set priority: " - +config.priority); - break setVariables; - } - - if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand( - netId, - WifiConfiguration.hiddenSSIDVarName, - Integer.toString(config.hiddenSSID ? 1 : 0))) { - Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+ - config.hiddenSSID); - break setVariables; - } - - for (WifiConfiguration.EnterpriseField field - : config.enterpriseFields) { - String varName = field.varName(); - String value = field.value(); - if (value != null) { - if (field != config.eap) { - value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); - } - if (!WifiNative.setNetworkVariableCommand( - netId, - varName, - value)) { - Log.d(TAG, config.SSID + ": failed to set " + varName + - ": " + value); - break setVariables; - } - } - } - return netId; - } - - if (newNetwork) { - WifiNative.removeNetworkCommand(netId); - Log.d(TAG, - "Failed to set a network variable, removed network: " - + netId); - } - - return -1; - } - - private List<WifiConfiguration> getConfiguredNetworksNative() { - String listStr = WifiNative.listNetworksCommand(); - - List<WifiConfiguration> networks = - new ArrayList<WifiConfiguration>(); - if (listStr == null) - return networks; - - String[] lines = listStr.split("\n"); - // Skip the first line, which is a header - for (int i = 1; i < lines.length; i++) { - String[] result = lines[i].split("\t"); - // network-id | ssid | bssid | flags - WifiConfiguration config = new WifiConfiguration(); - try { - config.networkId = Integer.parseInt(result[0]); - } catch(NumberFormatException e) { - continue; - } - if (result.length > 3) { - if (result[3].indexOf("[CURRENT]") != -1) - config.status = WifiConfiguration.Status.CURRENT; - else if (result[3].indexOf("[DISABLED]") != -1) - config.status = WifiConfiguration.Status.DISABLED; - else - config.status = WifiConfiguration.Status.ENABLED; - } else { - config.status = WifiConfiguration.Status.ENABLED; - } - readNetworkVariables(config); - networks.add(config); - } - return networks; - } - - /** - * Read the variables from the supplicant daemon that are needed to - * fill in the WifiConfiguration object. - * - * @param config the {@link WifiConfiguration} object to be filled in. - */ - private void readNetworkVariables(WifiConfiguration config) { - - int netId = config.networkId; - if (netId < 0) - return; - - /* - * TODO: maybe should have a native method that takes an array of - * variable names and returns an array of values. But we'd still - * be doing a round trip to the supplicant daemon for each variable. - */ - String value; - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName); - if (!TextUtils.isEmpty(value)) { - config.SSID = removeDoubleQuotes(value); - } else { - config.SSID = null; - } - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName); - if (!TextUtils.isEmpty(value)) { - config.BSSID = value; - } else { - config.BSSID = null; - } - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName); - config.priority = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.priority = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName); - config.hiddenSSID = false; - if (!TextUtils.isEmpty(value)) { - try { - config.hiddenSSID = Integer.parseInt(value) != 0; - } catch (NumberFormatException ignore) { - } - } - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName); - config.wepTxKeyIndex = -1; - if (!TextUtils.isEmpty(value)) { - try { - config.wepTxKeyIndex = Integer.parseInt(value); - } catch (NumberFormatException ignore) { - } - } - - for (int i = 0; i < 4; i++) { - value = WifiNative.getNetworkVariableCommand(netId, - WifiConfiguration.wepKeyVarNames[i]); - if (!TextUtils.isEmpty(value)) { - config.wepKeys[i] = value; - } else { - config.wepKeys[i] = null; - } - } - - value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName); - if (!TextUtils.isEmpty(value)) { - config.preSharedKey = value; - } else { - config.preSharedKey = null; - } - - value = WifiNative.getNetworkVariableCommand(config.networkId, - WifiConfiguration.Protocol.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.Protocol.strings); - if (0 <= index) { - config.allowedProtocols.set(index); - } - } - } - - value = WifiNative.getNetworkVariableCommand(config.networkId, - WifiConfiguration.KeyMgmt.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.KeyMgmt.strings); - if (0 <= index) { - config.allowedKeyManagement.set(index); - } - } - } - - value = WifiNative.getNetworkVariableCommand(config.networkId, - WifiConfiguration.AuthAlgorithm.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.AuthAlgorithm.strings); - if (0 <= index) { - config.allowedAuthAlgorithms.set(index); - } - } - } - - value = WifiNative.getNetworkVariableCommand(config.networkId, - WifiConfiguration.PairwiseCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.PairwiseCipher.strings); - if (0 <= index) { - config.allowedPairwiseCiphers.set(index); - } - } - } - - value = WifiNative.getNetworkVariableCommand(config.networkId, - WifiConfiguration.GroupCipher.varName); - if (!TextUtils.isEmpty(value)) { - String vals[] = value.split(" "); - for (String val : vals) { - int index = - lookupString(val, WifiConfiguration.GroupCipher.strings); - if (0 <= index) { - config.allowedGroupCiphers.set(index); - } - } - } - - for (WifiConfiguration.EnterpriseField field : - config.enterpriseFields) { - value = WifiNative.getNetworkVariableCommand(netId, - field.varName()); - if (!TextUtils.isEmpty(value)) { - if (field != config.eap) value = removeDoubleQuotes(value); - field.setValue(value); - } - } - - } - - /** - * Poll for info not reported via events - * RSSI & Linkspeed - */ - private void requestPolledInfo() { - int newRssi = WifiNative.getRssiCommand(); - if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values - /* some implementations avoid negative values by adding 256 - * so we need to adjust for that here. - */ - if (newRssi > 0) newRssi -= 256; - mWifiInfo.setRssi(newRssi); - /* - * Rather then sending the raw RSSI out every time it - * changes, we precalculate the signal level that would - * be displayed in the status bar, and only send the - * broadcast if that much more coarse-grained number - * changes. This cuts down greatly on the number of - * broadcasts, at the cost of not mWifiInforming others - * interested in RSSI of all the changes in signal - * level. - */ - // TODO: The second arg to the call below needs to be a symbol somewhere, but - // it's actually the size of an array of icons that's private - // to StatusBar Policy. - int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); - if (newSignalLevel != mLastSignalLevel) { - sendRssiChangeBroadcast(newRssi); - } - mLastSignalLevel = newSignalLevel; - } else { - mWifiInfo.setRssi(-200); - } - int newLinkSpeed = WifiNative.getLinkSpeedCommand(); - if (newLinkSpeed != -1) { - mWifiInfo.setLinkSpeed(newLinkSpeed); - } - } - - /** - * Resets the Wi-Fi Connections by clearing any state, resetting any sockets - * using the interface, stopping DHCP & disabling interface - */ - private void handleNetworkDisconnect() { - Log.d(TAG, "Reset connections and stopping DHCP"); - - /* - * Reset connections & stop DHCP - */ - NetworkUtils.resetConnections(mInterfaceName); - - if (!NetworkUtils.stopDhcp(mInterfaceName)) { - Log.e(TAG, "Could not stop DHCP"); - } - - /* Disable interface */ - NetworkUtils.disableInterface(mInterfaceName); - - /* send event to CM & network change broadcast */ - setDetailedState(DetailedState.DISCONNECTED); - sendNetworkStateChangeBroadcast(mLastBssid); - - /* Reset data structures */ - mWifiInfo.setIpAddress(0); - mWifiInfo.setBSSID(null); - mWifiInfo.setSSID(null); - mWifiInfo.setNetworkId(-1); - - /* Clear network properties */ - mNetworkProperties.clear(); - - mLastBssid= null; - mLastNetworkId = -1; - - } - - - /********************************************************* - * Notifications from WifiMonitor - ********************************************************/ - - /** - * A structure for supplying information about a supplicant state - * change in the STATE_CHANGE event message that comes from the - * WifiMonitor - * thread. - */ - private static class StateChangeResult { - StateChangeResult(int networkId, String BSSID, Object state) { - this.state = state; - this.BSSID = BSSID; - this.networkId = networkId; - } - int networkId; - String BSSID; - Object state; - } - - /** - * Send the tracker a notification that a user-entered password key - * may be incorrect (i.e., caused authentication to fail). - */ - void notifyPasswordKeyMayBeIncorrect() { - sendMessage(PASSWORD_MAY_BE_INCORRECT_EVENT); - } - - /** - * Send the tracker a notification that a connection to the supplicant - * daemon has been established. - */ - void notifySupplicantConnection() { - sendMessage(SUP_CONNECTION_EVENT); - } - - /** - * Send the tracker a notification that a connection to the supplicant - * daemon has been established. - */ - void notifySupplicantLost() { - sendMessage(SUP_DISCONNECTION_EVENT); - } - - /** - * Send the tracker a notification that the state of Wifi connectivity - * has changed. - * @param networkId the configured network on which the state change occurred - * @param newState the new network state - * @param BSSID when the new state is {@link DetailedState#CONNECTED - * NetworkInfo.DetailedState.CONNECTED}, - * this is the MAC address of the access point. Otherwise, it - * is {@code null}. - */ - void notifyNetworkStateChange(DetailedState newState, String BSSID, int networkId) { - if (newState == NetworkInfo.DetailedState.CONNECTED) { - sendMessage(obtainMessage(NETWORK_CONNECTION_EVENT, - new StateChangeResult(networkId, BSSID, newState))); - } else { - sendMessage(obtainMessage(NETWORK_DISCONNECTION_EVENT, - new StateChangeResult(networkId, BSSID, newState))); - } - } - - /** - * Send the tracker a notification that the state of the supplicant - * has changed. - * @param networkId the configured network on which the state change occurred - * @param newState the new {@code SupplicantState} - */ - void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) { - sendMessage(obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT, - new StateChangeResult(networkId, BSSID, newState))); - } - - /** - * Send the tracker a notification that a scan has completed, and results - * are available. - */ - void notifyScanResultsAvailable() { - /** - * Switch scan mode over to passive. - * Turning off scan-only mode happens only in "Connect" mode - */ - setScanType(false); - sendMessage(SCAN_RESULTS_EVENT); - } - - void notifyDriverStarted() { - sendMessage(DRIVER_START_EVENT); - } - - void notifyDriverStopped() { - sendMessage(DRIVER_STOP_EVENT); - } - - void notifyDriverHung() { - setWifiEnabled(false); - setWifiEnabled(true); - } - - - /******************************************************** - * HSM states - *******************************************************/ - - class DefaultState extends HierarchicalState { - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - SyncParams syncParams; - switch (message.what) { - /* Synchronous call returns */ - case CMD_PING_SUPPLICANT: - case CMD_START_SCAN: - case CMD_DISCONNECT: - case CMD_RECONNECT: - case CMD_REASSOCIATE: - case CMD_REMOVE_NETWORK: - case CMD_ENABLE_NETWORK: - case CMD_DISABLE_NETWORK: - case CMD_ADD_OR_UPDATE_NETWORK: - case CMD_GET_RSSI: - case CMD_GET_RSSI_APPROX: - case CMD_GET_LINK_SPEED: - case CMD_GET_MAC_ADDR: - case CMD_SAVE_CONFIG: - case CMD_CONNECTION_STATUS: - case CMD_GET_NETWORK_CONFIG: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = false; - syncParams.mSyncReturn.intValue = -1; - syncParams.mSyncReturn.stringValue = null; - syncParams.mSyncReturn.configList = null; - notifyOnMsgObject(message); - } - break; - case CM_CMD_TEARDOWN: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - mTeardownRequested.set(true); - sendMessage(CMD_DISCONNECT); - sendMessage(CMD_STOP_DRIVER); - /* Mark wifi available when CM tears down */ - mNetworkInfo.setIsAvailable(true); - break; - case CM_CMD_RECONNECT: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - mTeardownRequested.set(false); - sendMessage(CMD_START_DRIVER); - sendMessage(CMD_RECONNECT); - break; - case CMD_ENABLE_RSSI_POLL: - mEnableRssiPolling = (message.arg1 == 1); - mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL); - break; - default: - if (DBG) Log.w(TAG, "Unhandled " + message); - break; - } - return HANDLED; - } - } - - class InitialState extends HierarchicalState { - @Override - //TODO: could move logging into a common class - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - // [31-8] Reserved for future use - // [7 - 0] HSM state change - // 50021 wifi_state_changed (custom|1|5) - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - if (WifiNative.isDriverLoaded()) { - transitionTo(mDriverLoadedState); - } - else { - transitionTo(mDriverUnloadedState); - } - } - } - - class DriverLoadingState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - final Message message = new Message(); - message.copyFrom(getCurrentMessage()); - new Thread(new Runnable() { - public void run() { - sWakeLock.acquire(); - //enabling state - switch(message.arg1) { - case WIFI_STATE_ENABLING: - setWifiState(WIFI_STATE_ENABLING); - break; - case WIFI_AP_STATE_ENABLING: - setWifiApState(WIFI_AP_STATE_ENABLING); - break; - } - - if(WifiNative.loadDriver()) { - Log.d(TAG, "Driver load successful"); - sendMessage(CMD_LOAD_DRIVER_SUCCESS); - } else { - Log.e(TAG, "Failed to load driver!"); - switch(message.arg1) { - case WIFI_STATE_ENABLING: - setWifiState(WIFI_STATE_UNKNOWN); - break; - case WIFI_AP_STATE_ENABLING: - setWifiApState(WIFI_AP_STATE_FAILED); - break; - } - sendMessage(CMD_LOAD_DRIVER_FAILURE); - } - sWakeLock.release(); - } - }).start(); - } - - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_LOAD_DRIVER_SUCCESS: - transitionTo(mDriverLoadedState); - break; - case CMD_LOAD_DRIVER_FAILURE: - transitionTo(mDriverFailedState); - break; - case CMD_LOAD_DRIVER: - case CMD_UNLOAD_DRIVER: - case CMD_START_SUPPLICANT: - case CMD_STOP_SUPPLICANT: - case CMD_START_AP: - case CMD_STOP_AP: - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: - case CMD_SET_SCAN_TYPE: - case CMD_SET_POWER_MODE: - case CMD_SET_BLUETOOTH_COEXISTENCE: - case CMD_SET_BLUETOOTH_SCAN_MODE: - case CMD_SET_NUM_ALLOWED_CHANNELS: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverLoadedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch(message.what) { - case CMD_UNLOAD_DRIVER: - transitionTo(mDriverUnloadingState); - break; - case CMD_START_SUPPLICANT: - if(WifiNative.startSupplicant()) { - Log.d(TAG, "Supplicant start successful"); - mWifiMonitor.startMonitoring(); - setWifiState(WIFI_STATE_ENABLED); - transitionTo(mWaitForSupState); - } else { - Log.e(TAG, "Failed to start supplicant!"); - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0)); - } - break; - case CMD_START_AP: - try { - nwService.startAccessPoint((WifiConfiguration) message.obj, - mInterfaceName, - SOFTAP_IFACE); - } catch(Exception e) { - Log.e(TAG, "Exception in startAccessPoint()"); - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0)); - break; - } - Log.d(TAG, "Soft AP start successful"); - setWifiApState(WIFI_AP_STATE_ENABLED); - transitionTo(mSoftApStartedState); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverUnloadingState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - final Message message = new Message(); - message.copyFrom(getCurrentMessage()); - new Thread(new Runnable() { - public void run() { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - sWakeLock.acquire(); - if(WifiNative.unloadDriver()) { - Log.d(TAG, "Driver unload successful"); - sendMessage(CMD_UNLOAD_DRIVER_SUCCESS); - - switch(message.arg1) { - case WIFI_STATE_DISABLED: - case WIFI_STATE_UNKNOWN: - setWifiState(message.arg1); - break; - case WIFI_AP_STATE_DISABLED: - case WIFI_AP_STATE_FAILED: - setWifiApState(message.arg1); - break; - } - } else { - Log.e(TAG, "Failed to unload driver!"); - sendMessage(CMD_UNLOAD_DRIVER_FAILURE); - - switch(message.arg1) { - case WIFI_STATE_DISABLED: - case WIFI_STATE_UNKNOWN: - setWifiState(WIFI_STATE_UNKNOWN); - break; - case WIFI_AP_STATE_DISABLED: - case WIFI_AP_STATE_FAILED: - setWifiApState(WIFI_AP_STATE_FAILED); - break; - } - } - sWakeLock.release(); - } - }).start(); - } - - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_UNLOAD_DRIVER_SUCCESS: - transitionTo(mDriverUnloadedState); - break; - case CMD_UNLOAD_DRIVER_FAILURE: - transitionTo(mDriverFailedState); - break; - case CMD_LOAD_DRIVER: - case CMD_UNLOAD_DRIVER: - case CMD_START_SUPPLICANT: - case CMD_STOP_SUPPLICANT: - case CMD_START_AP: - case CMD_STOP_AP: - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: - case CMD_SET_SCAN_TYPE: - case CMD_SET_POWER_MODE: - case CMD_SET_BLUETOOTH_COEXISTENCE: - case CMD_SET_BLUETOOTH_SCAN_MODE: - case CMD_SET_NUM_ALLOWED_CHANNELS: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverUnloadedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_LOAD_DRIVER: - transitionTo(mDriverLoadingState); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverFailedState extends HierarchicalState { - @Override - public void enter() { - Log.e(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - return NOT_HANDLED; - } - } - - - class WaitForSupState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch(message.what) { - case SUP_CONNECTION_EVENT: - Log.d(TAG, "Supplicant connection established"); - mSupplicantStateTracker.resetSupplicantState(); - /* Initialize data structures */ - resetNotificationTimer(); - setTeardownRequested(false); - mLastBssid = null; - mLastNetworkId = -1; - mLastSignalLevel = -1; - - mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand()); - - //TODO: initialize and fix multicast filtering - //mWM.initializeMulticastFiltering(); - - if (mBluetoothA2dp == null) { - mBluetoothA2dp = new BluetoothA2dp(mContext); - } - checkIsBluetoothPlaying(); - - checkUseStaticIp(); - sendSupplicantConnectionChangedBroadcast(true); - transitionTo(mDriverSupReadyState); - break; - case CMD_STOP_SUPPLICANT: - Log.d(TAG, "Stop supplicant received"); - WifiNative.stopSupplicant(); - transitionTo(mDriverLoadedState); - break; - /* Fail soft ap when waiting for supplicant start */ - case CMD_START_AP: - Log.d(TAG, "Failed to start soft AP with a running supplicant"); - setWifiApState(WIFI_AP_STATE_FAILED); - break; - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: - case CMD_SET_SCAN_TYPE: - case CMD_SET_POWER_MODE: - case CMD_SET_BLUETOOTH_COEXISTENCE: - case CMD_SET_BLUETOOTH_SCAN_MODE: - case CMD_SET_NUM_ALLOWED_CHANNELS: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - case CMD_STOP_AP: - case CMD_START_SUPPLICANT: - case CMD_UNLOAD_DRIVER: - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverSupReadyState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - /* Initialize for connect mode operation at start */ - mIsScanMode = false; - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - SyncParams syncParams; - switch(message.what) { - case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */ - Log.d(TAG, "Stop supplicant received"); - WifiNative.stopSupplicant(); - //$FALL-THROUGH$ - case SUP_DISCONNECTION_EVENT: /* Supplicant died */ - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - //Remove any notifications on disconnection - setNotificationVisible(false, 0, false, 0); - WifiNative.closeSupplicantConnection(); - handleNetworkDisconnect(); - sendSupplicantConnectionChangedBroadcast(false); - mSupplicantStateTracker.resetSupplicantState(); - transitionTo(mDriverLoadedState); - - /* When supplicant dies, unload driver and enter failed state */ - //TODO: consider bringing up supplicant again - if (message.what == SUP_DISCONNECTION_EVENT) { - Log.d(TAG, "Supplicant died, unloading driver"); - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0)); - } - break; - case CMD_START_DRIVER: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - WifiNative.startDriverCommand(); - transitionTo(mDriverStartingState); - break; - case SCAN_RESULTS_EVENT: - setScanResults(WifiNative.scanResultsCommand()); - sendScanResultsAvailableBroadcast(); - checkAndSetNotification(); - break; - case CMD_PING_SUPPLICANT: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.pingCommand(); - notifyOnMsgObject(message); - break; - case CMD_ADD_OR_UPDATE_NETWORK: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - syncParams = (SyncParams) message.obj; - WifiConfiguration config = (WifiConfiguration) syncParams.mParameter; - syncParams.mSyncReturn.intValue = addOrUpdateNetworkNative(config); - notifyOnMsgObject(message); - break; - case CMD_REMOVE_NETWORK: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.removeNetworkCommand( - message.arg1); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.removeNetworkCommand(message.arg1); - } - break; - case CMD_ENABLE_NETWORK: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - EnableNetParams enableNetParams = (EnableNetParams) syncParams.mParameter; - syncParams.mSyncReturn.boolValue = WifiNative.enableNetworkCommand( - enableNetParams.netId, enableNetParams.disableOthers); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.enableNetworkCommand(message.arg1, message.arg2 == 1); - } - break; - case CMD_DISABLE_NETWORK: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.disableNetworkCommand( - message.arg1); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.disableNetworkCommand(message.arg1); - } - break; - case CMD_BLACKLIST_NETWORK: - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - WifiNative.addToBlacklistCommand((String)message.obj); - break; - case CMD_CLEAR_BLACKLIST: - WifiNative.clearBlacklistCommand(); - break; - case CMD_GET_NETWORK_CONFIG: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.configList = getConfiguredNetworksNative(); - notifyOnMsgObject(message); - break; - case CMD_SAVE_CONFIG: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.saveConfigCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.saveConfigCommand(); - } - // Inform the backup manager about a data change - IBackupManager ibm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - if (ibm != null) { - try { - ibm.dataChanged("com.android.providers.settings"); - } catch (Exception e) { - // Try again later - } - } - break; - case CMD_CONNECTION_STATUS: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.stringValue = WifiNative.statusCommand(); - notifyOnMsgObject(message); - break; - case CMD_GET_MAC_ADDR: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.stringValue = WifiNative.getMacAddressCommand(); - notifyOnMsgObject(message); - break; - /* Cannot start soft AP while in client mode */ - case CMD_START_AP: - Log.d(TAG, "Failed to start soft AP with a running supplicant"); - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - setWifiApState(WIFI_AP_STATE_FAILED); - break; - case CMD_SET_SCAN_MODE: - mIsScanMode = (message.arg1 == SCAN_ONLY_MODE); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class DriverStartingState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch(message.what) { - case DRIVER_START_EVENT: - transitionTo(mDriverStartedState); - break; - /* Queue driver commands & connection events */ - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case SUPPLICANT_STATE_CHANGE_EVENT: - case NETWORK_CONNECTION_EVENT: - case NETWORK_DISCONNECTION_EVENT: - case PASSWORD_MAY_BE_INCORRECT_EVENT: - case CMD_SET_SCAN_TYPE: - case CMD_SET_POWER_MODE: - case CMD_SET_BLUETOOTH_COEXISTENCE: - case CMD_SET_BLUETOOTH_SCAN_MODE: - case CMD_SET_NUM_ALLOWED_CHANNELS: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - /* Queue the asynchronous version of these commands */ - case CMD_START_SCAN: - case CMD_DISCONNECT: - case CMD_REASSOCIATE: - case CMD_RECONNECT: - if (message.arg2 != SYNCHRONOUS_CALL) { - deferMessage(message); - } - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverStartedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - try { - mBatteryStats.noteWifiRunning(); - } catch (RemoteException ignore) {} - - /* Initialize channel count */ - setNumAllowedChannels(); - - if (mIsScanMode) { - WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); - WifiNative.disconnectCommand(); - transitionTo(mScanModeState); - } else { - WifiNative.setScanResultHandlingCommand(CONNECT_MODE); - /* If supplicant has already connected, before we could finish establishing - * the control channel connection, we miss all the supplicant events. - * Disconnect and reconnect when driver has started to ensure we receive - * all supplicant events. - * - * TODO: This is a bit unclean, ideally the supplicant should never - * connect until told to do so by the framework - */ - WifiNative.disconnectCommand(); - WifiNative.reconnectCommand(); - transitionTo(mConnectModeState); - } - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - SyncParams syncParams; - switch(message.what) { - case CMD_SET_SCAN_TYPE: - if (message.arg1 == SCAN_ACTIVE) { - WifiNative.setScanModeCommand(true); - } else { - WifiNative.setScanModeCommand(false); - } - break; - case CMD_SET_POWER_MODE: - WifiNative.setPowerModeCommand(message.arg1); - break; - case CMD_SET_BLUETOOTH_COEXISTENCE: - WifiNative.setBluetoothCoexistenceModeCommand(message.arg1); - break; - case CMD_SET_BLUETOOTH_SCAN_MODE: - WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1); - break; - case CMD_SET_NUM_ALLOWED_CHANNELS: - mNumAllowedChannels = message.arg1; - WifiNative.setNumAllowedChannelsCommand(message.arg1); - break; - case CMD_START_DRIVER: - /* Ignore another driver start */ - break; - case CMD_STOP_DRIVER: - WifiNative.stopDriverCommand(); - transitionTo(mDriverStoppingState); - break; - case CMD_REQUEST_CM_WAKELOCK: - if (mCm == null) { - mCm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - } - mCm.requestNetworkTransitionWakelock(TAG); - break; - case CMD_START_PACKET_FILTERING: - WifiNative.startPacketFiltering(); - break; - case CMD_STOP_PACKET_FILTERING: - WifiNative.stopPacketFiltering(); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - @Override - public void exit() { - if (DBG) Log.d(TAG, getName() + "\n"); - try { - mBatteryStats.noteWifiStopped(); - } catch (RemoteException ignore) { } - } - } - - class DriverStoppingState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch(message.what) { - case DRIVER_STOP_EVENT: - transitionTo(mDriverStoppedState); - break; - /* Queue driver commands */ - case CMD_START_DRIVER: - case CMD_STOP_DRIVER: - case CMD_SET_SCAN_TYPE: - case CMD_SET_POWER_MODE: - case CMD_SET_BLUETOOTH_COEXISTENCE: - case CMD_SET_BLUETOOTH_SCAN_MODE: - case CMD_SET_NUM_ALLOWED_CHANNELS: - case CMD_START_PACKET_FILTERING: - case CMD_STOP_PACKET_FILTERING: - deferMessage(message); - break; - /* Queue the asynchronous version of these commands */ - case CMD_START_SCAN: - case CMD_DISCONNECT: - case CMD_REASSOCIATE: - case CMD_RECONNECT: - if (message.arg2 != SYNCHRONOUS_CALL) { - deferMessage(message); - } - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DriverStoppedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - // Take down any open network notifications on driver stop - setNotificationVisible(false, 0, false, 0); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - return NOT_HANDLED; - } - } - - class ScanModeState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - SyncParams syncParams; - switch(message.what) { - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - /* Ignore */ - return HANDLED; - } else { - WifiNative.setScanResultHandlingCommand(message.arg1); - WifiNative.reconnectCommand(); - mIsScanMode = false; - transitionTo(mDisconnectedState); - } - break; - case CMD_START_SCAN: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.scanCommand( - message.arg1 == SCAN_ACTIVE); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - } - break; - /* Ignore */ - case CMD_DISCONNECT: - case CMD_RECONNECT: - case CMD_REASSOCIATE: - case SUPPLICANT_STATE_CHANGE_EVENT: - case NETWORK_CONNECTION_EVENT: - case NETWORK_DISCONNECTION_EVENT: - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class ConnectModeState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - SyncParams syncParams; - StateChangeResult stateChangeResult; - switch(message.what) { - case PASSWORD_MAY_BE_INCORRECT_EVENT: - mPasswordKeyMayBeIncorrect = true; - break; - case SUPPLICANT_STATE_CHANGE_EVENT: - stateChangeResult = (StateChangeResult) message.obj; - mSupplicantStateTracker.handleEvent(stateChangeResult); - break; - case CMD_START_SCAN: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = true; - notifyOnMsgObject(message); - } - /* We need to set scan type in completed state */ - Message newMsg = obtainMessage(); - newMsg.copyFrom(message); - mSupplicantStateTracker.sendMessage(newMsg); - break; - /* Do a redundant disconnect without transition */ - case CMD_DISCONNECT: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.disconnectCommand(); - } - break; - case CMD_RECONNECT: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.reconnectCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.reconnectCommand(); - } - break; - case CMD_REASSOCIATE: - if (message.arg2 == SYNCHRONOUS_CALL) { - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.reassociateCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.reassociateCommand(); - } - break; - case SCAN_RESULTS_EVENT: - /* Set the scan setting back to "connect" mode */ - WifiNative.setScanResultHandlingCommand(CONNECT_MODE); - /* Handle scan results */ - return NOT_HANDLED; - case NETWORK_CONNECTION_EVENT: - Log.d(TAG,"Network connection established"); - stateChangeResult = (StateChangeResult) message.obj; - - /* Remove any notifications */ - setNotificationVisible(false, 0, false, 0); - resetNotificationTimer(); - - mWifiInfo.setBSSID(mLastBssid = stateChangeResult.BSSID); - mWifiInfo.setNetworkId(stateChangeResult.networkId); - mLastNetworkId = stateChangeResult.networkId; - - /* send event to CM & network change broadcast */ - setDetailedState(DetailedState.OBTAINING_IPADDR); - sendNetworkStateChangeBroadcast(mLastBssid); - - transitionTo(mConnectingState); - break; - case NETWORK_DISCONNECTION_EVENT: - Log.d(TAG,"Network connection lost"); - handleNetworkDisconnect(); - transitionTo(mDisconnectedState); - break; - case CMD_GET_RSSI: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.intValue = WifiNative.getRssiCommand(); - notifyOnMsgObject(message); - break; - case CMD_GET_RSSI_APPROX: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.intValue = WifiNative.getRssiApproxCommand(); - notifyOnMsgObject(message); - break; - case CMD_GET_LINK_SPEED: - syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.intValue = WifiNative.getLinkSpeedCommand(); - notifyOnMsgObject(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class ConnectingState extends HierarchicalState { - boolean modifiedBluetoothCoexistenceMode; - int powerMode; - Thread mDhcpThread; - - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - - if (!mUseStaticIp) { - - mDhcpThread = null; - modifiedBluetoothCoexistenceMode = false; - powerMode = DRIVER_POWER_MODE_AUTO; - - if (shouldDisableCoexistenceMode()) { - /* - * There are problems setting the Wi-Fi driver's power - * mode to active when bluetooth coexistence mode is - * enabled or sense. - * <p> - * We set Wi-Fi to active mode when - * obtaining an IP address because we've found - * compatibility issues with some routers with low power - * mode. - * <p> - * In order for this active power mode to properly be set, - * we disable coexistence mode until we're done with - * obtaining an IP address. One exception is if we - * are currently connected to a headset, since disabling - * coexistence would interrupt that connection. - */ - modifiedBluetoothCoexistenceMode = true; - - // Disable the coexistence mode - WifiNative.setBluetoothCoexistenceModeCommand( - WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED); - } - - powerMode = WifiNative.getPowerModeCommand(); - if (powerMode < 0) { - // Handle the case where supplicant driver does not support - // getPowerModeCommand. - powerMode = DRIVER_POWER_MODE_AUTO; - } - if (powerMode != DRIVER_POWER_MODE_ACTIVE) { - WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE); - } - - Log.d(TAG, "DHCP request started"); - mDhcpThread = new Thread(new Runnable() { - public void run() { - if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) { - Log.d(TAG, "DHCP request succeeded"); - sendMessage(CMD_IP_CONFIG_SUCCESS); - } else { - Log.d(TAG, "DHCP request failed: " + - NetworkUtils.getDhcpError()); - sendMessage(CMD_IP_CONFIG_FAILURE); - } - } - }); - mDhcpThread.start(); - } else { - if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) { - Log.v(TAG, "Static IP configuration succeeded"); - sendMessage(CMD_IP_CONFIG_SUCCESS); - } else { - Log.v(TAG, "Static IP configuration failed"); - sendMessage(CMD_IP_CONFIG_FAILURE); - } - } - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - - switch(message.what) { - case CMD_IP_CONFIG_SUCCESS: - mReconnectCount = 0; - mLastSignalLevel = -1; // force update of signal strength - mWifiInfo.setIpAddress(mDhcpInfo.ipAddress); - Log.d(TAG, "IP configuration: " + mDhcpInfo); - configureNetworkProperties(); - setDetailedState(DetailedState.CONNECTED); - sendNetworkStateChangeBroadcast(mLastBssid); - transitionTo(mConnectedState); - break; - case CMD_IP_CONFIG_FAILURE: - mWifiInfo.setIpAddress(0); - - Log.e(TAG, "IP configuration failed"); - /** - * If we've exceeded the maximum number of retries for DHCP - * to a given network, disable the network - */ - if (++mReconnectCount > getMaxDhcpRetries()) { - Log.e(TAG, "Failed " + - mReconnectCount + " times, Disabling " + mLastNetworkId); - WifiNative.disableNetworkCommand(mLastNetworkId); - } - - /* DHCP times out after about 30 seconds, we do a - * disconnect and an immediate reconnect to try again - */ - WifiNative.disconnectCommand(); - WifiNative.reconnectCommand(); - transitionTo(mDisconnectingState); - break; - case CMD_DISCONNECT: - if (message.arg2 == SYNCHRONOUS_CALL) { - SyncParams syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.disconnectCommand(); - } - transitionTo(mDisconnectingState); - break; - /* Ignore */ - case NETWORK_CONNECTION_EVENT: - break; - case CMD_STOP_DRIVER: - sendMessage(CMD_DISCONNECT); - deferMessage(message); - break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - sendMessage(CMD_DISCONNECT); - deferMessage(message); - } - break; - case CMD_RECONFIGURE_IP: - deferMessage(message); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - - @Override - public void exit() { - /* reset power state & bluetooth coexistence if on DHCP */ - if (!mUseStaticIp) { - if (powerMode != DRIVER_POWER_MODE_ACTIVE) { - WifiNative.setPowerModeCommand(powerMode); - } - - if (modifiedBluetoothCoexistenceMode) { - // Set the coexistence mode back to its default value - WifiNative.setBluetoothCoexistenceModeCommand( - WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE); - } - } - - } - } - - class ConnectedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_DISCONNECT: - if (message.arg2 == SYNCHRONOUS_CALL) { - SyncParams syncParams = (SyncParams) message.obj; - syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand(); - notifyOnMsgObject(message); - } else { - /* asynchronous handling */ - WifiNative.disconnectCommand(); - } - transitionTo(mDisconnectingState); - break; - case CMD_RECONFIGURE_IP: - Log.d(TAG,"Reconfiguring IP on connection"); - NetworkUtils.resetConnections(mInterfaceName); - transitionTo(mConnectingState); - break; - case CMD_STOP_DRIVER: - sendMessage(CMD_DISCONNECT); - deferMessage(message); - break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - sendMessage(CMD_DISCONNECT); - deferMessage(message); - } - break; - /* Ignore */ - case NETWORK_CONNECTION_EVENT: - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DisconnectingState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_STOP_DRIVER: /* Stop driver only after disconnect handled */ - deferMessage(message); - break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - deferMessage(message); - } - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class DisconnectedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - WifiNative.setScanResultHandlingCommand(message.arg1); - //Supplicant disconnect to prevent further connects - WifiNative.disconnectCommand(); - mIsScanMode = true; - transitionTo(mScanModeState); - } - break; - /* Ignore network disconnect */ - case NETWORK_DISCONNECTION_EVENT: - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - class SoftApStartedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch(message.what) { - case CMD_STOP_AP: - Log.d(TAG,"Stopping Soft AP"); - setWifiApState(WIFI_AP_STATE_DISABLING); - try { - nwService.stopAccessPoint(); - } catch(Exception e) { - Log.e(TAG, "Exception in stopAccessPoint()"); - } - transitionTo(mDriverLoadedState); - break; - case CMD_START_AP: - Log.d(TAG,"SoftAP set on a running access point"); - try { - nwService.setAccessPoint((WifiConfiguration) message.obj, - mInterfaceName, - SOFTAP_IFACE); - } catch(Exception e) { - Log.e(TAG, "Exception in nwService during soft AP set"); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0)); - } - break; - /* Fail client mode operation when soft AP is enabled */ - case CMD_START_SUPPLICANT: - Log.e(TAG,"Cannot start supplicant with a running soft AP"); - setWifiState(WIFI_STATE_UNKNOWN); - break; - default: - return NOT_HANDLED; - } - EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); - return HANDLED; - } - } - - - class SupplicantStateTracker extends HierarchicalStateMachine { - - private int mRssiPollToken = 0; - - /** - * The max number of the WPA supplicant loop iterations before we - * decide that the loop should be terminated: - */ - private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; - private int mLoopDetectIndex = 0; - private int mLoopDetectCount = 0; - - /** - * Supplicant state change commands follow - * the ordinal values defined in SupplicantState.java - */ - private static final int DISCONNECTED = 0; - private static final int INACTIVE = 1; - private static final int SCANNING = 2; - private static final int ASSOCIATING = 3; - private static final int ASSOCIATED = 4; - private static final int FOUR_WAY_HANDSHAKE = 5; - private static final int GROUP_HANDSHAKE = 6; - private static final int COMPLETED = 7; - private static final int DORMANT = 8; - private static final int UNINITIALIZED = 9; - private static final int INVALID = 10; - - private HierarchicalState mUninitializedState; - private HierarchicalState mInitializedState; - private HierarchicalState mInactiveState; - private HierarchicalState mDisconnectState; - private HierarchicalState mScanState; - private HierarchicalState mConnectState; - private HierarchicalState mHandshakeState; - private HierarchicalState mCompletedState; - private HierarchicalState mDormantState; - - - public SupplicantStateTracker(Context context, Handler target) { - super(TAG, target.getLooper()); - - mUninitializedState = new UninitializedState(); - mInitializedState = new InitializedState(); - mInactiveState = new InactiveState(); - mDisconnectState = new DisconnectedState(); - mScanState = new ScanState(); - mConnectState = new ConnectState(); - mHandshakeState = new HandshakeState(); - mCompletedState = new CompletedState(); - mDormantState = new DormantState(); - - - addState(mUninitializedState); - addState(mInitializedState); - addState(mInactiveState, mInitializedState); - addState(mDisconnectState, mInitializedState); - addState(mScanState, mInitializedState); - addState(mConnectState, mInitializedState); - addState(mHandshakeState, mConnectState); - addState(mCompletedState, mConnectState); - addState(mDormantState, mInitializedState); - - setInitialState(mUninitializedState); - - //start the state machine - start(); - } - - public void handleEvent(StateChangeResult stateChangeResult) { - SupplicantState newState = (SupplicantState) stateChangeResult.state; - - // Supplicant state change - // [31-13] Reserved for future use - // [8 - 0] Supplicant state (as defined in SupplicantState.java) - // 50023 supplicant_state_changed (custom|1|5) - EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal()); - - sendMessage(obtainMessage(newState.ordinal(), stateChangeResult)); - } - - public void resetSupplicantState() { - transitionTo(mUninitializedState); - } - - private void resetLoopDetection() { - mLoopDetectCount = 0; - mLoopDetectIndex = 0; - } - - private boolean handleTransition(Message msg) { - if (DBG) Log.d(TAG, getName() + msg.toString() + "\n"); - switch (msg.what) { - case DISCONNECTED: - transitionTo(mDisconnectState); - break; - case SCANNING: - transitionTo(mScanState); - break; - case ASSOCIATING: - StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; - /* BSSID is valid only in ASSOCIATING state */ - mWifiInfo.setBSSID(stateChangeResult.BSSID); - //$FALL-THROUGH$ - case ASSOCIATED: - case FOUR_WAY_HANDSHAKE: - case GROUP_HANDSHAKE: - transitionTo(mHandshakeState); - break; - case COMPLETED: - transitionTo(mCompletedState); - break; - case DORMANT: - transitionTo(mDormantState); - break; - case INACTIVE: - transitionTo(mInactiveState); - break; - case UNINITIALIZED: - case INVALID: - transitionTo(mUninitializedState); - break; - default: - return NOT_HANDLED; - } - StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; - SupplicantState supState = (SupplicantState) stateChangeResult.state; - setDetailedState(WifiInfo.getDetailedStateOf(supState)); - mWifiInfo.setSupplicantState(supState); - mWifiInfo.setNetworkId(stateChangeResult.networkId); - //TODO: Modify WifiMonitor to report SSID on events - //mWifiInfo.setSSID() - return HANDLED; - } - - /******************************************************** - * HSM states - *******************************************************/ - - class InitializedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_START_SCAN: - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - break; - default: - if (DBG) Log.w(TAG, "Ignoring " + message); - break; - } - return HANDLED; - } - } - - class UninitializedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - mNetworkInfo.setIsAvailable(false); - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - default: - if (!handleTransition(message)) { - if (DBG) Log.w(TAG, "Ignoring " + message); - } - break; - } - return HANDLED; - } - } - - class InactiveState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(false); - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - - class DisconnectedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(true); - resetLoopDetection(); - - /* If a disconnect event happens after a password key failure - * event, disable the network - */ - if (mPasswordKeyMayBeIncorrect) { - Log.d(TAG, "Failed to authenticate, disabling network " + - mWifiInfo.getNetworkId()); - WifiNative.disableNetworkCommand(mWifiInfo.getNetworkId()); - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, true); - } - else { - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class ScanState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(true); - mPasswordKeyMayBeIncorrect = false; - resetLoopDetection(); - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class ConnectState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_START_SCAN: - WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class HandshakeState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - final Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(true); - - if (mLoopDetectIndex > message.what) { - mLoopDetectCount++; - } - if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { - WifiNative.disableNetworkCommand(stateChangeResult.networkId); - mLoopDetectCount = 0; - } - - mLoopDetectIndex = message.what; - - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class CompletedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(true); - - mRssiPollToken++; - if (mEnableRssiPolling) { - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } - - resetLoopDetection(); - - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - case ASSOCIATING: - case ASSOCIATED: - case FOUR_WAY_HANDSHAKE: - case GROUP_HANDSHAKE: - case COMPLETED: - break; - case CMD_RSSI_POLL: - if (message.arg1 == mRssiPollToken) { - // Get Info and continue polling - requestPolledInfo(); - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } else { - // Polling has completed - } - break; - case CMD_ENABLE_RSSI_POLL: - mRssiPollToken++; - if (mEnableRssiPolling) { - // first poll - requestPolledInfo(); - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } - break; - default: - return handleTransition(message); - } - return HANDLED; - } - } - - class DormantState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(true); - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - - /* TODO: reconnect is now being handled at DHCP failure handling - * If we run into issues with staying in Dormant state, might - * need a reconnect here - */ - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - } - - private class NotificationEnabledSettingObserver extends ContentObserver { - - public NotificationEnabledSettingObserver(Handler handler) { - super(handler); - } - - public void register() { - ContentResolver cr = mContext.getContentResolver(); - cr.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); - mNotificationEnabled = getValue(); - } - - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - - mNotificationEnabled = getValue(); - if (!mNotificationEnabled) { - // Remove any notification that may be showing - setNotificationVisible(false, 0, true, 0); - } - - resetNotificationTimer(); - } - - private boolean getValue() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; - } - } -} +}
\ No newline at end of file |