diff options
44 files changed, 3689 insertions, 313 deletions
diff --git a/api/current.xml b/api/current.xml index d97a0f4..7df4115 100644 --- a/api/current.xml +++ b/api/current.xml @@ -496,6 +496,17 @@ visibility="public" > </field> +<field name="GLOBAL_SEARCH" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.permission.GLOBAL_SEARCH"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="HARDWARE_TEST" type="java.lang.String" transient="false" @@ -25945,6 +25956,17 @@ visibility="public" > </method> +<method name="getPathPermissions" + return="android.content.pm.PathPermission[]" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getReadPermission" return="java.lang.String" abstract="false" @@ -26113,6 +26135,19 @@ <parameter name="sortOrder" type="java.lang.String"> </parameter> </method> +<method name="setPathPermissions" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="permissions" type="android.content.pm.PathPermission[]"> +</parameter> +</method> <method name="setReadPermission" return="void" abstract="false" @@ -37714,6 +37749,73 @@ > </field> </class> +<class name="PathPermission" + extends="android.os.PatternMatcher" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="PathPermission" + type="android.content.pm.PathPermission" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="pattern" type="java.lang.String"> +</parameter> +<parameter name="type" type="int"> +</parameter> +<parameter name="readPermission" type="java.lang.String"> +</parameter> +<parameter name="writePermission" type="java.lang.String"> +</parameter> +</constructor> +<constructor name="PathPermission" + type="android.content.pm.PathPermission" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="src" type="android.os.Parcel"> +</parameter> +</constructor> +<method name="getReadPermission" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getWritePermission" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="PermissionGroupInfo" extends="android.content.pm.PackageItemInfo" abstract="false" @@ -38043,6 +38145,17 @@ visibility="public" > </field> +<field name="pathPermissions" + type="android.content.pm.PathPermission[]" + transient="false" + volatile="false" + value="null" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="readPermission" type="java.lang.String" transient="false" @@ -46736,6 +46849,1742 @@ </method> </class> </package> +<package name="android.gesture" +> +<class name="Gesture" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<constructor name="Gesture" + type="android.gesture.Gesture" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="addStroke" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="stroke" type="android.gesture.GestureStroke"> +</parameter> +</method> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getBoundingBox" + return="android.graphics.RectF" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getID" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getLength" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getStrokes" + return="java.util.ArrayList<android.gesture.GestureStroke>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getStrokesCount" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="toBitmap" + return="android.graphics.Bitmap" + 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> +<parameter name="edge" type="int"> +</parameter> +<parameter name="numSample" type="int"> +</parameter> +<parameter name="color" type="int"> +</parameter> +</method> +<method name="toBitmap" + return="android.graphics.Bitmap" + 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> +<parameter name="inset" type="int"> +</parameter> +<parameter name="color" type="int"> +</parameter> +</method> +<method name="toPath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="toPath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="android.graphics.Path"> +</parameter> +</method> +<method name="toPath" + return="android.graphics.Path" + 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> +<parameter name="edge" type="int"> +</parameter> +<parameter name="numSample" type="int"> +</parameter> +</method> +<method name="toPath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="android.graphics.Path"> +</parameter> +<parameter name="width" type="int"> +</parameter> +<parameter name="height" type="int"> +</parameter> +<parameter name="edge" type="int"> +</parameter> +<parameter name="numSample" type="int"> +</parameter> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="out" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="GestureLibraries" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<method name="fromFile" + return="android.gesture.GestureLibrary" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.lang.String"> +</parameter> +</method> +<method name="fromFile" + return="android.gesture.GestureLibrary" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.io.File"> +</parameter> +</method> +<method name="fromPrivateFile" + return="android.gesture.GestureLibrary" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="name" type="java.lang.String"> +</parameter> +</method> +<method name="fromRawResource" + return="android.gesture.GestureLibrary" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="resourceId" type="int"> +</parameter> +</method> +</class> +<class name="GestureLibrary" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="GestureLibrary" + type="android.gesture.GestureLibrary" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +</constructor> +<method name="addGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="getGestureEntries" + return="java.util.Set<java.lang.String>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestures" + return="java.util.ArrayList<android.gesture.Gesture>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +</method> +<method name="getLearner" + return="android.gesture.Learner" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getOrientationStyle" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getSequenceType" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isReadOnly" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="load" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="recognize" + return="java.util.ArrayList<android.gesture.Prediction>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="removeEntry" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +</method> +<method name="removeGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="save" + return="boolean" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="setOrientationStyle" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="style" type="int"> +</parameter> +</method> +<method name="setSequenceType" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="int"> +</parameter> +</method> +<field name="mStore" + type="android.gesture.GestureStore" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="protected" +> +</field> +</class> +<class name="GestureOverlayView" + extends="android.widget.FrameLayout" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="GestureOverlayView" + type="android.gesture.GestureOverlayView" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</constructor> +<constructor name="GestureOverlayView" + type="android.gesture.GestureOverlayView" + 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> +<constructor name="GestureOverlayView" + type="android.gesture.GestureOverlayView" + 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> +<parameter name="defStyle" type="int"> +</parameter> +</constructor> +<method name="addOnGestureListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGestureListener"> +</parameter> +</method> +<method name="addOnGesturePerformedListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener"> +</parameter> +</method> +<method name="addOnGesturingListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturingListener"> +</parameter> +</method> +<method name="cancelClearAnimation" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="cancelGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="clear" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="animated" type="boolean"> +</parameter> +</method> +<method name="getCurrentStroke" + return="java.util.ArrayList<android.gesture.GesturePoint>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getFadeOffset" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGesture" + return="android.gesture.Gesture" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestureColor" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGesturePath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGesturePath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="android.graphics.Path"> +</parameter> +</method> +<method name="getGestureStrokeAngleThreshold" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestureStrokeLengthThreshold" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestureStrokeSquarenessTreshold" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestureStrokeType" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestureStrokeWidth" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getOrientation" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getUncertainGestureColor" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isEventsInterceptionEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isFadeEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isGestureVisible" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isGesturing" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="removeAllOnGestureListeners" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="removeAllOnGesturePerformedListeners" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="removeAllOnGesturingListeners" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="removeOnGestureListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGestureListener"> +</parameter> +</method> +<method name="removeOnGesturePerformedListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturePerformedListener"> +</parameter> +</method> +<method name="removeOnGesturingListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.gesture.GestureOverlayView.OnGesturingListener"> +</parameter> +</method> +<method name="setEventsInterceptionEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="enabled" type="boolean"> +</parameter> +</method> +<method name="setFadeEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="fadeEnabled" type="boolean"> +</parameter> +</method> +<method name="setFadeOffset" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="fadeOffset" type="long"> +</parameter> +</method> +<method name="setGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="setGestureColor" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="color" type="int"> +</parameter> +</method> +<method name="setGestureStrokeAngleThreshold" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gestureStrokeAngleThreshold" type="float"> +</parameter> +</method> +<method name="setGestureStrokeLengthThreshold" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gestureStrokeLengthThreshold" type="float"> +</parameter> +</method> +<method name="setGestureStrokeSquarenessTreshold" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gestureStrokeSquarenessTreshold" type="float"> +</parameter> +</method> +<method name="setGestureStrokeType" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gestureStrokeType" type="int"> +</parameter> +</method> +<method name="setGestureStrokeWidth" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gestureStrokeWidth" type="float"> +</parameter> +</method> +<method name="setGestureVisible" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="visible" type="boolean"> +</parameter> +</method> +<method name="setOrientation" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="orientation" type="int"> +</parameter> +</method> +<method name="setUncertainGestureColor" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="color" type="int"> +</parameter> +</method> +<field name="GESTURE_STROKE_TYPE_MULTIPLE" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="GESTURE_STROKE_TYPE_SINGLE" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ORIENTATION_HORIZONTAL" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ORIENTATION_VERTICAL" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<interface name="GestureOverlayView.OnGestureListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onGesture" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +<parameter name="event" type="android.view.MotionEvent"> +</parameter> +</method> +<method name="onGestureCancelled" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +<parameter name="event" type="android.view.MotionEvent"> +</parameter> +</method> +<method name="onGestureEnded" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +<parameter name="event" type="android.view.MotionEvent"> +</parameter> +</method> +<method name="onGestureStarted" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +<parameter name="event" type="android.view.MotionEvent"> +</parameter> +</method> +</interface> +<interface name="GestureOverlayView.OnGesturePerformedListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onGesturePerformed" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +</interface> +<interface name="GestureOverlayView.OnGesturingListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onGesturingEnded" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +</method> +<method name="onGesturingStarted" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overlay" type="android.gesture.GestureOverlayView"> +</parameter> +</method> +</interface> +<class name="GesturePoint" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="GesturePoint" + type="android.gesture.GesturePoint" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="x" type="float"> +</parameter> +<parameter name="y" type="float"> +</parameter> +<parameter name="t" type="long"> +</parameter> +</constructor> +<field name="timestamp" + type="long" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="x" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="y" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="GestureStore" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="GestureStore" + type="android.gesture.GestureStore" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="addGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="getGestureEntries" + return="java.util.Set<java.lang.String>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGestures" + return="java.util.ArrayList<android.gesture.Gesture>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +</method> +<method name="getOrientationStyle" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getSequenceType" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="hasChanged" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="load" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="stream" type="java.io.InputStream"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="load" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="stream" type="java.io.InputStream"> +</parameter> +<parameter name="closeStream" type="boolean"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="recognize" + return="java.util.ArrayList<android.gesture.Prediction>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="removeEntry" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +</method> +<method name="removeGesture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entryName" type="java.lang.String"> +</parameter> +<parameter name="gesture" type="android.gesture.Gesture"> +</parameter> +</method> +<method name="save" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="stream" type="java.io.OutputStream"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="save" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="stream" type="java.io.OutputStream"> +</parameter> +<parameter name="closeStream" type="boolean"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +</method> +<method name="setOrientationStyle" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="style" type="int"> +</parameter> +</method> +<method name="setSequenceType" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="type" type="int"> +</parameter> +</method> +<field name="ORIENTATION_INVARIANT" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ORIENTATION_SENSITIVE" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SEQUENCE_INVARIANT" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SEQUENCE_SENSITIVE" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="GestureStroke" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="GestureStroke" + type="android.gesture.GestureStroke" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="points" type="java.util.ArrayList<android.gesture.GesturePoint>"> +</parameter> +</constructor> +<method name="clearPath" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="computeOrientedBoundingBox" + return="android.gesture.OrientedBoundingBox" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getPath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="toPath" + return="android.graphics.Path" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="width" type="float"> +</parameter> +<parameter name="height" type="float"> +</parameter> +<parameter name="numSample" type="int"> +</parameter> +</method> +<field name="boundingBox" + type="android.graphics.RectF" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="length" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="points" + type="float[]" + transient="false" + volatile="false" + value="null" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="Learner" + extends="java.lang.Object" + abstract="true" + static="false" + final="false" + deprecated="not deprecated" + visibility="" +> +</class> +<class name="OrientedBoundingBox" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<field name="centerX" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="centerY" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="height" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="orientation" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="squareness" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="width" + type="float" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="Prediction" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<field name="name" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="score" + type="double" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +</package> <package name="android.graphics" > <class name="AvoidXfermode" diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5ee29ac..ec8d56b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2545,32 +2545,39 @@ public final class ActivityThread { classname = "android.app.FullBackupAgent"; } try { - java.lang.ClassLoader cl = packageInfo.getClassLoader(); - agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance(); - } catch (Exception e) { - throw new RuntimeException("Unable to instantiate backup agent " - + data.appInfo.backupAgentName + ": " + e.toString(), e); - } - - // set up the agent's context - try { - if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent " - + data.appInfo.backupAgentName); - - ApplicationContext context = new ApplicationContext(); - context.init(packageInfo, null, this); - context.setOuterContext(agent); - agent.attach(context); - agent.onCreate(); + IBinder binder = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance(); + + // set up the agent's context + if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent " + + data.appInfo.backupAgentName); + + ApplicationContext context = new ApplicationContext(); + context.init(packageInfo, null, this); + context.setOuterContext(agent); + agent.attach(context); + + agent.onCreate(); + binder = agent.onBind(); + mBackupAgents.put(packageName, agent); + } catch (Exception e) { + // If this is during restore, fail silently; otherwise go + // ahead and let the user see the crash. + Log.e(TAG, "Agent threw during creation: " + e); + if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) { + throw e; + } + // falling through with 'binder' still null + } // tell the OS that we're live now - IBinder binder = agent.onBind(); try { ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder); } catch (RemoteException e) { // nothing to do. } - mBackupAgents.put(packageName, agent); } catch (Exception e) { throw new RuntimeException("Unable to create BackupAgent " + data.appInfo.backupAgentName + ": " + e.toString(), e); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 022a9d9..6d6aca4 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -991,7 +991,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS }; @Override - public void cancel() { + public void dismiss() { if (!isShowing()) return; // We made sure the IME was displayed, so also make sure it is closed @@ -1003,7 +1003,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS getWindow().getDecorView().getWindowToken(), 0); } - super.cancel(); + super.dismiss(); } /** diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 49c94d1..c8e952f 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -18,7 +18,8 @@ package android.app; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources.NotFoundException; +import android.content.res.ColorStateList; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -300,29 +301,17 @@ class SuggestionsAdapter extends ResourceCursorAdapter { ((SuggestionItemView)view).setColor(backgroundColor); final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol)); - setViewText(cursor, views.mText1, mText1Col, isHtml); - setViewText(cursor, views.mText2, mText2Col, isHtml); - setViewIcon(cursor, views.mIcon1, mIconName1Col); - setViewIcon(cursor, views.mIcon2, mIconName2Col); - } - - private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) { - if (v == null) { - return; - } - CharSequence text = null; - if (textCol >= 0) { - String str = cursor.getString(textCol); - text = (str != null && isHtml) ? Html.fromHtml(str) : str; + String text1 = null; + if (mText1Col >= 0) { + text1 = cursor.getString(mText1Col); } - // Set the text even if it's null, since we need to clear any previous text. - v.setText(text); - - if (TextUtils.isEmpty(text)) { - v.setVisibility(View.GONE); - } else { - v.setVisibility(View.VISIBLE); + String text2 = null; + if (mText2Col >= 0) { + text2 = cursor.getString(mText2Col); } + ((SuggestionItemView)view).setTextStrings(text1, text2, isHtml, mProviderContext); + setViewIcon(cursor, views.mIcon1, mIconName1Col); + setViewIcon(cursor, views.mIcon2, mIconName2Col); } private void setViewIcon(Cursor cursor, ImageView v, int iconNameCol) { @@ -476,7 +465,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { if (drawable != null) { mOutsideDrawablesCache.put(drawableId, drawable); } - } catch (NotFoundException nfe) { + } catch (Resources.NotFoundException nfe) { if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId); // drawable = null; } @@ -509,8 +498,82 @@ class SuggestionsAdapter extends ResourceCursorAdapter { * draws on top of the list view selection highlight). */ private class SuggestionItemView extends ViewGroup { + /** + * Parses a given HTMl string and manages Spannable variants of the string for different + * states of the suggestion item (selected, pressed and normal). Colors for these different + * states are specified in the html font tag color attribute in the format '@<RESOURCEID>' + * where RESOURCEID is the ID of a ColorStateList or Color resource. + */ + private class MultiStateText { + private CharSequence mNormal = null; // text to display in normal state. + private CharSequence mSelected = null; // text to display in selected state. + private CharSequence mPressed = null; // text to display in pressed state. + private String mPlainText = null; // valid if the text is stateless plain text. + + public MultiStateText(boolean isHtml, String text, Context context) { + if (!isHtml || text == null) { + mPlainText = text; + return; + } + + String textNormal = text; + String textSelected = text; + String textPressed = text; + int textLength = text.length(); + int start = text.indexOf("\"@"); + + // For each font color attribute which has the value in the form '@<RESOURCEID>', + // try to load the resource and create the display strings for the 3 states. + while (start >= 0) { + start++; + int end = text.indexOf("\"", start); + if (end == -1) break; + + String colorIdString = text.substring(start, end); + int colorId = Integer.parseInt(colorIdString.substring(1)); + try { + // The following call works both for color lists and colors. + ColorStateList csl = context.getResources().getColorStateList(colorId); + int normalColor = csl.getColorForState( + View.EMPTY_STATE_SET, csl.getDefaultColor()); + int selectedColor = csl.getColorForState( + View.SELECTED_STATE_SET, csl.getDefaultColor()); + int pressedColor = csl.getColorForState( + View.PRESSED_STATE_SET, csl.getDefaultColor()); + + // Convert the int color values into a hex string, and strip the first 2 + // characters which will be the alpha (html doesn't want this). + textNormal = textNormal.replace(colorIdString, + "#" + Integer.toHexString(normalColor).substring(2)); + textSelected = textSelected.replace(colorIdString, + "#" + Integer.toHexString(selectedColor).substring(2)); + textPressed = textPressed.replace(colorIdString, + "#" + Integer.toHexString(pressedColor).substring(2)); + } catch (Resources.NotFoundException e) { + // Nothing to do. + } + + start = text.indexOf("\"@", end); + } + mNormal = Html.fromHtml(textNormal); + mSelected = Html.fromHtml(textSelected); + mPressed = Html.fromHtml(textPressed); + } + public CharSequence normal() { + return (mPlainText != null) ? mPlainText : mNormal; + } + public CharSequence selected() { + return (mPlainText != null) ? mPlainText : mSelected; + } + public CharSequence pressed() { + return (mPlainText != null) ? mPlainText : mPressed; + } + } + private int mBackgroundColor; // the background color to draw in normal state. private View mView; // the suggestion item's view. + private MultiStateText mText1Strings = null; + private MultiStateText mText2Strings = null; protected SuggestionItemView(Context context, Cursor cursor) { // Initialize ourselves @@ -537,12 +600,48 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } } + private void setInitialTextForView(TextView view, MultiStateText multiState, + String plainText) { + // Set the text even if it's null, since we need to clear any previous text. + CharSequence text = (multiState != null) ? multiState.normal() : plainText; + view.setText(text); + + if (TextUtils.isEmpty(text)) { + view.setVisibility(View.GONE); + } else { + view.setVisibility(View.VISIBLE); + } + } + + public void setTextStrings(String text1, String text2, boolean isHtml, Context context) { + mText1Strings = new MultiStateText(isHtml, text1, context); + mText2Strings = new MultiStateText(isHtml, text2, context); + + ChildViewCache views = (ChildViewCache) getTag(); + setInitialTextForView(views.mText1, mText1Strings, text1); + setInitialTextForView(views.mText2, mText2Strings, text2); + } + + public void updateTextViewContentIfRequired() { + // Check if the pressed or selected state has changed since the last call. + boolean isPressedNow = isPressed(); + boolean isSelectedNow = isSelected(); + + ChildViewCache views = (ChildViewCache) getTag(); + views.mText1.setText((isPressedNow ? mText1Strings.pressed() : + (isSelectedNow ? mText1Strings.selected() : mText1Strings.normal()))); + views.mText2.setText((isPressedNow ? mText2Strings.pressed() : + (isSelectedNow ? mText2Strings.selected() : mText2Strings.normal()))); + } + public void setColor(int backgroundColor) { mBackgroundColor = backgroundColor; } @Override public void dispatchDraw(Canvas canvas) { + updateTextViewContentIfRequired(); + if (mBackgroundColor != 0 && !isPressed() && !isSelected()) { canvas.drawColor(mBackgroundColor); } diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java index 34a1a0c..37a58a9 100644 --- a/core/java/android/backup/BackupManager.java +++ b/core/java/android/backup/BackupManager.java @@ -42,6 +42,9 @@ import android.util.Log; public class BackupManager { private static final String TAG = "BackupManager"; + /** @hide TODO: REMOVE THIS */ + public static final boolean EVEN_THINK_ABOUT_DOING_RESTORE = false; + private Context mContext; private static IBackupManager sService; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 5cc5730..6b50405 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -17,6 +17,7 @@ package android.content; import android.content.pm.PackageManager; +import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; @@ -29,6 +30,7 @@ import android.database.SQLException; import android.net.Uri; import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.os.Process; import java.io.File; import java.io.FileNotFoundException; @@ -65,8 +67,10 @@ import java.io.FileNotFoundException; */ public abstract class ContentProvider implements ComponentCallbacks { private Context mContext = null; + private int mMyUid; private String mReadPermission; private String mWritePermission; + private PathPermission[] mPathPermissions; private Transport mTransport = new Transport(); @@ -108,24 +112,20 @@ public abstract class ContentProvider implements ComponentCallbacks { public IBulkCursor bulkQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, CursorWindow window) { - checkReadPermission(uri); + enforceReadPermission(uri); Cursor cursor = ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder); if (cursor == null) { return null; } - String wperm = getWritePermission(); return new CursorToBulkCursorAdaptor(cursor, observer, ContentProvider.this.getClass().getName(), - wperm == null || - getContext().checkCallingOrSelfPermission(getWritePermission()) - == PackageManager.PERMISSION_GRANTED, - window); + hasWritePermission(uri), window); } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - checkReadPermission(uri); + enforceReadPermission(uri); return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder); } @@ -136,55 +136,84 @@ public abstract class ContentProvider implements ComponentCallbacks { public Uri insert(Uri uri, ContentValues initialValues) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.insert(uri, initialValues); } public int bulkInsert(Uri uri, ContentValues[] initialValues) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.bulkInsert(uri, initialValues); } public int delete(Uri uri, String selection, String[] selectionArgs) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.delete(uri, selection, selectionArgs); } public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.update(uri, values, selection, selectionArgs); } public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - if (mode != null && mode.startsWith("rw")) checkWritePermission(uri); - else checkReadPermission(uri); + if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); + else enforceReadPermission(uri); return ContentProvider.this.openFile(uri, mode); } public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { - if (mode != null && mode.startsWith("rw")) checkWritePermission(uri); - else checkReadPermission(uri); + if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); + else enforceReadPermission(uri); return ContentProvider.this.openAssetFile(uri, mode); } public ISyncAdapter getSyncAdapter() { - checkWritePermission(null); + enforceWritePermission(null); SyncAdapter sa = ContentProvider.this.getSyncAdapter(); return sa != null ? sa.getISyncAdapter() : null; } - private void checkReadPermission(Uri uri) { + private void enforceReadPermission(Uri uri) { + final int uid = Binder.getCallingUid(); + if (uid == mMyUid) { + return; + } + + final Context context = getContext(); final String rperm = getReadPermission(); final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - if (getContext().checkUriPermission(uri, rperm, null, pid, uid, + if (rperm == null + || context.checkPermission(rperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return; + } + + PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0) { + i--; + final PathPermission pp = pps[i]; + final String pprperm = pp.getReadPermission(); + if (pprperm != null && pp.match(path)) { + if (context.checkPermission(pprperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { return; } + String msg = "Permission Denial: reading " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() @@ -193,20 +222,57 @@ public abstract class ContentProvider implements ComponentCallbacks { throw new SecurityException(msg); } - private void checkWritePermission(Uri uri) { + private boolean hasWritePermission(Uri uri) { + final int uid = Binder.getCallingUid(); + if (uid == mMyUid) { + return true; + } + + final Context context = getContext(); final String wperm = getWritePermission(); final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - if (getContext().checkUriPermission(uri, null, wperm, pid, uid, + if (wperm == null + || context.checkPermission(wperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0) { + i--; + final PathPermission pp = pps[i]; + final String ppwperm = pp.getWritePermission(); + if (ppwperm != null && pp.match(path)) { + if (context.checkPermission(ppwperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { + return true; + } + + return false; + } + + private void enforceWritePermission(Uri uri) { + if (hasWritePermission(uri)) { return; } + String msg = "Permission Denial: writing " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " requires " + wperm; + + " requires " + getWritePermission(); throw new SecurityException(msg); } } @@ -266,6 +332,28 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** + * Change the path-based permission required to read and/or write data in + * the content provider. This is normally set for you from its manifest + * information when the provider is first created. + * + * @param permissions Array of path permission descriptions. + */ + protected final void setPathPermissions(PathPermission[] permissions) { + mPathPermissions = permissions; + } + + /** + * Return the path-based permissions required for read and/or write access to + * this content provider. This method can be called from multiple + * threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>. + */ + public final PathPermission[] getPathPermissions() { + return mPathPermissions; + } + + /** * Called when the provider is being started. * * @return true if the provider was successfully loaded, false otherwise @@ -600,9 +688,11 @@ public abstract class ContentProvider implements ComponentCallbacks { */ if (mContext == null) { mContext = context; + mMyUid = Process.myUid(); if (info != null) { setReadPermission(info.readPermission); setWritePermission(info.writePermission); + setPathPermissions(info.pathPermissions); } ContentProvider.this.onCreate(); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index b293636..0e2deed 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1918,6 +1918,7 @@ public class PackageParser { outInfo.metaData, outError)) == null) { return false; } + } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestGrantUriPermission); @@ -1941,7 +1942,7 @@ public class PackageParser { if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } - + sa.recycle(); if (pa != null) { @@ -1956,6 +1957,101 @@ public class PackageParser { outInfo.info.uriPermissionPatterns = newp; } outInfo.info.grantUriPermissions = true; + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>"; + return false; + } + XmlUtils.skipCurrentTag(parser); + + } else if (parser.getName().equals("path-permission")) { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestPathPermission); + + PathPermission pa = null; + + String permission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_permission); + String readPermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission); + if (readPermission == null) { + readPermission = permission; + } + String writePermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission); + if (writePermission == null) { + writePermission = permission; + } + + boolean havePerm = false; + if (readPermission != null) { + readPermission = readPermission.intern(); + havePerm = true; + } + if (writePermission != null) { + writePermission = readPermission.intern(); + havePerm = true; + } + + if (!havePerm) { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No readPermission or writePermssion for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No readPermission or writePermssion for <path-permission>"; + return false; + } + + String path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_path); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_LITERAL, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_PREFIX, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission); + } + + sa.recycle(); + + if (pa != null) { + if (outInfo.info.pathPermissions == null) { + outInfo.info.pathPermissions = new PathPermission[1]; + outInfo.info.pathPermissions[0] = pa; + } else { + final int N = outInfo.info.pathPermissions.length; + PathPermission[] newp = new PathPermission[N+1]; + System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N); + newp[N] = pa; + outInfo.info.pathPermissions = newp; + } + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>"; + return false; } XmlUtils.skipCurrentTag(parser); diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java new file mode 100644 index 0000000..7e49d7d --- /dev/null +++ b/core/java/android/content/pm/PathPermission.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PatternMatcher; + +/** + * Description of permissions needed to access a particular path + * in a {@link ProviderInfo}. + */ +public class PathPermission extends PatternMatcher { + private final String mReadPermission; + private final String mWritePermission; + + public PathPermission(String pattern, int type, String readPermission, + String writePermission) { + super(pattern, type); + mReadPermission = readPermission; + mWritePermission = writePermission; + } + + public String getReadPermission() { + return mReadPermission; + } + + public String getWritePermission() { + return mWritePermission; + } + + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mReadPermission); + dest.writeString(mWritePermission); + } + + public PathPermission(Parcel src) { + super(src); + mReadPermission = src.readString(); + mWritePermission = src.readString(); + } + + public static final Parcelable.Creator<PathPermission> CREATOR + = new Parcelable.Creator<PathPermission>() { + public PathPermission createFromParcel(Parcel source) { + return new PathPermission(source); + } + + public PathPermission[] newArray(int size) { + return new PathPermission[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index b67ddf6..d01460e 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; */ public final class ProviderInfo extends ComponentInfo implements Parcelable { + /** The name provider is published under content:// */ public String authority = null; @@ -56,6 +57,14 @@ public final class ProviderInfo extends ComponentInfo */ public PatternMatcher[] uriPermissionPatterns = null; + /** + * If non-null, these are path-specific permissions that are allowed for + * accessing the provider. Any permissions listed here will allow a + * holding client to access the provider, and the provider will check + * the URI it provides when making calls against the patterns here. + */ + public PathPermission[] pathPermissions = null; + /** If true, this content provider allows multiple instances of itself * to run in different process. If false, a single instances is always * run in {@link #processName}. */ @@ -78,6 +87,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = orig.writePermission; grantUriPermissions = orig.grantUriPermissions; uriPermissionPatterns = orig.uriPermissionPatterns; + pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; initOrder = orig.initOrder; isSyncable = orig.isSyncable; @@ -94,6 +104,7 @@ public final class ProviderInfo extends ComponentInfo out.writeString(writePermission); out.writeInt(grantUriPermissions ? 1 : 0); out.writeTypedArray(uriPermissionPatterns, parcelableFlags); + out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); out.writeInt(initOrder); out.writeInt(isSyncable ? 1 : 0); @@ -122,6 +133,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = in.readString(); grantUriPermissions = in.readInt() != 0; uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); + pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; initOrder = in.readInt(); isSyncable = in.readInt() != 0; diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java index 2262477..c92f665 100755 --- a/core/java/android/gesture/Gesture.java +++ b/core/java/android/gesture/Gesture.java @@ -57,11 +57,6 @@ public class Gesture implements Parcelable { mGestureID = GESTURE_ID_BASE + sGestureCount++; } - void recycle() { - mStrokes.clear(); - mBoundingBox.setEmpty(); - } - /** * @return all the strokes of the gesture */ @@ -162,20 +157,6 @@ public class Gesture implements Parcelable { } /** - * draw the gesture - * - * @param canvas - */ - void draw(Canvas canvas, Paint paint) { - final ArrayList<GestureStroke> strokes = mStrokes; - final int count = strokes.size(); - - for (int i = 0; i < count; i++) { - strokes.get(i).draw(canvas, paint); - } - } - - /** * Create a bitmap of the gesture with a transparent background * * @param width width of the target bitmap diff --git a/core/java/android/gesture/package.html b/core/java/android/gesture/package.html index a54a017..32c44d2 100644 --- a/core/java/android/gesture/package.html +++ b/core/java/android/gesture/package.html @@ -1,5 +1,5 @@ <HTML> <BODY> -@hide +Provides classes to create, recognize, load and save gestures. </BODY> </HTML> diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 1214abc..51e6c1e 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -68,6 +68,12 @@ public class Process { public static final int PHONE_UID = 1001; /** + * Defines the UID/GID for the WIFI supplicant process. + * @hide + */ + public static final int WIFI_UID = 1010; + + /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning * to applications. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index aa583ac..6ed1ac8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1328,7 +1328,7 @@ public final class Settings { * boolean (1 or 0). */ public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; - + /** * Whether live web suggestions while the user types into search dialogs are * enabled. Browsers and other search UIs should respect this, as it allows @@ -2300,7 +2300,7 @@ public final class Settings { * @hide */ public static final String BACKUP_TRANSPORT = "backup_transport"; - + /** * Version for which the setup wizard was last shown. Bumped for * each release when there is new setup information to show. @@ -2954,6 +2954,13 @@ public final class Settings { "vending_pd_resend_frequency_ms"; /** + * Frequency in milliseconds at which we should cycle through the promoted applications + * on the home screen or the categories page. + */ + public static final String VENDING_PROMO_REFRESH_FREQUENCY_MS = + "vending_promo_refresh_freq_ms"; + + /** * URL that points to the legal terms of service to display in Settings. * <p> * This should be a https URL. For a pretty user-friendly URL, use diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index b959907..c615957 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -247,7 +247,12 @@ public class Searchables { for (int i = 0; i < webSearchInfoList.size(); ++i) { ActivityInfo ai = webSearchInfoList.get(i).activityInfo; ComponentName component = new ComponentName(ai.packageName, ai.name); - newSearchablesForWebSearchList.add(newSearchablesMap.get(component)); + SearchableInfo searchable = newSearchablesMap.get(component); + if (searchable == null) { + Log.w(LOG_TAG, "did not find component in searchables: " + component); + } else { + newSearchablesForWebSearchList.add(searchable); + } } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index ec671d5..9f01923 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -16,15 +16,27 @@ package android.webkit; +import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.preference.PreferenceManager; import android.provider.Checkin; +import android.provider.Settings; +import android.util.Log; +import java.io.File; import java.lang.SecurityException; + +import android.content.SharedPreferences.Editor; import android.content.pm.PackageManager; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; +import java.util.HashSet; import java.util.Locale; /** @@ -176,6 +188,43 @@ public class WebSettings { private boolean mBuiltInZoomControls = false; private boolean mAllowFileAccess = true; + // Donut-specific hack to keep Gears permissions in sync with the + // system location setting. + // TODO: Make sure this hack is removed in Eclair, when Gears + // is also removed. + // Used to remember if we checked the Gears permissions already. + static boolean mCheckedGearsPermissions = false; + // The Gears permissions database directory. + private final static String GEARS_DATABASE_DIR = "gears"; + // The Gears permissions database file name. + private final static String GEARS_DATABASE_FILE = "permissions.db"; + // The Gears location permissions table. + private final static String GEARS_LOCATION_ACCESS_TABLE_NAME = + "LocationAccess"; + // The Gears storage access permissions table. + private final static String GEARS_STORAGE_ACCESS_TABLE_NAME = "Access"; + // The Gears permissions db schema version table. + private final static String GEARS_SCHEMA_VERSION_TABLE_NAME = + "VersionInfo"; + // The shared pref name. + private static final String LAST_KNOWN_LOCATION_SETTING = + "lastKnownLocationSystemSetting"; + // The Browser package name. + private static final String BROWSER_PACKAGE_NAME = "com.android.browser"; + // The Google URLs whitelisted for Gears location access. + private static HashSet<String> sGearsWhiteList; + + static { + sGearsWhiteList = new HashSet<String>(); + // NOTE: DO NOT ADD A "/" AT THE END! + sGearsWhiteList.add("http://www.google.com"); + sGearsWhiteList.add("http://www.google.co.uk"); + } + + private static final String LOGTAG = "webcore"; + static final boolean DEBUG = false; + static final boolean LOGV_ENABLED = DEBUG; + // Class to handle messages before WebCore is ready. private class EventHandler { // Message id for syncing @@ -196,6 +245,7 @@ public class WebSettings { switch (msg.what) { case SYNC: synchronized (WebSettings.this) { + checkGearsPermissions(); if (mBrowserFrame.mNativeFrame != 0) { nativeSync(mBrowserFrame.mNativeFrame); } @@ -1163,6 +1213,126 @@ public class WebSettings { return size; } + private void checkGearsPermissions() { + // Did we already check the permissions? + if (mCheckedGearsPermissions) { + return; + } + // Are we running in the browser? + if (!BROWSER_PACKAGE_NAME.equals(mContext.getPackageName())) { + return; + } + // Is the pluginsPath sane? + if (mPluginsPath == null || mPluginsPath.length() == 0) { + // We don't yet have a meaningful plugin path, so + // we can't do anything about the Gears permissions. + return; + } + // Remember we checked the Gears permissions. + mCheckedGearsPermissions = true; + // Get the current system settings. + int setting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USE_LOCATION_FOR_SERVICES, -1); + // Check if we need to set the Gears permissions. + if (setting != -1 && locationSystemSettingChanged(setting)) { + setGearsPermissionForGoogleDomains(setting); + } + } + + private boolean locationSystemSettingChanged(int newSetting) { + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(mContext); + int oldSetting = 0; + oldSetting = prefs.getInt(LAST_KNOWN_LOCATION_SETTING, oldSetting); + if (oldSetting == newSetting) { + return false; + } + Editor ed = prefs.edit(); + ed.putInt(LAST_KNOWN_LOCATION_SETTING, newSetting); + ed.commit(); + return true; + } + + private void setGearsPermissionForGoogleDomains(int systemPermission) { + // Transform the system permission into a Gears permission + int gearsPermission = (systemPermission == 1 ? 1 : 2); + // Build the path to the Gears library. + + File file = new File(mPluginsPath).getParentFile(); + if (file == null) { + return; + } + // Build the Gears database file name. + file = new File(file.getAbsolutePath() + File.separator + + GEARS_DATABASE_DIR + File.separator + GEARS_DATABASE_FILE); + // Remember whether or not we need to create the LocationAccess table. + boolean needToCreateTables = !file.exists(); + // Try opening the Gears database. + SQLiteDatabase permissions; + try { + permissions = SQLiteDatabase.openOrCreateDatabase(file, null); + } catch (SQLiteException e) { + if (LOGV_ENABLED) { + Log.v(LOGTAG, "Could not open Gears permission DB: " + + e.getMessage()); + } + // Just bail out. + return; + } + // We now have a database open. Begin a transaction. + permissions.beginTransaction(); + try { + if (needToCreateTables) { + // Create the tables. Note that this creates the + // Gears tables for the permissions DB schema version 2. + // The Gears schema upgrade process will take care of the rest. + // First, the storage access table. + SQLiteStatement statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_STORAGE_ACCESS_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Next the location access table. + statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_LOCATION_ACCESS_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Finally, the schema version table. + statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_SCHEMA_VERSION_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Set the schema version to 2. + ContentValues schema = new ContentValues(); + schema.put("Name", "Version"); + schema.put("Value", 2); + permissions.insert(GEARS_SCHEMA_VERSION_TABLE_NAME, null, + schema); + } + + ContentValues permissionValues = new ContentValues(); + + for (String url : sGearsWhiteList) { + permissionValues.put("Name", url); + permissionValues.put("Value", gearsPermission); + permissions.replace(GEARS_LOCATION_ACCESS_TABLE_NAME, null, + permissionValues); + permissionValues.clear(); + } + // Commit the transaction. + permissions.setTransactionSuccessful(); + } catch (SQLiteException e) { + if (LOGV_ENABLED) { + Log.v(LOGTAG, "Could not set the Gears permissions: " + + e.getMessage()); + } + } finally { + permissions.endTransaction(); + permissions.close(); + } + } /* Post a SYNC message to handle syncing the native settings. */ private synchronized void postSync() { // Only post if a sync is not pending diff --git a/core/jni/android_backup_BackupDataOutput.cpp b/core/jni/android_backup_BackupDataOutput.cpp index d02590e..ce30aaa 100644 --- a/core/jni/android_backup_BackupDataOutput.cpp +++ b/core/jni/android_backup_BackupDataOutput.cpp @@ -70,7 +70,7 @@ writeEntityData_native(JNIEnv* env, jobject clazz, int w, jbyteArray data, int s int err; BackupDataWriter* writer = (BackupDataWriter*)w; - if (env->GetArrayLength(data) > size) { + if (env->GetArrayLength(data) < size) { // size mismatch return -1; } diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp index 9f93e2f..ae744a8 100644 --- a/core/jni/android_net_wifi_Wifi.cpp +++ b/core/jni/android_net_wifi_Wifi.cpp @@ -27,6 +27,8 @@ namespace android { +static jboolean sScanModeActive = false; + /* * The following remembers the jfieldID's of the fields * of the DhcpInfo Java object, so that we don't have @@ -254,27 +256,29 @@ static jboolean android_net_wifi_reassociateCommand(JNIEnv* env, jobject clazz) return doBooleanCommand("REASSOCIATE", "OK"); } -static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz) +static jboolean doSetScanMode(jboolean setActive) +{ + return doBooleanCommand((setActive ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE"), "OK"); +} + +static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz, jboolean forceActive) { jboolean result; + // Ignore any error from setting the scan mode. // The scan will still work. - (void)doBooleanCommand("DRIVER SCAN-ACTIVE", "OK"); + if (forceActive && !sScanModeActive) + doSetScanMode(true); result = doBooleanCommand("SCAN", "OK"); - (void)doBooleanCommand("DRIVER SCAN-PASSIVE", "OK"); + if (forceActive && !sScanModeActive) + doSetScanMode(sScanModeActive); return result; } static jboolean android_net_wifi_setScanModeCommand(JNIEnv* env, jobject clazz, jboolean setActive) { - jboolean result; - // Ignore any error from setting the scan mode. - // The scan will still work. - if (setActive) { - return doBooleanCommand("DRIVER SCAN-ACTIVE", "OK"); - } else { - return doBooleanCommand("DRIVER SCAN-PASSIVE", "OK"); - } + sScanModeActive = setActive; + return doSetScanMode(setActive); } static jboolean android_net_wifi_startDriverCommand(JNIEnv* env, jobject clazz) @@ -509,7 +513,7 @@ static JNINativeMethod gWifiMethods[] = { { "disconnectCommand", "()Z", (void *)android_net_wifi_disconnectCommand }, { "reconnectCommand", "()Z", (void *)android_net_wifi_reconnectCommand }, { "reassociateCommand", "()Z", (void *)android_net_wifi_reassociateCommand }, - { "scanCommand", "()Z", (void*) android_net_wifi_scanCommand }, + { "scanCommand", "(Z)Z", (void*) android_net_wifi_scanCommand }, { "setScanModeCommand", "(Z)Z", (void*) android_net_wifi_setScanModeCommand }, { "startDriverCommand", "()Z", (void*) android_net_wifi_startDriverCommand }, { "stopDriverCommand", "()Z", (void*) android_net_wifi_stopDriverCommand }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 599360f..23967f4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -995,6 +995,29 @@ android:description="@string/permdesc_changeBackgroundDataSetting" android:label="@string/permlab_changeBackgroundDataSetting" /> + <!-- This permission can be used on content providers to allow the global + search system to access their data. Typically it used when the + provider has some permissions protecting it (which global search + would not be expected to hold), and added as a read-only permission + to the path in the provider where global search queries are + performed. This permission can not be held by regular applications; + it is used by applications to protect themselves from everyone else + besides global search. --> + <permission android:name="android.permission.GLOBAL_SEARCH" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:protectionLevel="signatureOrSystem" /> + + <!-- Internal permission protecting access to the global search + system: ensures that only the system can access the provider + to perform queries (since this otherwise provides unrestricted + access to a variety of content providers), and to write the + search statistics (to keep applications from gaming the source + ranking). + @hide --> + <permission android:name="android.permission.GLOBAL_SEARCH_CONTROL" + android:permissionGroup="android.permission-group.SYSTEM_TOOLS" + android:protectionLevel="signature" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/color/search_url_text.xml b/core/res/res/color/search_url_text.xml new file mode 100644 index 0000000..449fdf0 --- /dev/null +++ b/core/res/res/color/search_url_text.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="@android:color/search_url_text_pressed"/> + <item android:state_selected="true" android:color="@android:color/search_url_text_selected"/> + <item android:color="@android:color/search_url_text_normal"/> <!-- not selected --> +</selector> diff --git a/core/res/res/values-no-rNO/arrays.xml b/core/res/res/values-nb-rNO/arrays.xml index 500e8e1..500e8e1 100644 --- a/core/res/res/values-no-rNO/arrays.xml +++ b/core/res/res/values-nb-rNO/arrays.xml diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 7571e24..12a76ba 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -953,6 +953,20 @@ <attr name="pathPattern" format="string" /> </declare-styleable> + <!-- Attributes that can be supplied in an AndroidManifest.xml + <code>path-permission</code> tag, a child of the + {@link #AndroidManifestProvider provider} tag, describing a permission + that allows access to a specific path in the provider. This tag can be + specified multiple time to supply multiple paths. --> + <declare-styleable name="AndroidManifestPathPermission" parent="AndroidManifestProvider"> + <attr name="path" /> + <attr name="pathPrefix" /> + <attr name="pathPattern" /> + <attr name="permission" /> + <attr name="readPermission" /> + <attr name="writePermission" /> + </declare-styleable> + <!-- The <code>service</code> tag declares a {@link android.app.Service} class that is available as part of the package's application components, implementing diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index d284d0f..b7de997 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -74,7 +74,9 @@ <color name="perms_normal_perm_color">#c0c0c0</color> <!-- For search-related UIs --> - <color name="search_url_text">#7fa87f</color> + <color name="search_url_text_normal">#7fa87f</color> + <color name="search_url_text_selected">@android:color/black</color> + <color name="search_url_text_pressed">@android:color/black</color> <color name="search_widget_corpus_item_background">@android:color/lighter_gray</color> </resources> diff --git a/include/utils/String8.h b/include/utils/String8.h index c49faf6..ecc5774 100644 --- a/include/utils/String8.h +++ b/include/utils/String8.h @@ -29,11 +29,107 @@ // --------------------------------------------------------------------------- +extern "C" { + +typedef uint32_t char32_t; + +size_t strlen32(const char32_t *); +size_t strnlen32(const char32_t *, size_t); + +/* + * Returns the length of "src" when "src" is valid UTF-8 string. + * Returns 0 if src is NULL, 0-length string or non UTF-8 string. + * This function should be used to determine whether "src" is valid UTF-8 + * characters with valid unicode codepoints. "src" must be null-terminated. + * + * If you are going to use other GetUtf... functions defined in this header + * with string which may not be valid UTF-8 with valid codepoint (form 0 to + * 0x10FFFF), you should use this function before calling others, since the + * other functions do not check whether the string is valid UTF-8 or not. + * + * If you do not care whether "src" is valid UTF-8 or not, you should use + * strlen() as usual, which should be much faster. + */ +size_t utf8_length(const char *src); + +/* + * Returns the UTF-32 length of "src". + */ +size_t utf32_length(const char *src, size_t src_len); + +/* + * Returns the UTF-8 length of "src". + */ +size_t utf8_length_from_utf32(const char32_t *src, size_t src_len); + +/* + * Returns the unicode value at "index". + * Returns -1 when the index is invalid (equals to or more than "src_len"). + * If returned value is positive, it is able to be converted to char32_t, which + * is unsigned. Then, if "next_index" is not NULL, the next index to be used is + * stored in "next_index". "next_index" can be NULL. + */ +int32_t utf32_at(const char *src, size_t src_len, + size_t index, size_t *next_index); + +/* + * Stores a UTF-32 string converted from "src" in "dst", if "dst_length" is not + * large enough to store the string, the part of the "src" string is stored + * into "dst". + * Returns the size actually used for storing the string. + * "dst" is not null-terminated when dst_len is fully used (like strncpy). + */ +size_t utf8_to_utf32(const char* src, size_t src_len, + char32_t* dst, size_t dst_len); + +/* + * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not + * large enough to store the string, the part of the "src" string is stored + * into "dst" as much as possible. See the examples for more detail. + * Returns the size actually used for storing the string. + * dst" is not null-terminated when dst_len is fully used (like strncpy). + * + * Example 1 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" >= 7 + * -> + * Returned value == 6 + * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0 + * (note that "dst" is null-terminated) + * + * Example 2 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" == 5 + * -> + * Returned value == 3 + * "dst" becomes \xE3\x81\x82\0 + * (note that "dst" is null-terminated, but \u3044 is not stored in "dst" + * since "dst" does not have enough size to store the character) + * + * Example 3 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" == 6 + * -> + * Returned value == 6 + * "dst" becomes \xE3\x81\x82\xE3\x81\x84 + * (note that "dst" is NOT null-terminated, like strncpy) + */ +size_t utf32_to_utf8(const char32_t* src, size_t src_len, + char* dst, size_t dst_len); + +} + +// --------------------------------------------------------------------------- + namespace android { class TextOutput; -//! This is a string holding UTF-8 characters. +//! This is a string holding UTF-8 characters. Does not allow the value more +// than 0x10FFFF, which is not valid unicode codepoint. class String8 { public: @@ -45,7 +141,8 @@ public: explicit String8(const String16& o); explicit String8(const char16_t* o); explicit String8(const char16_t* o, size_t numChars); - + explicit String8(const char32_t* o); + explicit String8(const char32_t* o, size_t numChars); ~String8(); inline const char* string() const; @@ -59,11 +156,20 @@ public: status_t setTo(const char* other); status_t setTo(const char* other, size_t numChars); status_t setTo(const char16_t* other, size_t numChars); - + status_t setTo(const char32_t* other, + size_t length); + status_t append(const String8& other); status_t append(const char* other); status_t append(const char* other, size_t numChars); + // Note that this function takes O(N) time to calculate the value. + // No cache value is stored. + size_t getUtf32Length() const; + int32_t getUtf32At(size_t index, + size_t *next_index) const; + size_t getUtf32(char32_t* dst, size_t dst_len) const; + inline String8& operator=(const String8& other); inline String8& operator=(const char* other); @@ -103,7 +209,7 @@ public: void toLower(size_t start, size_t numChars); void toUpper(); void toUpper(size_t start, size_t numChars); - + /* * These methods operate on the string as if it were a path name. */ @@ -346,7 +452,7 @@ inline String8::operator const char*() const return mString; } -}; // namespace android +} // namespace android // --------------------------------------------------------------------------- diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 8a19fbd..f5bdeda 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -738,12 +738,13 @@ bool AudioFlinger::streamMute(int stream) const bool AudioFlinger::isMusicActive() const { + Mutex::Autolock _l(mLock); #ifdef WITH_A2DP if (isA2dpEnabled()) { - return mA2dpMixerThread->isMusicActive(); + return mA2dpMixerThread->isMusicActive_l(); } #endif - return mHardwareMixerThread->isMusicActive(); + return mHardwareMixerThread->isMusicActive_l(); } status_t AudioFlinger::setParameter(const char* key, const char* value) @@ -1444,7 +1445,8 @@ bool AudioFlinger::MixerThread::streamMute(int stream) const return mStreamTypes[stream].mute; } -bool AudioFlinger::MixerThread::isMusicActive() const +// isMusicActive_l() must be called with AudioFlinger::mLock held +bool AudioFlinger::MixerThread::isMusicActive_l() const { size_t count = mActiveTracks.size(); for (size_t i = 0 ; i < count ; ++i) { @@ -2030,7 +2032,10 @@ void AudioFlinger::MixerThread::OutputTrack::write(int16_t* data, uint32_t frame inBuffer.i16 = data; if (mCblk->user == 0) { - if (mOutputMixerThread->isMusicActive()) { + mOutputMixerThread->mAudioFlinger->mLock.lock(); + bool isMusicActive = mOutputMixerThread->isMusicActive_l(); + mOutputMixerThread->mAudioFlinger->mLock.unlock(); + if (isMusicActive) { mCblk->forceReady = 1; LOGV("OutputTrack::start() force ready"); } else if (mCblk->frameCount > frames){ diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h index 8e47b29..634934e 100644 --- a/libs/audioflinger/AudioFlinger.h +++ b/libs/audioflinger/AudioFlinger.h @@ -463,7 +463,7 @@ private: virtual float streamVolume(int stream) const; virtual bool streamMute(int stream) const; - bool isMusicActive() const; + bool isMusicActive_l() const; sp<Track> createTrack_l( diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 109f28d..87edb01 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -1573,7 +1573,6 @@ status_t ResTable::add(Asset* asset, void* cookie, bool copyData) status_t ResTable::add(ResTable* src) { mError = src->mError; - mParams = src->mParams; for (size_t i=0; i<src->mHeaders.size(); i++) { mHeaders.add(src->mHeaders[i]); diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp index c50d343..e908ec1 100644 --- a/libs/utils/String8.cpp +++ b/libs/utils/String8.cpp @@ -25,25 +25,39 @@ #include <ctype.h> -namespace android { +/* + * Functions outside android is below the namespace android, since they use + * functions and constants in android namespace. + */ // --------------------------------------------------------------------------- -static const uint32_t kByteMask = 0x000000BF; -static const uint32_t kByteMark = 0x00000080; +namespace android { + +static const char32_t kByteMask = 0x000000BF; +static const char32_t kByteMark = 0x00000080; // Surrogates aren't valid for UTF-32 characters, so define some // constants that will let us screen them out. -static const uint32_t kUnicodeSurrogateHighStart = 0x0000D800; -static const uint32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; -static const uint32_t kUnicodeSurrogateLowStart = 0x0000DC00; -static const uint32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; -static const uint32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; -static const uint32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; +static const char32_t kUnicodeSurrogateHighStart = 0x0000D800; +static const char32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; +static const char32_t kUnicodeSurrogateLowStart = 0x0000DC00; +static const char32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; +static const char32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; +static const char32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; +static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF; // Mask used to set appropriate bits in first byte of UTF-8 sequence, // indexed by number of bytes in the sequence. -static const uint32_t kFirstByteMark[] = { +// 0xxxxxxx +// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000 +// 110yyyyx 10xxxxxx +// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0 +// 1110yyyy 10yxxxxx 10xxxxxx +// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0 +// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx +// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0 +static const char32_t kFirstByteMark[] = { 0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0 }; @@ -52,7 +66,7 @@ static const uint32_t kFirstByteMark[] = { #define RES_PATH_SEPARATOR '/' // Return number of utf8 bytes required for the character. -static size_t utf32_to_utf8_bytes(uint32_t srcChar) +static size_t utf32_to_utf8_bytes(char32_t srcChar) { size_t bytesToWrite; @@ -79,7 +93,7 @@ static size_t utf32_to_utf8_bytes(uint32_t srcChar) } } // Max code point for Unicode is 0x0010FFFF. - else if (srcChar < 0x00110000) + else if (srcChar <= kUnicodeMaxCodepoint) { bytesToWrite = 4; } @@ -94,7 +108,7 @@ static size_t utf32_to_utf8_bytes(uint32_t srcChar) // Write out the source character to <dstP>. -static void utf32_to_utf8(uint8_t* dstP, uint32_t srcChar, size_t bytes) +static void utf32_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes) { dstP += bytes; switch (bytes) @@ -126,7 +140,7 @@ void initialize_string8() // Bite me, Darwin! gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects; #endif - + SharedBuffer* buf = SharedBuffer::alloc(1); char* str = (char*)buf->data(); *str = 0; @@ -160,20 +174,20 @@ static char* allocFromUTF8(const char* in, size_t len) return getEmptyString(); } -// Note: not dealing with expanding surrogate pairs. -static char* allocFromUTF16(const char16_t* in, size_t len) +template<typename T, typename L> +static char* allocFromUTF16OrUTF32(const T* in, L len) { if (len == 0) return getEmptyString(); - + size_t bytes = 0; - const char16_t* end = in+len; - const char16_t* p = in; - + const T* end = in+len; + const T* p = in; + while (p < end) { bytes += utf32_to_utf8_bytes(*p); p++; } - + SharedBuffer* buf = SharedBuffer::alloc(bytes+1); LOG_ASSERT(buf, "Unable to allocate shared buffer"); if (buf) { @@ -181,19 +195,30 @@ static char* allocFromUTF16(const char16_t* in, size_t len) char* str = (char*)buf->data(); char* d = str; while (p < end) { - uint32_t c = *p++; + const T c = *p++; size_t len = utf32_to_utf8_bytes(c); utf32_to_utf8((uint8_t*)d, c, len); d += len; } *d = 0; - + return str; } - + return getEmptyString(); } +// Note: not dealing with expanding surrogate pairs. +static char* allocFromUTF16(const char16_t* in, size_t len) +{ + return allocFromUTF16OrUTF32<char16_t, size_t>(in, len); +} + +static char* allocFromUTF32(const char32_t* in, size_t len) +{ + return allocFromUTF16OrUTF32<char32_t, size_t>(in, len); +} + // --------------------------------------------------------------------------- String8::String8() @@ -238,6 +263,16 @@ String8::String8(const char16_t* o, size_t len) { } +String8::String8(const char32_t* o) + : mString(allocFromUTF32(o, strlen32(o))) +{ +} + +String8::String8(const char32_t* o, size_t len) + : mString(allocFromUTF32(o, len)) +{ +} + String8::~String8() { SharedBuffer::bufferFromData(mString)->release(); @@ -280,6 +315,16 @@ status_t String8::setTo(const char16_t* other, size_t len) return NO_MEMORY; } +status_t String8::setTo(const char32_t* other, size_t len) +{ + SharedBuffer::bufferFromData(mString)->release(); + mString = allocFromUTF32(other, len); + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + status_t String8::append(const String8& other) { const size_t otherLen = other.bytes(); @@ -418,6 +463,21 @@ void String8::toUpper(size_t start, size_t length) unlockBuffer(len); } +size_t String8::getUtf32Length() const +{ + return utf32_length(mString, length()); +} + +int32_t String8::getUtf32At(size_t index, size_t *next_index) const +{ + return utf32_at(mString, length(), index, next_index); +} + +size_t String8::getUtf32(char32_t* dst, size_t dst_len) const +{ + return utf8_to_utf32(mString, length(), dst, dst_len); +} + TextOutput& operator<<(TextOutput& to, const String8& val) { to << val.string(); @@ -427,7 +487,6 @@ TextOutput& operator<<(TextOutput& to, const String8& val) // --------------------------------------------------------------------------- // Path functions - void String8::setPathName(const char* name) { setPathName(name, strlen(name)); @@ -600,5 +659,192 @@ String8& String8::convertToResPath() return *this; } - }; // namespace android + +// --------------------------------------------------------------------------- + +size_t strlen32(const char32_t *s) +{ + const char32_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + +size_t strnlen32(const char32_t *s, size_t maxlen) +{ + const char32_t *ss = s; + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +size_t utf8_length(const char *src) +{ + const char *cur = src; + size_t ret = 0; + while (*cur != '\0') { + const char first_char = *cur++; + if ((first_char & 0x80) == 0) { // ASCII + ret += 1; + continue; + } + // (UTF-8's character must not be like 10xxxxxx, + // but 110xxxxx, 1110xxxx, ... or 1111110x) + if ((first_char & 0x40) == 0) { + return 0; + } + + int32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = 0; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80; + num_to_read < 5 && (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx + return 0; + } + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + // "first_char" must be (110xxxxx - 11110xxx) + if (num_to_read == 5) { + return 0; + } + to_ignore_mask |= mask; + utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1)); + if (utf32 > android::kUnicodeMaxCodepoint) { + return 0; + } + + ret += num_to_read; + } + return ret; +} + +size_t utf32_length(const char *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return 0; + } + size_t ret = 0; + const char* cur; + const char* end; + size_t num_to_skip; + for (cur = src, end = src + src_len, num_to_skip = 1; + cur < end; + cur += num_to_skip, ret++) { + const char first_char = *cur; + num_to_skip = 1; + if ((first_char & 0x80) == 0) { // ASCII + continue; + } + int32_t mask; + + for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) { + } + } + return ret; +} + +size_t utf8_length_from_utf32(const char32_t *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return 0; + } + size_t ret = 0; + const char32_t *end = src + src_len; + while (src < end) { + ret += android::utf32_to_utf8_bytes(*src++); + } + return ret; +} + +static int32_t utf32_at_internal(const char* cur, size_t *num_read) +{ + const char first_char = *cur; + if ((first_char & 0x80) == 0) { // ASCII + *num_read = 1; + return *cur; + } + cur++; + char32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = first_char; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80; + (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + to_ignore_mask |= mask; + utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1))); + + *num_read = num_to_read; + return static_cast<int32_t>(utf32); +} + +int32_t utf32_at(const char *src, size_t src_len, + size_t index, size_t *next_index) +{ + if (index >= src_len) { + return -1; + } + size_t dummy_index; + if (next_index == NULL) { + next_index = &dummy_index; + } + size_t num_read; + int32_t ret = utf32_at_internal(src + index, &num_read); + if (ret >= 0) { + *next_index = index + num_read; + } + + return ret; +} + +size_t utf8_to_utf32(const char* src, size_t src_len, + char32_t* dst, size_t dst_len) +{ + if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) { + return 0; + } + + const char* cur = src; + const char* end = src + src_len; + char32_t* cur_utf32 = dst; + const char32_t* end_utf32 = dst + dst_len; + while (cur_utf32 < end_utf32 && cur < end) { + size_t num_read; + *cur_utf32++ = + static_cast<char32_t>(utf32_at_internal(cur, &num_read)); + cur += num_read; + } + if (cur_utf32 < end_utf32) { + *cur_utf32 = 0; + } + return static_cast<size_t>(cur_utf32 - dst); +} + +size_t utf32_to_utf8(const char32_t* src, size_t src_len, + char* dst, size_t dst_len) +{ + if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) { + return 0; + } + const char32_t *cur_utf32 = src; + const char32_t *end_utf32 = src + src_len; + char *cur = dst; + const char *end = dst + dst_len; + while (cur_utf32 < end_utf32 && cur < end) { + size_t len = android::utf32_to_utf8_bytes(*cur_utf32); + android::utf32_to_utf8((uint8_t *)cur, *cur_utf32++, len); + cur += len; + } + if (cur < end) { + *cur = '\0'; + } + return cur - dst; +} diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java new file mode 100644 index 0000000..645f3f6 --- /dev/null +++ b/media/java/android/media/ExifInterface.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +/** + * Wrapper for native Exif library + * {@hide} + */ +public class ExifInterface { + private static final String TAG = "ExifInterface"; + private String mFilename; + + // Constants used for the Orientation Exif tag. + public static final int ORIENTATION_UNDEFINED = 0; + public static final int ORIENTATION_NORMAL = 1; + + // Constants used for white balance + public static final int WHITEBALANCE_AUTO = 0; + public static final int WHITEBALANCE_MANUAL = 1; + + // left right reversed mirror + public static final int ORIENTATION_FLIP_HORIZONTAL = 2; + public static final int ORIENTATION_ROTATE_180 = 3; + + // upside down mirror + public static final int ORIENTATION_FLIP_VERTICAL = 4; + + // flipped about top-left <--> bottom-right axis + public static final int ORIENTATION_TRANSPOSE = 5; + + // rotate 90 cw to right it + public static final int ORIENTATION_ROTATE_90 = 6; + + // flipped about top-right <--> bottom-left axis + public static final int ORIENTATION_TRANSVERSE = 7; + + // rotate 270 to right it + public static final int ORIENTATION_ROTATE_270 = 8; + + // The Exif tag names + public static final String TAG_ORIENTATION = "Orientation"; + + public static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal"; + public static final String TAG_MAKE = "Make"; + public static final String TAG_MODEL = "Model"; + public static final String TAG_FLASH = "Flash"; + public static final String TAG_IMAGE_WIDTH = "ImageWidth"; + public static final String TAG_IMAGE_LENGTH = "ImageLength"; + + public static final String TAG_GPS_LATITUDE = "GPSLatitude"; + public static final String TAG_GPS_LONGITUDE = "GPSLongitude"; + + public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef"; + public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; + public static final String TAG_WHITE_BALANCE = "WhiteBalance"; + + private boolean mSavedAttributes = false; + private boolean mHasThumbnail = false; + private HashMap<String, String> mCachedAttributes = null; + + static { + System.loadLibrary("exif"); + } + + private static ExifInterface sExifObj = null; + /** + * Since the underlying jhead native code is not thread-safe, + * ExifInterface should use singleton interface instead of public + * constructor. + */ + private static synchronized ExifInterface instance() { + if (sExifObj == null) { + sExifObj = new ExifInterface(); + } + + return sExifObj; + } + + /** + * The following 3 static methods are handy routines for atomic operation + * of underlying jhead library. It retrieves EXIF data and then release + * ExifInterface immediately. + */ + public static synchronized HashMap<String, String> loadExifData(String filename) { + ExifInterface exif = instance(); + HashMap<String, String> exifData = null; + if (exif != null) { + exif.setFilename(filename); + exifData = exif.getAttributes(); + } + return exifData; + } + + public static synchronized void saveExifData(String filename, HashMap<String, String> exifData) { + ExifInterface exif = instance(); + if (exif != null) { + exif.setFilename(filename); + exif.saveAttributes(exifData); + } + } + + public static synchronized byte[] getExifThumbnail(String filename) { + ExifInterface exif = instance(); + if (exif != null) { + exif.setFilename(filename); + return exif.getThumbnail(); + } + return null; + } + + public void setFilename(String filename) { + mFilename = filename; + } + + /** + * Given a HashMap of Exif tags and associated values, an Exif section in + * the JPG file is created and loaded with the tag data. saveAttributes() + * is expensive because it involves copying all the JPG data from one file + * to another and deleting the old file and renaming the other. It's best + * to collect all the attributes to write and make a single call rather + * than multiple calls for each attribute. You must call "commitChanges()" + * at some point to commit the changes. + */ + public void saveAttributes(HashMap<String, String> attributes) { + // format of string passed to native C code: + // "attrCnt attr1=valueLen value1attr2=value2Len value2..." + // example: + // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO" + StringBuilder sb = new StringBuilder(); + int size = attributes.size(); + if (attributes.containsKey("hasThumbnail")) { + --size; + } + sb.append(size + " "); + for (Map.Entry<String, String> iter : attributes.entrySet()) { + String key = iter.getKey(); + if (key.equals("hasThumbnail")) { + // this is a fake attribute not saved as an exif tag + continue; + } + String val = iter.getValue(); + sb.append(key + "="); + sb.append(val.length() + " "); + sb.append(val); + } + String s = sb.toString(); + saveAttributesNative(mFilename, s); + commitChangesNative(mFilename); + mSavedAttributes = true; + } + + /** + * Returns a HashMap loaded with the Exif attributes of the file. The key + * is the standard tag name and the value is the tag's value: e.g. + * Model -> Nikon. Numeric values are returned as strings. + */ + public HashMap<String, String> getAttributes() { + if (mCachedAttributes != null) { + return mCachedAttributes; + } + // format of string passed from native C code: + // "attrCnt attr1=valueLen value1attr2=value2Len value2..." + // example: + // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO" + mCachedAttributes = new HashMap<String, String>(); + + String attrStr = getAttributesNative(mFilename); + + // get count + int ptr = attrStr.indexOf(' '); + int count = Integer.parseInt(attrStr.substring(0, ptr)); + // skip past the space between item count and the rest of the attributes + ++ptr; + + for (int i = 0; i < count; i++) { + // extract the attribute name + int equalPos = attrStr.indexOf('=', ptr); + String attrName = attrStr.substring(ptr, equalPos); + ptr = equalPos + 1; // skip past = + + // extract the attribute value length + int lenPos = attrStr.indexOf(' ', ptr); + int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos)); + ptr = lenPos + 1; // skip pas the space + + // extract the attribute value + String attrValue = attrStr.substring(ptr, ptr + attrLen); + ptr += attrLen; + + if (attrName.equals("hasThumbnail")) { + mHasThumbnail = attrValue.equalsIgnoreCase("true"); + } else { + mCachedAttributes.put(attrName, attrValue); + } + } + return mCachedAttributes; + } + + /** + * Given a numerical white balance value, return a + * human-readable string describing it. + */ + public static String whiteBalanceToString(int whitebalance) { + switch (whitebalance) { + case WHITEBALANCE_AUTO: + return "Auto"; + case WHITEBALANCE_MANUAL: + return "Manual"; + default: + return ""; + } + } + + /** + * Given a numerical orientation, return a human-readable string describing + * the orientation. + */ + public static String orientationToString(int orientation) { + // TODO: this function needs to be localized and use string resource ids + // rather than strings + String orientationString; + switch (orientation) { + case ORIENTATION_NORMAL: + orientationString = "Normal"; + break; + case ORIENTATION_FLIP_HORIZONTAL: + orientationString = "Flipped horizontal"; + break; + case ORIENTATION_ROTATE_180: + orientationString = "Rotated 180 degrees"; + break; + case ORIENTATION_FLIP_VERTICAL: + orientationString = "Upside down mirror"; + break; + case ORIENTATION_TRANSPOSE: + orientationString = "Transposed"; + break; + case ORIENTATION_ROTATE_90: + orientationString = "Rotated 90 degrees"; + break; + case ORIENTATION_TRANSVERSE: + orientationString = "Transversed"; + break; + case ORIENTATION_ROTATE_270: + orientationString = "Rotated 270 degrees"; + break; + default: + orientationString = "Undefined"; + break; + } + return orientationString; + } + + /** + * Copies the thumbnail data out of the filename and puts it in the Exif + * data associated with the file used to create this object. You must call + * "commitChanges()" at some point to commit the changes. + */ + public boolean appendThumbnail(String thumbnailFileName) { + if (!mSavedAttributes) { + throw new RuntimeException("Must call saveAttributes " + + "before calling appendThumbnail"); + } + mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName); + return mHasThumbnail; + } + + public boolean hasThumbnail() { + if (!mSavedAttributes) { + getAttributes(); + } + return mHasThumbnail; + } + + public byte[] getThumbnail() { + return getThumbnailNative(mFilename); + } + + public static float[] getLatLng(HashMap<String, String> exifData) { + if (exifData == null) { + return null; + } + + String latValue = exifData.get(ExifInterface.TAG_GPS_LATITUDE); + String latRef = exifData.get(ExifInterface.TAG_GPS_LATITUDE_REF); + String lngValue = exifData.get(ExifInterface.TAG_GPS_LONGITUDE); + String lngRef = exifData.get(ExifInterface.TAG_GPS_LONGITUDE_REF); + float[] latlng = null; + + if (latValue != null && latRef != null + && lngValue != null && lngRef != null) { + latlng = new float[2]; + latlng[0] = ExifInterface.convertRationalLatLonToFloat( + latValue, latRef); + latlng[1] = ExifInterface.convertRationalLatLonToFloat( + lngValue, lngRef); + } + + return latlng; + } + + public static float convertRationalLatLonToFloat( + String rationalString, String ref) { + try { + String [] parts = rationalString.split(","); + + String [] pair; + pair = parts[0].split("/"); + int degrees = (int) (Float.parseFloat(pair[0].trim()) + / Float.parseFloat(pair[1].trim())); + + pair = parts[1].split("/"); + int minutes = (int) ((Float.parseFloat(pair[0].trim()) + / Float.parseFloat(pair[1].trim()))); + + pair = parts[2].split("/"); + float seconds = Float.parseFloat(pair[0].trim()) + / Float.parseFloat(pair[1].trim()); + + float result = degrees + (minutes / 60F) + (seconds / (60F * 60F)); + if ((ref.equals("S") || ref.equals("W"))) { + return -result; + } + return result; + } catch (RuntimeException ex) { + // if for whatever reason we can't parse the lat long then return + // null + return 0f; + } + } + + public static String convertRationalLatLonToDecimalString( + String rationalString, String ref, boolean usePositiveNegative) { + float result = convertRationalLatLonToFloat(rationalString, ref); + + String preliminaryResult = String.valueOf(result); + if (usePositiveNegative) { + String neg = (ref.equals("S") || ref.equals("E")) ? "-" : ""; + return neg + preliminaryResult; + } else { + return preliminaryResult + String.valueOf((char) 186) + " " + + ref; + } + } + + public static String makeLatLongString(double d) { + d = Math.abs(d); + + int degrees = (int) d; + + double remainder = d - degrees; + int minutes = (int) (remainder * 60D); + // really seconds * 1000 + int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D); + + String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000"; + return retVal; + } + + public static String makeLatStringRef(double lat) { + return lat >= 0D ? "N" : "S"; + } + + public static String makeLonStringRef(double lon) { + return lon >= 0D ? "W" : "E"; + } + + private native boolean appendThumbnailNative(String fileName, + String thumbnailFileName); + + private native void saveAttributesNative(String fileName, + String compressedAttributes); + + private native String getAttributesNative(String fileName); + + private native void commitChangesNative(String fileName); + + private native byte[] getThumbnailNative(String fileName); +} diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index cccc0fc..6de7bc1 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -54,7 +54,7 @@ import java.util.Iterator; /** * Internal service helper that no-one should use directly. - * + * * The way the scan currently works is: * - The Java MediaScannerService creates a MediaScanner (this class), and calls * MediaScanner.scanDirectories on it. @@ -96,7 +96,7 @@ import java.util.Iterator; * {@hide} */ public class MediaScanner -{ +{ static { System.loadLibrary("media_jni"); } @@ -108,17 +108,17 @@ public class MediaScanner Audio.Media.DATA, // 1 Audio.Media.DATE_MODIFIED, // 2 }; - + private static final int ID_AUDIO_COLUMN_INDEX = 0; private static final int PATH_AUDIO_COLUMN_INDEX = 1; private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2; - + private static final String[] VIDEO_PROJECTION = new String[] { Video.Media._ID, // 0 Video.Media.DATA, // 1 Video.Media.DATE_MODIFIED, // 2 }; - + private static final int ID_VIDEO_COLUMN_INDEX = 0; private static final int PATH_VIDEO_COLUMN_INDEX = 1; private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2; @@ -128,11 +128,11 @@ public class MediaScanner Images.Media.DATA, // 1 Images.Media.DATE_MODIFIED, // 2 }; - + private static final int ID_IMAGES_COLUMN_INDEX = 0; private static final int PATH_IMAGES_COLUMN_INDEX = 1; private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2; - + private static final String[] PLAYLISTS_PROJECTION = new String[] { Audio.Playlists._ID, // 0 Audio.Playlists.DATA, // 1 @@ -157,7 +157,7 @@ public class MediaScanner private static final String ALARMS_DIR = "/alarms/"; private static final String MUSIC_DIR = "/music/"; private static final String PODCAST_DIR = "/podcasts/"; - + private static final String[] ID3_GENRES = { // ID3v1 Genres "Blues", @@ -317,11 +317,11 @@ public class MediaScanner * to get the full system property. */ private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config."; - + // set to true if file path comparisons should be case insensitive. // this should be set when scanning files on a case insensitive file system. private boolean mCaseInsensitivePaths; - + private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); private static class FileCacheEntry { @@ -331,7 +331,7 @@ public class MediaScanner long mLastModified; boolean mSeenInFileSystem; boolean mLastModifiedChanged; - + FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) { mTableUri = tableUri; mRowId = rowId; @@ -346,10 +346,10 @@ public class MediaScanner return mPath; } } - - // hashes file path to FileCacheEntry. + + // hashes file path to FileCacheEntry. // path should be lower case if mCaseInsensitivePaths is true - private HashMap<String, FileCacheEntry> mFileCache; + private HashMap<String, FileCacheEntry> mFileCache; private ArrayList<FileCacheEntry> mPlayLists; private HashMap<String, Uri> mGenreCache; @@ -360,7 +360,7 @@ public class MediaScanner mContext = c; mBitmapOptions.inSampleSize = 1; mBitmapOptions.inJustDecodeBounds = true; - + setDefaultRingtoneFileNames(); } @@ -370,11 +370,11 @@ public class MediaScanner mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX + Settings.System.NOTIFICATION_SOUND); } - + private MyMediaScannerClient mClient = new MyMediaScannerClient(); - + private class MyMediaScannerClient implements MediaScannerClient { - + private String mArtist; private String mAlbumArtist; // use this if mArtist is missing private String mAlbum; @@ -389,11 +389,11 @@ public class MediaScanner private String mPath; private long mLastModified; private long mFileSize; - + public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) { - + // special case certain file names - // I use regionMatches() instead of substring() below + // I use regionMatches() instead of substring() below // to avoid memory allocation int lastSlash = path.lastIndexOf('/'); if (lastSlash >= 0 && lastSlash + 2 < path.length()) { @@ -401,7 +401,7 @@ public class MediaScanner if (path.regionMatches(lastSlash + 1, "._", 0, 2)) { return null; } - + // ignore album art files created by Windows Media Player: // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) { @@ -416,7 +416,7 @@ public class MediaScanner } } } - + mMimeType = null; // try mimeType first, if it is specified if (mimeType != null) { @@ -435,7 +435,7 @@ public class MediaScanner mMimeType = mediaFileType.mimeType; } } - + String key = path; if (mCaseInsensitivePaths) { key = path.toLowerCase(); @@ -446,20 +446,20 @@ public class MediaScanner mFileCache.put(key, entry); } entry.mSeenInFileSystem = true; - + // add some slack to avoid a rounding error long delta = lastModified - entry.mLastModified; if (delta > 1 || delta < -1) { entry.mLastModified = lastModified; entry.mLastModifiedChanged = true; } - + if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) { mPlayLists.add(entry); // we don't process playlists in the main scan, so return null return null; } - + // clear all the metadata mArtist = null; mAlbumArtist = null; @@ -472,10 +472,10 @@ public class MediaScanner mDuration = 0; mPath = path; mLastModified = lastModified; - + return entry; } - + public void scanFile(String path, long lastModified, long fileSize) { doScanFile(path, null, lastModified, fileSize, false); } @@ -513,7 +513,7 @@ public class MediaScanner } else if (MediaFile.isImageFileType(mFileType)) { // we used to compute the width and height but it's not worth it } - + result = endFile(entry, ringtones, notifications, alarms, music, podcasts); } } catch (RemoteException e) { @@ -531,17 +531,17 @@ public class MediaScanner char ch = s.charAt(start++); // return defaultValue if we have no integer at all if (ch < '0' || ch > '9') return defaultValue; - + int result = ch - '0'; while (start < length) { ch = s.charAt(start++); if (ch < '0' || ch > '9') return result; result = result * 10 + (ch - '0'); } - + return result; - } - + } + public void handleStringTag(String name, String value) { if (name.equalsIgnoreCase("title") || name.startsWith("title;")) { // Don't trim() here, to preserve the special \001 character @@ -577,7 +577,7 @@ public class MediaScanner // track number might be of the form "2/12" // we just read the number before the slash int num = parseSubstring(value, 0, 0); - mTrack = (mTrack / 1000) * 1000 + num; + mTrack = (mTrack / 1000) * 1000 + num; } else if (name.equalsIgnoreCase("discnumber") || name.equals("set") || name.startsWith("set;")) { // set number might be of the form "1/3" @@ -588,16 +588,16 @@ public class MediaScanner mDuration = parseSubstring(value, 0, 0); } } - + public void setMimeType(String mimeType) { mMimeType = mimeType; mFileType = MediaFile.getFileTypeForMimeType(mimeType); } - + /** * Formats the data into a values array suitable for use with the Media * Content Provider. - * + * * @return a map of values */ private ContentValues toValues() { @@ -608,7 +608,7 @@ public class MediaScanner map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified); map.put(MediaStore.MediaColumns.SIZE, mFileSize); map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); - + if (MediaFile.isVideoFileType(mFileType)) { map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING)); map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING)); @@ -629,9 +629,9 @@ public class MediaScanner } return map; } - + private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, - boolean alarms, boolean music, boolean podcasts) + boolean alarms, boolean music, boolean podcasts) throws RemoteException { // update database Uri tableUri; @@ -649,7 +649,7 @@ public class MediaScanner return null; } entry.mTableUri = tableUri; - + // use album artist if artist is missing if (mArtist == null || mArtist.length() == 0) { mArtist = mAlbumArtist; @@ -680,10 +680,18 @@ public class MediaScanner values.put(Audio.Media.IS_ALARM, alarms); values.put(Audio.Media.IS_MUSIC, music); values.put(Audio.Media.IS_PODCAST, podcasts); - } else if (isImage) { - // nothing right now + } else if (mFileType == MediaFile.FILE_TYPE_JPEG) { + HashMap<String, String> exifData = + ExifInterface.loadExifData(entry.mPath); + if (exifData != null) { + float[] latlng = ExifInterface.getLatLng(exifData); + if (latlng != null) { + values.put(Images.Media.LATITUDE, latlng[0]); + values.put(Images.Media.LONGITUDE, latlng[1]); + } + } } - + Uri result = null; long rowId = entry.mRowId; if (rowId == 0) { @@ -730,15 +738,15 @@ public class MediaScanner } } } - + if (uri != null) { - // add entry to audio_genre_map + // add entry to audio_genre_map values.clear(); values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId)); mMediaProvider.insert(uri, values); } } - + if (notifications && !mDefaultNotificationSet) { if (TextUtils.isEmpty(mDefaultNotificationFilename) || doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) { @@ -752,36 +760,36 @@ public class MediaScanner mDefaultRingtoneSet = true; } } - + return result; } - + private boolean doesPathHaveFilename(String path, String filename) { int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1; int filenameLength = filename.length(); return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) && pathFilenameStart + filenameLength == path.length(); } - + private void setSettingIfNotSet(String settingName, Uri uri, long rowId) { - + String existingSettingValue = Settings.System.getString(mContext.getContentResolver(), settingName); - + if (TextUtils.isEmpty(existingSettingValue)) { // Set the setting to the given URI Settings.System.putString(mContext.getContentResolver(), settingName, ContentUris.withAppendedId(uri, rowId).toString()); } } - + }; // end of anonymous MediaScannerClient instance - + private void prescan(String filePath) throws RemoteException { Cursor c = null; String where = null; String[] selectionArgs = null; - + if (mFileCache == null) { mFileCache = new HashMap<String, FileCacheEntry>(); } else { @@ -792,7 +800,7 @@ public class MediaScanner } else { mPlayLists.clear(); } - + // Build the list of files from the content provider try { // Read existing files from the audio table @@ -801,14 +809,14 @@ public class MediaScanner selectionArgs = new String[] { filePath }; } c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null); - + if (c != null) { try { while (c.moveToNext()) { long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX); String path = c.getString(PATH_AUDIO_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX); - + String key = path; if (mCaseInsensitivePaths) { key = path.toLowerCase(); @@ -829,14 +837,14 @@ public class MediaScanner where = null; } c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null); - + if (c != null) { try { while (c.moveToNext()) { long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX); String path = c.getString(PATH_VIDEO_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX); - + String key = path; if (mCaseInsensitivePaths) { key = path.toLowerCase(); @@ -858,7 +866,7 @@ public class MediaScanner } mOriginalCount = 0; c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null); - + if (c != null) { try { mOriginalCount = c.getCount(); @@ -866,7 +874,7 @@ public class MediaScanner long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX); String path = c.getString(PATH_IMAGES_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX); - + String key = path; if (mCaseInsensitivePaths) { key = path.toLowerCase(); @@ -879,7 +887,7 @@ public class MediaScanner c = null; } } - + if (mProcessPlaylists) { // Read existing files from the playlists table if (filePath != null) { @@ -888,16 +896,16 @@ public class MediaScanner where = null; } c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null); - + if (c != null) { try { while (c.moveToNext()) { String path = c.getString(PATH_IMAGES_COLUMN_INDEX); - + if (path != null && path.length() > 0) { long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX); - + String key = path; if (mCaseInsensitivePaths) { key = path.toLowerCase(); @@ -919,7 +927,7 @@ public class MediaScanner } } } - + private boolean inScanDirectory(String path, String[] directories) { for (int i = 0; i < directories.length; i++) { if (path.startsWith(directories[i])) { @@ -928,25 +936,25 @@ public class MediaScanner } return false; } - + private void pruneDeadThumbnailFiles() { HashSet<String> existingFiles = new HashSet<String>(); String directory = "/sdcard/DCIM/.thumbnails"; String [] files = (new File(directory)).list(); if (files == null) files = new String[0]; - + for (int i = 0; i < files.length; i++) { String fullPathString = directory + "/" + files[i]; existingFiles.add(fullPathString); } - + try { Cursor c = mMediaProvider.query( - mThumbsUri, - new String [] { "_data" }, - null, - null, + mThumbsUri, + new String [] { "_data" }, + null, + null, null); Log.v(TAG, "pruneDeadThumbnailFiles... " + c); if (c != null && c.moveToFirst()) { @@ -955,7 +963,7 @@ public class MediaScanner existingFiles.remove(fullPathString); } while (c.moveToNext()); } - + for (String fileToDelete : existingFiles) { if (Config.LOGV) Log.v(TAG, "fileToDelete is " + fileToDelete); @@ -964,7 +972,7 @@ public class MediaScanner } catch (SecurityException ex) { } } - + Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); if (c != null) { c.close(); @@ -980,10 +988,10 @@ public class MediaScanner while (iterator.hasNext()) { FileCacheEntry entry = iterator.next(); String path = entry.mPath; - + // remove database entries for files that no longer exist. boolean fileMissing = false; - + if (!entry.mSeenInFileSystem) { if (inScanDirectory(path, directories)) { // we didn't see this file in the scan directory. @@ -997,7 +1005,7 @@ public class MediaScanner } } } - + if (fileMissing) { // do not delete missing playlists, since they may have been modified by the user. // the user can delete them in the media player instead. @@ -1016,25 +1024,25 @@ public class MediaScanner } } } - + // handle playlists last, after we know what media files are on the storage. if (mProcessPlaylists) { processPlayLists(); } - + if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) pruneDeadThumbnailFiles(); - + // allow GC to clean up mGenreCache = null; mPlayLists = null; mFileCache = null; mMediaProvider = null; } - + private void initialize(String volumeName) { mMediaProvider = mContext.getContentResolver().acquireProvider("media"); - + mAudioUri = Audio.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); @@ -1051,23 +1059,23 @@ public class MediaScanner if ( Process.supportsProcesses()) { mCaseInsensitivePaths = true; } - } + } } public void scanDirectories(String[] directories, String volumeName) { try { long start = System.currentTimeMillis(); - initialize(volumeName); + initialize(volumeName); prescan(null); long prescan = System.currentTimeMillis(); - + for (int i = 0; i < directories.length; i++) { processDirectory(directories[i], MediaFile.sFileExtensions, mClient); } long scan = System.currentTimeMillis(); postscan(directories); long end = System.currentTimeMillis(); - + if (Config.LOGD) { Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n"); Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n"); @@ -1088,9 +1096,9 @@ public class MediaScanner // this function is used to scan a single file public Uri scanSingleFile(String path, String volumeName, String mimeType) { try { - initialize(volumeName); + initialize(volumeName); prescan(path); - + File file = new File(path); // always scan the file, so we can return the content://media Uri for existing files return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true); @@ -1105,7 +1113,7 @@ public class MediaScanner int result = 0; int end1 = path1.length(); int end2 = path2.length(); - + while (end1 > 0 && end2 > 0) { int slash1 = path1.lastIndexOf('/', end1 - 1); int slash2 = path2.lastIndexOf('/', end2 - 1); @@ -1123,13 +1131,13 @@ public class MediaScanner end2 = start2 - 1; } else break; } - + return result; } - private boolean addPlayListEntry(String entry, String playListDirectory, + private boolean addPlayListEntry(String entry, String playListDirectory, Uri uri, ContentValues values, int index) { - + // watch for trailing whitespace int entryLength = entry.length(); while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--; @@ -1146,36 +1154,36 @@ public class MediaScanner // if we have a relative path, combine entry with playListDirectory if (!fullPath) entry = playListDirectory + entry; - + //FIXME - should we look for "../" within the path? - + // best matching MediaFile for the play list entry FileCacheEntry bestMatch = null; - + // number of rightmost file/directory names for bestMatch - int bestMatchLength = 0; - + int bestMatchLength = 0; + Iterator<FileCacheEntry> iterator = mFileCache.values().iterator(); while (iterator.hasNext()) { FileCacheEntry cacheEntry = iterator.next(); String path = cacheEntry.mPath; - + if (path.equalsIgnoreCase(entry)) { bestMatch = cacheEntry; break; // don't bother continuing search } - + int matchLength = matchPaths(path, entry); if (matchLength > bestMatchLength) { bestMatch = cacheEntry; bestMatchLength = matchLength; } } - + if (bestMatch == null) { return false; } - + try { // OK, now we need to add this to the database values.clear(); @@ -1189,7 +1197,7 @@ public class MediaScanner return true; } - + private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) { BufferedReader reader = null; try { @@ -1266,7 +1274,7 @@ public class MediaScanner public WplHandler(String playListDirectory, Uri uri) { this.playListDirectory = playListDirectory; this.uri = uri; - + RootElement root = new RootElement("smil"); Element body = root.getChild("body"); Element seq = body.getChild("seq"); @@ -1316,12 +1324,12 @@ public class MediaScanner } } } - + private void processPlayLists() throws RemoteException { Iterator<FileCacheEntry> iterator = mPlayLists.iterator(); while (iterator.hasNext()) { FileCacheEntry entry = iterator.next(); - String path = entry.mPath; + String path = entry.mPath; // only process playlist files if they are new or have been modified since the last scan if (entry.mLastModifiedChanged) { @@ -1332,7 +1340,7 @@ public class MediaScanner long rowId = entry.mRowId; if (rowId == 0) { // Create a new playlist - + int lastDot = path.lastIndexOf('.'); String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot)); values.put(MediaStore.Audio.Playlists.NAME, name); @@ -1343,7 +1351,7 @@ public class MediaScanner membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY); } else { uri = ContentUris.withAppendedId(mPlaylistsUri, rowId); - + // update lastModified value of existing playlist values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified); mMediaProvider.update(uri, values, null, null); @@ -1352,7 +1360,7 @@ public class MediaScanner membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY); mMediaProvider.delete(membersUri, null, null); } - + String playListDirectory = path.substring(0, lastSlash + 1); MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); @@ -1363,7 +1371,7 @@ public class MediaScanner processPlsPlayList(path, playListDirectory, membersUri, values); else if (fileType == MediaFile.FILE_TYPE_WPL) processWplPlayList(path, playListDirectory, membersUri); - + Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null, null, null); try { @@ -1377,18 +1385,18 @@ public class MediaScanner } } } - + private native void processDirectory(String path, String extensions, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client); public native void setLocale(String locale); - + public native byte[] extractAlbumArt(FileDescriptor fd); private native final void native_setup(); private native final void native_finalize(); @Override - protected void finalize() { + protected void finalize() { mContext.getContentResolver().releaseProvider(mMediaProvider); - native_finalize(); + native_finalize(); } } diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index c22cd53..5435da7 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -1225,6 +1225,8 @@ audioCallback_EndLoop: LOGV("Cbk restarting track\n"); if (lpToneGen->prepareWave()) { lpToneGen->mState = TONE_STARTING; + // must reload lpToneDesc as prepareWave() may change mpToneDesc + lpToneDesc = lpToneGen->mpToneDesc; } else { LOGW("Cbk restarting prepareWave() failed\n"); lpToneGen->mState = TONE_IDLE; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index b6bc8a5..2b36904 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -16,12 +16,16 @@ package com.android.providers.settings; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.EOFException; import java.util.Arrays; import java.util.HashMap; +import java.util.zip.CRC32; import android.backup.BackupDataInput; import android.backup.BackupDataOutput; @@ -34,7 +38,9 @@ import android.database.Cursor; import android.media.AudioManager; import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.FileUtils; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; @@ -52,6 +58,13 @@ public class SettingsBackupAgent extends BackupHelperAgent { private static final String KEY_SYNC = "sync_providers"; private static final String KEY_LOCALE = "locale"; + private static final int STATE_SYSTEM = 0; + private static final int STATE_SECURE = 1; + private static final int STATE_SYNC = 2; + private static final int STATE_LOCALE = 3; + private static final int STATE_WIFI = 4; + private static final int STATE_SIZE = 5; // The number of state items + private static String[] sortedSystemKeys = null; private static String[] sortedSecureKeys = null; @@ -87,20 +100,22 @@ public class SettingsBackupAgent extends BackupHelperAgent { byte[] secureSettingsData = getSecureSettings(); byte[] syncProviders = mSettingsHelper.getSyncProviders(); byte[] locale = mSettingsHelper.getLocaleData(); - - data.writeEntityHeader(KEY_SYSTEM, systemSettingsData.length); - data.writeEntityData(systemSettingsData, systemSettingsData.length); - - data.writeEntityHeader(KEY_SECURE, secureSettingsData.length); - data.writeEntityData(secureSettingsData, secureSettingsData.length); - - data.writeEntityHeader(KEY_SYNC, syncProviders.length); - data.writeEntityData(syncProviders, syncProviders.length); - - data.writeEntityHeader(KEY_LOCALE, locale.length); - data.writeEntityData(locale, locale.length); - - backupFile(FILE_WIFI_SUPPLICANT, data); + byte[] wifiData = getFileData(FILE_WIFI_SUPPLICANT); + + long[] stateChecksums = readOldChecksums(oldState); + + stateChecksums[STATE_SYSTEM] = + writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); + stateChecksums[STATE_SECURE] = + writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); + stateChecksums[STATE_SYNC] = + writeIfChanged(stateChecksums[STATE_SYNC], KEY_SYNC, syncProviders, data); + stateChecksums[STATE_LOCALE] = + writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); + stateChecksums[STATE_WIFI] = + writeIfChanged(stateChecksums[STATE_WIFI], FILE_WIFI_SUPPLICANT, wifiData, data); + + writeNewChecksums(stateChecksums, newState); } @Override @@ -115,11 +130,15 @@ public class SettingsBackupAgent extends BackupHelperAgent { final int size = data.getDataSize(); if (KEY_SYSTEM.equals(key)) { restoreSettings(data, Settings.System.CONTENT_URI); + mSettingsHelper.applyAudioSettings(); } else if (KEY_SECURE.equals(key)) { restoreSettings(data, Settings.Secure.CONTENT_URI); -// TODO: Re-enable WIFI restore when we figure out a solution for the permissions -// } else if (FILE_WIFI_SUPPLICANT.equals(key)) { -// restoreFile(FILE_WIFI_SUPPLICANT, data); + } else if (FILE_WIFI_SUPPLICANT.equals(key)) { + restoreFile(FILE_WIFI_SUPPLICANT, data); + FileUtils.setPermissions(FILE_WIFI_SUPPLICANT, + FileUtils.S_IRUSR | FileUtils.S_IWUSR | + FileUtils.S_IRGRP | FileUtils.S_IWGRP, + Process.myUid(), Process.WIFI_UID); } else if (KEY_SYNC.equals(key)) { mSettingsHelper.setSyncProviders(data); } else if (KEY_LOCALE.equals(key)) { @@ -132,6 +151,49 @@ public class SettingsBackupAgent extends BackupHelperAgent { } } + private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException { + long[] stateChecksums = new long[STATE_SIZE]; + + DataInputStream dataInput = new DataInputStream( + new FileInputStream(oldState.getFileDescriptor())); + for (int i = 0; i < STATE_SIZE; i++) { + try { + stateChecksums[i] = dataInput.readLong(); + } catch (EOFException eof) { + break; + } + } + dataInput.close(); + return stateChecksums; + } + + private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState) + throws IOException { + DataOutputStream dataOutput = new DataOutputStream( + new FileOutputStream(newState.getFileDescriptor())); + for (int i = 0; i < STATE_SIZE; i++) { + dataOutput.writeLong(checksums[i]); + } + dataOutput.close(); + } + + private long writeIfChanged(long oldChecksum, String key, byte[] data, + BackupDataOutput output) { + CRC32 checkSummer = new CRC32(); + checkSummer.update(data); + long newChecksum = checkSummer.getValue(); + if (oldChecksum == newChecksum) { + return oldChecksum; + } + try { + output.writeEntityHeader(key, data.length); + output.writeEntityData(data, data.length); + } catch (IOException ioe) { + // Bail + } + return newChecksum; + } + private byte[] getSystemSettings() { Cursor sortedCursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null, null, Settings.NameValueTable.NAME); @@ -248,7 +310,7 @@ public class SettingsBackupAgent extends BackupHelperAgent { return result; } - private void backupFile(String filename, BackupDataOutput data) { + private byte[] getFileData(String filename) { try { File file = new File(filename); if (file.exists()) { @@ -260,14 +322,13 @@ public class SettingsBackupAgent extends BackupHelperAgent { got = fis.read(bytes, offset, bytes.length - offset); if (got > 0) offset += got; } while (offset < bytes.length && got > 0); - data.writeEntityHeader(filename, bytes.length); - data.writeEntityData(bytes, bytes.length); + return bytes; } else { - data.writeEntityHeader(filename, 0); - data.writeEntityData(EMPTY_DATA, 0); + return EMPTY_DATA; } } catch (IOException ioe) { Log.w(TAG, "Couldn't backup " + filename); + return EMPTY_DATA; } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 2c5775a..ca739e6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -167,6 +167,9 @@ public class SettingsHelper { // Check if locale was set by the user: Configuration conf = mContext.getResources().getConfiguration(); Locale loc = conf.locale; + // TODO: The following is not working as intended because the network is forcing a locale + // change after registering. Need to find some other way to detect if the user manually + // changed the locale if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard final String[] availableLocales = mContext.getAssets().getLocales(); @@ -193,6 +196,14 @@ public class SettingsHelper { } catch (RemoteException e) { // Intentionally left blank } + } + /** + * Informs the audio service of changes to the settings so that + * they can be re-read and applied. + */ + void applyAudioSettings() { + AudioManager am = new AudioManager(mContext); + am.reloadAudioSettings(); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2abf8b3..c0de9a5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -126,11 +126,14 @@ public class SettingsProvider extends ContentProvider { // a notification and then using the contract class to get their data, // the system property will be updated and they'll get the new data. + boolean backedUpDataChanged = false; String property = null, table = uri.getPathSegments().get(0); if (table.equals("system")) { property = Settings.System.SYS_PROP_SETTING_VERSION; + backedUpDataChanged = true; } else if (table.equals("secure")) { property = Settings.Secure.SYS_PROP_SETTING_VERSION; + backedUpDataChanged = true; } else if (table.equals("gservices")) { property = Settings.Gservices.SYS_PROP_SETTING_VERSION; } @@ -142,7 +145,9 @@ public class SettingsProvider extends ContentProvider { } // Inform the backup manager about a data change - mBackupManager.dataChanged(); + if (backedUpDataChanged) { + mBackupManager.dataChanged(); + } // Now send the notification through the content framework. String notify = uri.getQueryParameter("notify"); diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index a713edf..1eba469 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -38,6 +38,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeUnit; + /** * @hide Synthesizes speech from text. This is implemented as a service so that @@ -358,20 +360,38 @@ public class TtsService extends Service implements OnCompletionListener { * Stops all speech output and removes any utterances still in the queue. */ private int stop() { - Log.i("TTS", "Stopping"); - mSpeechQueue.clear(); + int result = TextToSpeech.TTS_ERROR; + boolean speechQueueAvailable = false; + try{ + // If the queue is locked for more than 1 second, + // something has gone very wrong with processSpeechQueue. + speechQueueAvailable = speechQueueLock.tryLock(1000, TimeUnit.MILLISECONDS); + if (speechQueueAvailable) { + Log.i("TTS", "Stopping"); + mSpeechQueue.clear(); - int result = nativeSynth.stop(); - mIsSpeaking = false; - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. + result = nativeSynth.stop(); + mIsSpeaking = false; + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + Log.i("TTS", "Stopped"); + } + } catch (InterruptedException e) { + Log.e("TTS stop", "tryLock interrupted"); + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); } + return result; } - Log.i("TTS", "Stopped"); - return result; } public void onCompletion(MediaPlayer arg0) { @@ -443,6 +463,7 @@ public class TtsService extends Service implements OnCompletionListener { } nativeSynth.speak(text); } catch (InterruptedException e) { + Log.e("TTS speakInternalOnly", "tryLock interrupted"); e.printStackTrace(); } finally { // This check is needed because finally will always run; @@ -497,6 +518,7 @@ public class TtsService extends Service implements OnCompletionListener { } nativeSynth.synthesizeToFile(text, filename); } catch (InterruptedException e) { + Log.e("TTS synthToFileInternalOnly", "tryLock interrupted"); e.printStackTrace(); } finally { // This check is needed because finally will always run; diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java index a60788a..22669d2 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java @@ -189,7 +189,7 @@ abstract class VpnService<E extends VpnProfile> { mServiceHelper.stop(); } catch (Throwable e) { - Log.e(TAG, "onError()", e); + Log.e(TAG, "onDisconnect()", e); onFinalCleanUp(); } } @@ -219,21 +219,28 @@ abstract class VpnService<E extends VpnProfile> { } private void waitUntilConnectedOrTimedout() { - sleep(2000); // 2 seconds - for (int i = 0; i < 60; i++) { - if (VPN_IS_UP.equals(SystemProperties.get(VPN_UP))) { - onConnected(); - return; - } - sleep(500); // 0.5 second - } + // Run this in the background thread to not block UI + new Thread(new Runnable() { + public void run() { + sleep(2000); // 2 seconds + for (int i = 0; i < 60; i++) { + if (VPN_IS_UP.equals(SystemProperties.get(VPN_UP))) { + onConnected(); + return; + } else if (mState != VpnState.CONNECTING) { + break; + } + sleep(500); // 0.5 second + } - synchronized (this) { - if (mState == VpnState.CONNECTING) { - Log.d(TAG, " connecting timed out !!"); - onError(); + synchronized (VpnService.this) { + if (mState == VpnState.CONNECTING) { + Log.d(TAG, " connecting timed out !!"); + onError(); + } + } } - } + }).start(); } private synchronized void onConnected() { diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 190d3e6..38fb7c9 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -49,6 +49,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Power; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.Vibrator; import android.provider.Settings; import android.text.TextUtils; @@ -945,6 +946,9 @@ class NotificationManagerService extends INotificationManager.Stub // to accidentally lose. private void updateAdbNotification() { if (mAdbEnabled && mBatteryPlugged == BatteryManager.BATTERY_PLUGGED_USB) { + if ("0".equals(SystemProperties.get("persist.adb.notify"))) { + return; + } if (!mAdbNotificationShown) { NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index a940af3..01394ad 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -436,7 +436,7 @@ public class WifiService extends IWifiManager.Stub { * see {@link android.net.wifi.WifiManager#startScan()} * @return {@code true} if the operation succeeds */ - public boolean startScan() { + public boolean startScan(boolean forceActive) { enforceChangePermission(); synchronized (mWifiStateTracker) { switch (mWifiStateTracker.getSupplicantState()) { @@ -450,7 +450,7 @@ public class WifiService extends IWifiManager.Stub { WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY); break; } - return WifiNative.scanCommand(); + return WifiNative.scanCommand(forceActive); } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 2fe4dd4..aad542a 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -56,6 +56,7 @@ import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageManager; +import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -7072,6 +7073,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen == PackageManager.PERMISSION_GRANTED) { return null; } + + PathPermission[] pps = cpi.pathPermissions; + if (pps != null) { + int i = pps.length; + while (i > 0) { + i--; + PathPermission pp = pps[i]; + if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid, + cpi.exported ? -1 : cpi.applicationInfo.uid) + == PackageManager.PERMISSION_GRANTED + && mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == -1) { + return null; + } + if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid, + cpi.exported ? -1 : cpi.applicationInfo.uid) + == PackageManager.PERMISSION_GRANTED) { + return null; + } + } + } + String msg = "Permission Denial: opening provider " + cpi.name + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid + ", uid=" + callingUid + ") requires " diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java index cbcac6c..39bbf16 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java @@ -72,6 +72,7 @@ public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShel // Run tests runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis); + activity.clearCache(); dumpMemoryInfo(); // Kill activity diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 0d22eca..09f7cbc 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -87,6 +87,10 @@ public class TestShellActivity extends Activity implements LayoutTestController } } + public void clearCache() { + mWebView.clearCache(true); + } + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 3d65d3c..fa328e8 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -40,7 +40,7 @@ interface IWifiManager boolean pingSupplicant(); - boolean startScan(); + boolean startScan(boolean forceActive); List<ScanResult> getScanResults(); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 7a15f27..1f73bec 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -476,9 +476,27 @@ public class WifiManager { * on completion of the scan. * @return {@code true} if the operation succeeded, i.e., the scan was initiated */ - public boolean startScan() { + public boolean startScan() { try { - return mService.startScan(); + return mService.startScan(false); + } catch (RemoteException e) { + return false; + } + } + + /** + * Request a scan for access points. Returns immediately. The availability + * of the results is made known later by means of an asynchronous event sent + * on completion of the scan. + * This is a variant of startScan that forces an active scan, even if passive + * scans are the current default + * @return {@code true} if the operation succeeded, i.e., the scan was initiated + * + * @hide + */ + public boolean startScanActive() { + try { + return mService.startScan(true); } catch (RemoteException e) { return false; } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 3851ac0..0799f5f 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -51,7 +51,7 @@ public class WifiNative { public native static boolean pingCommand(); - public native static boolean scanCommand(); + public native static boolean scanCommand(boolean forceActive); public native static boolean setScanModeCommand(boolean setActive); diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index 2fbc779..63687b3 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -1121,7 +1121,7 @@ public class WifiStateTracker extends NetworkStateTracker { } else { // In some situations, supplicant needs to be kickstarted to // start the background scanning - WifiNative.scanCommand(); + WifiNative.scanCommand(true); } } } |
