diff options
340 files changed, 25837 insertions, 3712 deletions
diff --git a/Android.mk b/Android.mk index b11877e2cde2..9c5773fd8ef5 100644 --- a/Android.mk +++ b/Android.mk @@ -362,6 +362,34 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS += \ -hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \ -hdf sdk.current $(framework_docs_SDK_CURRENT_DIR) +# ==== the api stubs and current.xml =========================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES) +LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES) +LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES) +LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS) +LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH) +LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR) +LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR) + +LOCAL_MODULE := api-stubs + +LOCAL_DROIDDOC_OPTIONS:=\ + $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ + -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_stubs_current_intermediates/src \ + -apixml $(INTERNAL_PLATFORM_API_FILE) \ + -nodocs + +LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk +LOCAL_DROIDDOC_CUSTOM_ASSET_DIR:=assets-sdk + +include $(BUILD_DROIDDOC) + +$(full_target): $(framework_built) +$(INTERNAL_PLATFORM_API_FILE): $(full_target) +$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) + # ==== static html in the sdk ================================== include $(CLEAR_VARS) @@ -380,10 +408,7 @@ LOCAL_DROIDDOC_OPTIONS:=\ -title "Android SDK" \ -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \ -todo $(OUT_DOCS)/$(LOCAL_MODULE)-docs-todo.html \ - -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_stubs_current_intermediates/src \ - -apixml $(INTERNAL_PLATFORM_API_FILE) \ -sdkvalues $(OUT_DOCS) \ - -warning 3 \ -hdf android.whichdoc offline LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk @@ -399,8 +424,6 @@ $(static_doc_index_redirect): \ $(full_target): $(static_doc_index_redirect) $(full_target): $(framework_built) -$(INTERNAL_PLATFORM_API_FILE): $(full_target) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) # ==== docs for the web (on the google app engine server) ======================= diff --git a/api/current.xml b/api/current.xml index a31208ab7a8e..3a7fd2f921bb 100644 --- a/api/current.xml +++ b/api/current.xml @@ -25978,6 +25978,50 @@ visibility="public" > </field> +<field name="EXTRA_DATA_KEY" + type="java.lang.String" + transient="false" + volatile="false" + value=""intent_extra_data_key"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="INTENT_ACTION_SEARCHABLES_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.search.action.SEARCHABLES_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="INTENT_ACTION_SEARCH_SETTINGS_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.search.action.SETTINGS_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="INTENT_ACTION_WEB_SEARCH_SETTINGS" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.search.action.WEB_SEARCH_SETTINGS"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="MENU_KEY" type="char" transient="false" @@ -26011,6 +26055,17 @@ visibility="public" > </field> +<field name="SHORTCUT_MIME_TYPE" + type="java.lang.String" + transient="false" + volatile="false" + value=""vnd.android.cursor.item/vnd.android.search.suggest"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SUGGEST_COLUMN_FORMAT" type="java.lang.String" transient="false" @@ -26077,6 +26132,17 @@ visibility="public" > </field> +<field name="SUGGEST_COLUMN_INTENT_EXTRA_DATA" + type="java.lang.String" + transient="false" + volatile="false" + value=""suggest_intent_extra_data"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SUGGEST_COLUMN_QUERY" type="java.lang.String" transient="false" @@ -26088,6 +26154,28 @@ visibility="public" > </field> +<field name="SUGGEST_COLUMN_SHORTCUT_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""suggest_shortcut_id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING" + type="java.lang.String" + transient="false" + volatile="false" + value=""suggest_spinner_while_refreshing"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SUGGEST_COLUMN_TEXT_1" type="java.lang.String" transient="false" @@ -26121,6 +26209,17 @@ visibility="public" > </field> +<field name="SUGGEST_NEVER_MAKE_SHORTCUT" + type="java.lang.String" + transient="false" + volatile="false" + value=""_-1"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SUGGEST_URI_PATH_QUERY" type="java.lang.String" transient="false" @@ -26132,6 +26231,17 @@ visibility="public" > </field> +<field name="SUGGEST_URI_PATH_SHORTCUT" + type="java.lang.String" + transient="false" + volatile="false" + value=""search_suggest_shortcut"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="USER_QUERY" type="java.lang.String" transient="false" @@ -35638,6 +35748,39 @@ visibility="public" > </field> +<field name="ACTION_TTS_CHECK_TTS_DATA" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.CHECK_TTS_DATA"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_TTS_INSTALL_TTS_DATA" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.INSTALL_TTS_DATA"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_TTS_QUEUE_PROCESSING_COMPLETED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.TTS_QUEUE_PROCESSING_COMPLETED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_UID_REMOVED" type="java.lang.String" transient="false" @@ -52656,6 +52799,17 @@ visibility="public" > </method> +<method name="getDensityScale" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getHeight" return="int" abstract="false" @@ -52729,6 +52883,58 @@ visibility="public" > </method> +<method name="getScaledHeight" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="canvas" type="android.graphics.Canvas"> +</parameter> +</method> +<method name="getScaledHeight" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="metrics" type="android.util.DisplayMetrics"> +</parameter> +</method> +<method name="getScaledWidth" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="canvas" type="android.graphics.Canvas"> +</parameter> +</method> +<method name="getScaledWidth" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="metrics" type="android.util.DisplayMetrics"> +</parameter> +</method> <method name="getWidth" return="int" abstract="false" @@ -52751,6 +52957,17 @@ visibility="public" > </method> +<method name="isAutoScalingEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isMutable" return="boolean" abstract="false" @@ -52795,6 +53012,32 @@ visibility="public" > </method> +<method name="setAutoScalingEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="autoScalingEnabled" type="boolean"> +</parameter> +</method> +<method name="setDensityScale" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="densityScale" type="float"> +</parameter> +</method> <method name="setPixel" return="void" abstract="false" @@ -52862,6 +53105,17 @@ visibility="public" > </field> +<field name="DENSITY_SCALE_UNKNOWN" + type="float" + transient="false" + volatile="false" + value="-1.0f" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="Bitmap.CompressFormat" extends="java.lang.Enum" @@ -53081,6 +53335,27 @@ deprecated="not deprecated" visibility="public" > +<parameter name="res" type="android.content.res.Resources"> +</parameter> +<parameter name="value" type="android.util.TypedValue"> +</parameter> +<parameter name="is" type="java.io.InputStream"> +</parameter> +<parameter name="pad" type="android.graphics.Rect"> +</parameter> +<parameter name="opts" type="android.graphics.BitmapFactory.Options"> +</parameter> +</method> +<method name="decodeStream" + return="android.graphics.Bitmap" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> <parameter name="is" type="java.io.InputStream"> </parameter> <parameter name="outPadding" type="android.graphics.Rect"> @@ -53129,6 +53404,16 @@ visibility="public" > </method> +<field name="inDensity" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="inDither" type="boolean" transient="false" @@ -53189,6 +53474,16 @@ visibility="public" > </field> +<field name="inScaled" + type="boolean" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="inTempStorage" type="byte[]" transient="false" @@ -54443,6 +54738,17 @@ visibility="public" > </method> +<method name="getDensityScale" + return="float" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getDrawFilter" return="android.graphics.DrawFilter" abstract="false" @@ -54789,6 +55095,19 @@ <parameter name="bitmap" type="android.graphics.Bitmap"> </parameter> </method> +<method name="setDensityScale" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="densityScale" type="float"> +</parameter> +</method> <method name="setDrawFilter" return="void" abstract="false" @@ -65953,6 +66272,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="setZoomCallback" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="cb" type="android.hardware.Camera.ZoomCallback"> +</parameter> +</method> <method name="startPreview" return="void" abstract="false" @@ -65992,6 +66324,25 @@ <parameter name="jpeg" type="android.hardware.Camera.PictureCallback"> </parameter> </method> +<method name="takePicture" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="shutter" type="android.hardware.Camera.ShutterCallback"> +</parameter> +<parameter name="raw" type="android.hardware.Camera.PictureCallback"> +</parameter> +<parameter name="postview" type="android.hardware.Camera.PictureCallback"> +</parameter> +<parameter name="jpeg" type="android.hardware.Camera.PictureCallback"> +</parameter> +</method> <field name="CAMERA_ERROR_SERVER_DIED" type="int" transient="false" @@ -66393,6 +66744,29 @@ > </field> </class> +<interface name="Camera.ZoomCallback" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onZoomUpdate" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="zoomLevel" type="int"> +</parameter> +<parameter name="camera" type="android.hardware.Camera"> +</parameter> +</method> +</interface> <class name="GeomagneticField" extends="java.lang.Object" abstract="false" @@ -115065,6 +115439,38 @@ <parameter name="listener" type="android.speech.tts.TextToSpeech.OnInitListener"> </parameter> </constructor> +<method name="addEarcon" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="earcon" type="java.lang.String"> +</parameter> +<parameter name="packagename" type="java.lang.String"> +</parameter> +<parameter name="resourceId" type="int"> +</parameter> +</method> +<method name="addEarcon" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="earcon" type="java.lang.String"> +</parameter> +<parameter name="filename" type="java.lang.String"> +</parameter> +</method> <method name="addSpeech" return="int" abstract="false" @@ -115374,6 +115780,144 @@ > </field> </class> +<class name="TextToSpeech.Engine" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="TextToSpeech.Engine" + type="android.speech.tts.TextToSpeech.Engine" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<field name="CHECK_VOICE_DATA_BAD_DATA" + type="int" + transient="false" + volatile="false" + value="-1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHECK_VOICE_DATA_FAIL" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHECK_VOICE_DATA_MISSING_DATA" + type="int" + transient="false" + volatile="false" + value="-2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHECK_VOICE_DATA_MISSING_VOLUME" + type="int" + transient="false" + volatile="false" + value="-3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="CHECK_VOICE_DATA_PASS" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="TTS_DEFAULT_STREAM" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="TTS_KEY_PARAM_STREAM" + type="java.lang.String" + transient="false" + volatile="false" + value=""streamType"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="TTS_KEY_PARAM_UTTERANCE_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""utteranceId"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="VOICE_DATA_FILES" + type="java.lang.String" + transient="false" + volatile="false" + value=""dataFiles"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="VOICE_DATA_FILES_INFO" + type="java.lang.String" + transient="false" + volatile="false" + value=""dataFilesInfo"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="VOICE_DATA_ROOT_DIRECTORY" + type="java.lang.String" + transient="false" + volatile="false" + value=""dataRoot"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <interface name="TextToSpeech.OnInitListener" abstract="true" static="true" @@ -136791,6 +137335,50 @@ visibility="public" > </method> +<field name="DENSITY_DEFAULT" + type="int" + transient="false" + volatile="false" + value="160" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DENSITY_HIGH" + type="int" + transient="false" + volatile="false" + value="240" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DENSITY_LOW" + type="int" + transient="false" + volatile="false" + value="120" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DENSITY_MEDIUM" + type="int" + transient="false" + volatile="false" + value="160" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="density" type="float" transient="false" @@ -138840,6 +139428,28 @@ visibility="public" > </field> +<field name="DENSITY_DEFAULT" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DENSITY_NONE" + type="int" + transient="false" + volatile="false" + value="65535" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="TYPE_ATTRIBUTE" type="int" transient="false" @@ -139068,6 +139678,16 @@ visibility="public" > </field> +<field name="density" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="resourceId" type="int" transient="false" @@ -175418,6 +176038,17 @@ <parameter name="yOffset" type="int"> </parameter> </method> +<method name="getSoftInputMode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getWidth" return="int" abstract="false" @@ -175623,6 +176254,19 @@ <parameter name="touchable" type="boolean"> </parameter> </method> +<method name="setSoftInputMode" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="mode" type="int"> +</parameter> +</method> <method name="setTouchInterceptor" return="void" abstract="false" diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp index 96389dd5d22c..97b43a4fe259 100644 --- a/camera/libcameraservice/CameraService.cpp +++ b/camera/libcameraservice/CameraService.cpp @@ -227,6 +227,8 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, mMediaPlayerClick = newMediaPlayer("/system/media/audio/ui/camera_click.ogg"); mMediaPlayerBeep = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg"); + mOverlayW = 0; + mOverlayH = 0; // Callback is disabled by default mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; @@ -399,6 +401,11 @@ void CameraService::Client::disconnect() mHardware->cancelPicture(true, true, true); // Release the hardware resources. mHardware->release(); + // Release the held overlay resources. + if (mUseOverlay) + { + mOverlayRef = 0; + } mHardware.clear(); mCameraService->removeClient(mCameraClient); @@ -420,11 +427,21 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) result = NO_ERROR; // asBinder() is safe on NULL (returns NULL) if (surface->asBinder() != mSurface->asBinder()) { - if (mSurface != 0 && !mUseOverlay) { + if (mSurface != 0) { LOGD("clearing old preview surface %p", mSurface.get()); - mSurface->unregisterBuffers(); + if ( !mUseOverlay) + { + mSurface->unregisterBuffers(); + } + else + { + // Force the destruction of any previous overlay + sp<Overlay> dummy; + mHardware->setOverlay( dummy ); + } } mSurface = surface; + mOverlayRef = 0; // If preview has been already started, set overlay or register preview // buffers now. if (mHardware->previewEnabled()) { @@ -520,8 +537,8 @@ status_t CameraService::Client::setOverlay() const char *format = params.getPreviewFormat(); int fmt; - if (!strcmp(format, "yuv422i")) - fmt = OVERLAY_FORMAT_YCbCr_422_I; + if (!strcmp(format, "yuv422i-yuyv")) + fmt = OVERLAY_FORMAT_YCbYCr_422_I; else if (!strcmp(format, "rgb565")) fmt = OVERLAY_FORMAT_RGB_565; else { @@ -529,16 +546,35 @@ status_t CameraService::Client::setOverlay() return -EINVAL; } + if ( w != mOverlayW || h != mOverlayH ) + { + // Force the destruction of any previous overlay + sp<Overlay> dummy; + mHardware->setOverlay( dummy ); + mOverlayRef = 0; + } + status_t ret = NO_ERROR; if (mSurface != 0) { - sp<OverlayRef> ref = mSurface->createOverlay(w, h, fmt); - ret = mHardware->setOverlay(new Overlay(ref)); + if (mOverlayRef.get() == NULL) { + mOverlayRef = mSurface->createOverlay(w, h, fmt); + if ( mOverlayRef.get() == NULL ) + { + LOGE("Overlay Creation Failed!"); + return -EINVAL; + } + ret = mHardware->setOverlay(new Overlay(mOverlayRef)); + } } else { ret = mHardware->setOverlay(NULL); } if (ret != NO_ERROR) { LOGE("mHardware->setOverlay() failed with status %d\n", ret); } + + mOverlayW = w; + mOverlayH = h; + return ret; } @@ -1092,6 +1128,7 @@ void CameraService::Client::postPreviewFrame(const sp<IMemory>& mem) ssize_t offset; size_t size; sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); + if ( !mUseOverlay ) { Mutex::Autolock surfaceLock(mSurfaceLock); if (mSurface != NULL) { diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h index ea93789d14a0..8a49fa6a7d99 100644 --- a/camera/libcameraservice/CameraService.h +++ b/camera/libcameraservice/CameraService.h @@ -189,6 +189,10 @@ private: sp<CameraHardwareInterface> mHardware; pid_t mClientPid; bool mUseOverlay; + + sp<OverlayRef> mOverlayRef; + int mOverlayW; + int mOverlayH; }; // ---------------------------------------------------------------------------- diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h index a7fd9a556af8..7665e81a314d 100644 --- a/cmds/keystore/keystore_get.h +++ b/cmds/keystore/keystore_get.h @@ -29,7 +29,7 @@ * is returned. Otherwise it returns the value in dynamically allocated memory * and sets the size if the pointer is not NULL. One can release the memory by * calling free(). */ -static char *keystore_get(char *key, int *size) +static char *keystore_get(const char *key, int *size) { char buffer[MAX_KEY_VALUE_LENGTH]; char *value; diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk new file mode 100644 index 000000000000..fd681a286b7b --- /dev/null +++ b/cmds/stagefright/Android.mk @@ -0,0 +1,66 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + stagefright.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright + +LOCAL_C_INCLUDES:= \ + frameworks/base/media/libstagefright \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE:= stagefright + +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + record.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright + +LOCAL_C_INCLUDES:= \ + frameworks/base/media/libstagefright \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE:= record + +include $(BUILD_EXECUTABLE) + +################################################################################ + +# include $(CLEAR_VARS) +# +# LOCAL_SRC_FILES:= \ +# play.cpp +# +# LOCAL_SHARED_LIBRARIES := \ +# libstagefright +# +# LOCAL_C_INCLUDES:= \ +# frameworks/base/media/libstagefright \ +# $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ +# $(TOP)/external/opencore/android +# +# LOCAL_CFLAGS += -Wno-multichar +# +# LOCAL_MODULE:= play +# +# include $(BUILD_EXECUTABLE) + +endif diff --git a/cmds/stagefright/WaveWriter.h b/cmds/stagefright/WaveWriter.h new file mode 100644 index 000000000000..a0eb66e2b9bc --- /dev/null +++ b/cmds/stagefright/WaveWriter.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_WAVEWRITER_H_ + +#define ANDROID_WAVEWRITER_H_ + +namespace android { + +class WaveWriter { +public: + WaveWriter(const char *filename, + uint16_t num_channels, uint32_t sampling_rate) + : mFile(fopen(filename, "wb")), + mTotalBytes(0) { + fwrite("RIFFxxxxWAVEfmt \x10\x00\x00\x00\x01\x00", 1, 22, mFile); + write_u16(num_channels); + write_u32(sampling_rate); + write_u32(sampling_rate * num_channels * 2); + write_u16(num_channels * 2); + write_u16(16); + fwrite("dataxxxx", 1, 8, mFile); + } + + ~WaveWriter() { + fseek(mFile, 40, SEEK_SET); + write_u32(mTotalBytes); + + fseek(mFile, 4, SEEK_SET); + write_u32(36 + mTotalBytes); + + fclose(mFile); + mFile = NULL; + } + + void Append(const void *data, size_t size) { + fwrite(data, 1, size, mFile); + mTotalBytes += size; + } + +private: + void write_u16(uint16_t x) { + fputc(x & 0xff, mFile); + fputc(x >> 8, mFile); + } + + void write_u32(uint32_t x) { + write_u16(x & 0xffff); + write_u16(x >> 16); + } + + FILE *mFile; + size_t mTotalBytes; +}; + +} // namespace android + +#endif // ANDROID_WAVEWRITER_H_ diff --git a/cmds/stagefright/play.cpp b/cmds/stagefright/play.cpp new file mode 100644 index 000000000000..c6e778e1361d --- /dev/null +++ b/cmds/stagefright/play.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <binder/ProcessState.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/TimedEventQueue.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXDecoder.h> + +using namespace android; + +struct NewPlayer { + NewPlayer(); + ~NewPlayer(); + + void setSource(const char *uri); + void start(); + void pause(); + void stop(); + +private: + struct PlayerEvent : public TimedEventQueue::Event { + PlayerEvent(NewPlayer *player, + void (NewPlayer::*method)(int64_t realtime_us)) + : mPlayer(player), + mMethod(method) { + } + + virtual void fire(TimedEventQueue *queue, int64_t realtime_us) { + (mPlayer->*mMethod)(realtime_us); + } + + private: + NewPlayer *mPlayer; + void (NewPlayer::*mMethod)(int64_t realtime_us); + + PlayerEvent(const PlayerEvent &); + PlayerEvent &operator=(const PlayerEvent &); + }; + + struct PlayVideoFrameEvent : public TimedEventQueue::Event { + PlayVideoFrameEvent(NewPlayer *player, MediaBuffer *buffer) + : mPlayer(player), + mBuffer(buffer) { + } + + virtual ~PlayVideoFrameEvent() { + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + } + + virtual void fire(TimedEventQueue *queue, int64_t realtime_us) { + mPlayer->onPlayVideoFrame(realtime_us, mBuffer); + mBuffer = NULL; + } + + private: + NewPlayer *mPlayer; + MediaBuffer *mBuffer; + + PlayVideoFrameEvent(const PlayVideoFrameEvent &); + PlayVideoFrameEvent &operator=(const PlayVideoFrameEvent &); + }; + + OMXClient mClient; + + MPEG4Extractor *mExtractor; + MediaSource *mAudioSource; + OMXDecoder *mAudioDecoder; + MediaSource *mVideoSource; + OMXDecoder *mVideoDecoder; + + int32_t mVideoWidth, mVideoHeight; + + TimedEventQueue mQueue; + wp<TimedEventQueue::Event> mPlayVideoFrameEvent; + + int64_t mMediaTimeUsStart; + int64_t mRealTimeUsStart; + + void setAudioSource(MediaSource *source); + void setVideoSource(MediaSource *source); + + int64_t approxRealTime(int64_t mediatime_us) const; + + void onStart(int64_t realtime_us); + void onPause(int64_t realtime_us); + void onFetchVideoFrame(int64_t realtime_us); + void onPlayVideoFrame(int64_t realtime_us, MediaBuffer *buffer); + + static int64_t getMediaBufferTimeUs(MediaBuffer *buffer); + + NewPlayer(const NewPlayer &); + NewPlayer &operator=(const NewPlayer &); +}; + +NewPlayer::NewPlayer() + : mExtractor(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0) { + status_t err = mClient.connect(); + assert(err == OK); +} + +NewPlayer::~NewPlayer() { + stop(); + + mClient.disconnect(); +} + +void NewPlayer::setSource(const char *uri) { + stop(); + + mExtractor = new MPEG4Extractor(new MmapSource(uri)); + + int num_tracks; + status_t err = mExtractor->countTracks(&num_tracks); + assert(err == OK); + + for (int i = 0; i < num_tracks; ++i) { + const sp<MetaData> meta = mExtractor->getTrackMetaData(i); + assert(meta != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + bool is_audio = false; + bool is_acceptable = false; + if (!strncasecmp(mime, "audio/", 6)) { + is_audio = true; + is_acceptable = (mAudioSource == NULL); + } else if (!strncasecmp(mime, "video/", 6)) { + is_acceptable = (mVideoSource == NULL); + } + + if (!is_acceptable) { + continue; + } + + MediaSource *source; + if (mExtractor->getTrack(i, &source) != OK) { + continue; + } + + if (is_audio) { + setAudioSource(source); + } else { + setVideoSource(source); + } + } +} + +void NewPlayer::setAudioSource(MediaSource *source) { + mAudioSource = source; + + sp<MetaData> meta = source->getFormat(); + + mAudioDecoder = OMXDecoder::Create(&mClient, meta); + mAudioDecoder->setSource(source); +} + +void NewPlayer::setVideoSource(MediaSource *source) { + mVideoSource = source; + + sp<MetaData> meta = source->getFormat(); + + bool success = meta->findInt32(kKeyWidth, &mVideoWidth); + assert(success); + + success = meta->findInt32(kKeyHeight, &mVideoHeight); + assert(success); + + mVideoDecoder = OMXDecoder::Create(&mClient, meta); + mVideoDecoder->setSource(source); +} + +void NewPlayer::start() { + mQueue.start(); + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onStart)); +} + +void NewPlayer::pause() { + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onPause)); +} + +void NewPlayer::stop() { + mQueue.stop(); + + delete mVideoDecoder; + mVideoDecoder = NULL; + delete mVideoSource; + mVideoSource = NULL; + mVideoWidth = mVideoHeight = 0; + + delete mAudioDecoder; + mAudioDecoder = NULL; + delete mAudioSource; + mAudioSource = NULL; + + delete mExtractor; + mExtractor = NULL; +} + +int64_t NewPlayer::approxRealTime(int64_t mediatime_us) const { + return mRealTimeUsStart + (mediatime_us - mMediaTimeUsStart); +} + +void NewPlayer::onStart(int64_t realtime_us) { + mRealTimeUsStart = TimedEventQueue::getRealTimeUs(); + + if (mVideoDecoder != NULL) { + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onFetchVideoFrame)); + } +} + +void NewPlayer::onFetchVideoFrame(int64_t realtime_us) { + MediaBuffer *buffer; + status_t err = mVideoDecoder->read(&buffer); + assert(err == OK); + + int64_t mediatime_us = getMediaBufferTimeUs(buffer); + + sp<TimedEventQueue::Event> event = new PlayVideoFrameEvent(this, buffer); + mPlayVideoFrameEvent = event; + + mQueue.postTimedEvent(event, approxRealTime(mediatime_us)); +} + +// static +int64_t NewPlayer::getMediaBufferTimeUs(MediaBuffer *buffer) { + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + return (int64_t)units * 1000000 / scale; +} + +void NewPlayer::onPlayVideoFrame(int64_t realtime_us, MediaBuffer *buffer) { + LOGI("playing video frame (mediatime: %.2f sec)\n", + getMediaBufferTimeUs(buffer) / 1E6); + fflush(stdout); + + buffer->release(); + buffer = NULL; + + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onFetchVideoFrame)); +} + +void NewPlayer::onPause(int64_t realtime_us) { +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + if (argc != 2) { + fprintf(stderr, "usage: %s filename\n", argv[0]); + return 1; + } + + NewPlayer player; + player.setSource(argv[1]); + player.start(); + sleep(10); + player.stop(); + + return 0; +} diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp new file mode 100644 index 000000000000..12bdead5c37c --- /dev/null +++ b/cmds/stagefright/record.cpp @@ -0,0 +1,181 @@ +/* + * 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <binder/ProcessState.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXDecoder.h> + +using namespace android; + +class DummySource : public MediaSource { +public: + DummySource(int width, int height) + : mSize((width * height * 3) / 2) { + mGroup.add_buffer(new MediaBuffer(mSize)); + } + + virtual ::status_t getMaxSampleSize(size_t *max_size) { + *max_size = mSize; + return ::OK; + } + + virtual ::status_t read(MediaBuffer **buffer) { + ::status_t err = mGroup.acquire_buffer(buffer); + if (err != ::OK) { + return err; + } + + char x = (char)((double)rand() / RAND_MAX * 255); + memset((*buffer)->data(), x, mSize); + (*buffer)->set_range(0, mSize); + + return ::OK; + } + +private: + MediaBufferGroup mGroup; + size_t mSize; + + DummySource(const DummySource &); + DummySource &operator=(const DummySource &); +}; + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + +#if 1 + if (argc != 2) { + fprintf(stderr, "usage: %s filename\n", argv[0]); + return 1; + } + + MPEG4Extractor extractor(new MmapSource(argv[1])); + int num_tracks; + assert(extractor.countTracks(&num_tracks) == ::OK); + + MediaSource *source = NULL; + sp<MetaData> meta; + for (int i = 0; i < num_tracks; ++i) { + meta = extractor.getTrackMetaData(i); + assert(meta.get() != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + if (strncasecmp(mime, "video/", 6)) { + continue; + } + + if (extractor.getTrack(i, &source) != ::OK) { + source = NULL; + continue; + } + break; + } + + if (source == NULL) { + fprintf(stderr, "Unable to find a suitable video track.\n"); + return 1; + } + + OMXClient client; + assert(client.connect() == android::OK); + + OMXDecoder *decoder = OMXDecoder::Create(&client, meta); + decoder->setSource(source); + + int width, height; + bool success = meta->findInt32(kKeyWidth, &width); + success = success && meta->findInt32(kKeyHeight, &height); + assert(success); + + sp<MetaData> enc_meta = new MetaData; + // enc_meta->setCString(kKeyMIMEType, "video/3gpp"); + enc_meta->setCString(kKeyMIMEType, "video/mp4v-es"); + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + + OMXDecoder *encoder = OMXDecoder::CreateEncoder(&client, enc_meta); + + encoder->setSource(decoder); + // encoder->setSource(meta, new DummySource(width, height)); + +#if 1 + MPEG4Writer writer("/sdcard/output.mp4"); + writer.addSource(enc_meta, encoder); + writer.start(); + sleep(120); + writer.stop(); +#else + encoder->start(); + + MediaBuffer *buffer; + while (encoder->read(&buffer) == ::OK) { + printf("got an output frame of size %d\n", buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + encoder->stop(); +#endif + + delete encoder; + encoder = NULL; + + delete decoder; + decoder = NULL; + + client.disconnect(); + + delete source; + source = NULL; +#endif + +#if 0 + CameraSource *source = CameraSource::Create(); + printf("source = %p\n", source); + + for (int i = 0; i < 100; ++i) { + MediaBuffer *buffer; + status_t err = source->read(&buffer); + assert(err == OK); + + printf("got a frame, data=%p, size=%d\n", + buffer->data(), buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + delete source; + source = NULL; +#endif + + return 0; +} + diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp new file mode 100644 index 000000000000..961942ad7ca2 --- /dev/null +++ b/cmds/stagefright/stagefright.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <pthread.h> +#include <stdlib.h> + +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/CachingDataSource.h> +#include <media/stagefright/ESDS.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaPlayerImpl.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXDecoder.h> + +#include "WaveWriter.h" + +using namespace android; + +//////////////////////////////////////////////////////////////////////////////// + +static bool convertToWav( + OMXClient *client, const sp<MetaData> &meta, MediaSource *source) { + printf("convertToWav\n"); + + OMXDecoder *decoder = OMXDecoder::Create(client, meta); + + int32_t sampleRate; + bool success = meta->findInt32(kKeySampleRate, &sampleRate); + assert(success); + + int32_t numChannels; + success = meta->findInt32(kKeyChannelCount, &numChannels); + assert(success); + + const char *mime; + success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + if (!strcasecmp("audio/3gpp", mime)) { + numChannels = 1; // XXX + } + + WaveWriter writer("/sdcard/Music/shoutcast.wav", numChannels, sampleRate); + + decoder->setSource(source); + for (int i = 0; i < 100; ++i) { + MediaBuffer *buffer; + + ::status_t err = decoder->read(&buffer); + if (err != ::OK) { + break; + } + + writer.Append((const char *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + delete decoder; + decoder = NULL; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +static int64_t getNowUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_usec + tv.tv_sec * 1000000; +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + if (argc > 1 && !strcmp(argv[1], "--list")) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + + assert(service.get() != NULL); + + sp<IOMX> omx = service->createOMX(); + assert(omx.get() != NULL); + + List<String8> list; + omx->list_nodes(&list); + + for (List<String8>::iterator it = list.begin(); + it != list.end(); ++it) { + printf("%s\n", (*it).string()); + } + + return 0; + } + +#if 0 + MediaPlayerImpl player(argv[1]); + player.play(); + + sleep(10000); +#else + DataSource::RegisterDefaultSniffers(); + + OMXClient client; + status_t err = client.connect(); + + MmapSource *dataSource = new MmapSource(argv[1]); + MediaExtractor *extractor = MediaExtractor::Create(dataSource); + dataSource = NULL; + + int numTracks; + err = extractor->countTracks(&numTracks); + + sp<MetaData> meta; + int i; + for (i = 0; i < numTracks; ++i) { + meta = extractor->getTrackMetaData(i); + + const char *mime; + meta->findCString(kKeyMIMEType, &mime); + + if (!strncasecmp(mime, "video/", 6)) { + break; + } + } + + OMXDecoder *decoder = OMXDecoder::Create(&client, meta); + + if (decoder != NULL) { + MediaSource *source; + err = extractor->getTrack(i, &source); + + decoder->setSource(source); + + decoder->start(); + + int64_t startTime = getNowUs(); + + int n = 0; + MediaBuffer *buffer; + while ((err = decoder->read(&buffer)) == OK) { + if ((++n % 16) == 0) { + printf("."); + fflush(stdout); + } + + buffer->release(); + buffer = NULL; + } + decoder->stop(); + printf("\n"); + + int64_t delay = getNowUs() - startTime; + printf("avg. %.2f fps\n", n * 1E6 / delay); + + delete decoder; + decoder = NULL; + + delete source; + source = NULL; + } + + delete extractor; + extractor = NULL; + + client.disconnect(); +#endif + + return 0; +} diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index ec7714d61481..ba6cc32808d4 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1068,6 +1068,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM unregisterActivityWatcher(watcher); return true; } + + case START_ACTIVITY_IN_PACKAGE_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + int uid = data.readInt(); + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + IBinder resultTo = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + boolean onlyIfNeeded = data.readInt() != 0; + int result = startActivityInPackage(uid, intent, resolvedType, + resultTo, resultWho, requestCode, onlyIfNeeded); + reply.writeNoException(); + reply.writeInt(result); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -2330,5 +2347,27 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public int startActivityInPackage(int uid, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(uid); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(resultTo); + data.writeString(resultWho); + data.writeInt(requestCode); + data.writeInt(onlyIfNeeded ? 1 : 0); + mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f2814f2acee6..76b47f1140c3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -32,7 +32,6 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; -import android.content.pm.PackageParser.Component; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 133091255cd9..444f222e5bde 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -20,6 +20,7 @@ import com.android.internal.policy.PolicyManager; import android.content.Context; import android.content.DialogInterface; +import android.content.ComponentName; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -784,8 +785,17 @@ public class Dialog implements DialogInterface, Window.Callback, * This hook is called when the user signals the desire to start a search. */ public boolean onSearchRequested() { - // not during dialogs, no. - return false; + final SearchManager searchManager = (SearchManager) mContext + .getSystemService(Context.SEARCH_SERVICE); + + // associate search with owner activity if possible (otherwise it will default to + // global search). + final ComponentName appName = mOwnerActivity == null ? null + : mOwnerActivity.getComponentName(); + final boolean globalSearch = (appName == null); + searchManager.startSearch(null, false, appName, null, globalSearch); + dismiss(); + return true; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index ee1b69b9af59..95b376cce673 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -262,6 +262,11 @@ public interface IActivityManager extends IInterface { public void unregisterActivityWatcher(IActivityWatcher watcher) throws RemoteException; + public int startActivityInPackage(int uid, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -415,4 +420,5 @@ public interface IActivityManager extends IInterface { int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; int REGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; int UNREGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93; + int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; } diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index 84a6085c4b75..bd725443e13a 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -37,4 +37,5 @@ interface ISearchManager { ISearchManagerCallback searchManagerCallback, int ident); void stopSearch(); + boolean isVisible(); } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 13eb0347e5a5..27c637652d5e 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -28,7 +28,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.graphics.drawable.Animatable; @@ -90,6 +89,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private static final String INSTANCE_KEY_STORED_APPDATA = "sData"; private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev"; private static final String INSTANCE_KEY_USER_QUERY = "uQry"; + + // The extra key used in an intent to the speech recognizer for in-app voice search. + private static final String EXTRA_CALLING_PACKAGE = "calling_package"; private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12; private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7; @@ -137,8 +139,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // A weak map of drawables we've gotten from other packages, so we don't load them // more than once. - private final WeakHashMap<String, Drawable> mOutsideDrawablesCache = - new WeakHashMap<String, Drawable>(); + private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache = + new WeakHashMap<String, Drawable.ConstantState>(); // Last known IME options value for the search edit text. private int mSearchAutoCompleteImeOptions; @@ -319,16 +321,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (!globalSearch && mSearchable == null) { globalSearch = true; mSearchable = searchManager.getSearchableInfo(componentName, globalSearch); - - // If we still get back null (i.e., there's not even a searchable info available - // for global search), then really give up. - if (mSearchable == null) { - // Unfortunately, we can't log here. it would be logspam every time the user - // clicks the "search" key on a non-search app. - return false; - } } - + + // If there's not even a searchable info available for global search, then really give up. + if (mSearchable == null) { + Log.w(LOG_TAG, "No global search provider."); + return false; + } + mLaunchComponent = componentName; mAppSearchData = appSearchData; // Using globalSearch here is just an optimization, just calling @@ -337,16 +337,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mActivityContext = mSearchable.getActivityContext(getContext()); // show the dialog. this will call onStart(). - if (!isShowing()) { - // First make sure the keyboard is showing (if needed), so that we get the right height - // for the dropdown to respect the IME. - if (getContext().getResources().getConfiguration().hardKeyboardHidden == - Configuration.HARDKEYBOARDHIDDEN_YES) { - InputMethodManager inputManager = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.showSoftInputUnchecked(0, null); - } - + if (!isShowing()) { // The Dialog uses a ContextThemeWrapper for the context; use this to change the // theme out from underneath us, between the global search theme and the in-app // search theme. They are identical except that the global search theme does not @@ -363,7 +354,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } show(); } - updateUI(); return true; @@ -499,6 +489,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ private void updateUI() { if (mSearchable != null) { + mDecor.setVisibility(View.VISIBLE); updateSearchAutoComplete(); updateSearchButton(); updateSearchAppIcon(); @@ -708,7 +699,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (DBG) Log.d(LOG_TAG, "onKeyDown(" + keyCode + "," + event + ")"); - + if (mSearchable == null) { + return false; + } + // handle back key to go back to previous searchable, etc. if (handleBackKey(keyCode, event)) { return true; @@ -744,6 +738,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (DBG_LOG_TIMING) { dbgLogTiming("onTextChanged()"); } + if (mSearchable == null) { + return; + } updateWidgetState(); if (!mSearchAutoComplete.isPerformingCompletion()) { // The user changed the query, remember it. @@ -856,11 +853,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @return A completely-configured intent ready to send to the voice search activity */ private Intent createVoiceAppSearchIntent(Intent baseIntent) { + ComponentName searchActivity = mSearchable.getSearchActivity(); + // create the necessary intent to set up a search-and-forward operation // in the voice search system. We have to keep the bundle separate, // because it becomes immutable once it enters the PendingIntent Intent queryIntent = new Intent(Intent.ACTION_SEARCH); - queryIntent.setComponent(mSearchable.getSearchActivity()); + queryIntent.setComponent(searchActivity); PendingIntent pending = PendingIntent.getActivity( getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT); @@ -900,6 +899,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); + voiceIntent.putExtra(EXTRA_CALLING_PACKAGE, + searchActivity == null ? null : searchActivity.toShortString()); // Add the values that configure forwarding the results voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending); @@ -993,7 +994,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS }; @Override - public void dismiss() { + public void hide() { if (!isShowing()) return; // We made sure the IME was displayed, so also make sure it is closed @@ -1004,10 +1005,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS imm.hideSoftInputFromWindow( getWindow().getDecorView().getWindowToken(), 0); } - - super.dismiss(); + + super.hide(); } - + /** * React to the user typing while in the suggestions list. First, check for action * keys. If not handled, try refocusing regular characters into the EditText. @@ -1041,6 +1042,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchAutoComplete.setSelection(selPoint); mSearchAutoComplete.setListSelection(0); mSearchAutoComplete.clearListSelection(); + mSearchAutoComplete.ensureImeVisible(); + return true; } @@ -1231,8 +1234,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } /** - * Launches an intent and dismisses the search dialog (unless the intent - * is one of the special intents that modifies the state of the search dialog). + * Launches an intent, including any special intent handling. Doesn't dismiss the dialog + * since that will be handled in {@link SearchDialogWrapper#performActivityResuming} */ private void launchIntent(Intent intent) { if (intent == null) { @@ -1241,8 +1244,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (handleSpecialIntent(intent)){ return; } - dismiss(); + Log.d(LOG_TAG, "launching " + intent); getContext().startActivity(intent); + + // in global search mode, SearchDialogWrapper#performActivityResuming will handle hiding + // the dialog when the next activity starts, but for in-app search, we still need to + // dismiss the dialog. + if (!mGlobalSearchMode) { + dismiss(); + } } /** @@ -1535,7 +1545,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS @Override public void performCompletion() { } - + + /** + * We override this method to be sure and show the soft keyboard if appropriate when + * the TextView has focus. + */ + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + if (hasWindowFocus) { + InputMethodManager inputManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(this, 0); + } + } + /** * We override this method so that we can allow a threshold of zero, which ACTV does not. */ @@ -1550,6 +1575,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (mSearchDialog.mSearchable == null) { + return false; + } if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { if (mSearchDialog.backToPreviousComponent()) { return true; diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index b795a5431ede..c98d966ce7bd 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1188,8 +1188,6 @@ public class SearchManager /** * Intent extra data key: This key will be used for the extra populated by the * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column. - * - * {@hide} */ public final static String EXTRA_DATA_KEY = "intent_extra_data_key"; @@ -1269,16 +1267,12 @@ public class SearchManager * result indicates the shortcut refers to a no longer valid sugggestion. * * @see #SUGGEST_COLUMN_SHORTCUT_ID - * - * @hide pending API council approval */ public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut"; /** * MIME type for shortcut validation. You'll use this in your suggestions content provider * in the getType() function. - * - * @hide pending API council approval */ public final static String SHORTCUT_MIME_TYPE = "vnd.android.cursor.item/vnd.android.search.suggest"; @@ -1389,9 +1383,7 @@ public class SearchManager * this element exists at the given row, this is the data that will be used when * forming the suggestion's intent. If not provided, the Intent's extra data field will be null. * This column allows suggestions to provide additional arbitrary data which will be included as - * an extra under the key EXTRA_DATA_KEY. - * - * @hide Pending API council approval. + * an extra under the key {@link #EXTRA_DATA_KEY}. */ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; /** @@ -1425,8 +1417,6 @@ public class SearchManager * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut. * Otherwise, the shortcut id will be used to check back for validation via * {@link #SUGGEST_URI_PATH_SHORTCUT}. - * - * @hide Pending API council approval. */ public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; @@ -1443,8 +1433,6 @@ public class SearchManager * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion * is being refreshed. - * - * @hide Pending API council approval. */ public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = "suggest_spinner_while_refreshing"; @@ -1452,8 +1440,6 @@ public class SearchManager /** * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion * should not be stored as a shortcut in global search. - * - * @hide Pending API council approval. */ public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1"; @@ -1500,8 +1486,6 @@ public class SearchManager * Intent action for starting a web search provider's settings activity. * Web search providers should handle this intent if they have provider-specific * settings to implement. - * - * @hide Pending API council approval. */ public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS = "android.search.action.WEB_SEARCH_SETTINGS"; @@ -1510,11 +1494,17 @@ public class SearchManager * Intent action broadcasted to inform that the searchables list or default have changed. * Components should handle this intent if they cache any searchable data and wish to stay * up to date on changes. - * - * @hide Pending API council approval. */ public final static String INTENT_ACTION_SEARCHABLES_CHANGED = "android.search.action.SEARCHABLES_CHANGED"; + + /** + * Intent action broadcasted to inform that the search settings have changed in some way. + * Either searchables have been enabled or disabled, or a different web search provider + * has been chosen. + */ + public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED + = "android.search.action.SETTINGS_CHANGED"; /** * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, @@ -1534,7 +1524,6 @@ public class SearchManager private int mIdent; // package private since they are used by the inner class SearchManagerCallback - /* package */ boolean mIsShowing = false; /* package */ final Handler mHandler; /* package */ OnDismissListener mDismissListener = null; /* package */ OnCancelListener mCancelListener = null; @@ -1600,12 +1589,9 @@ public class SearchManager ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { - if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing); - if (mIsShowing) return; if (mIdent == 0) throw new IllegalArgumentException( "Called from outside of an Activity context"); try { - mIsShowing = true; // activate the search manager and start it up! mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData, globalSearch, mSearchManagerCallback, mIdent); @@ -1626,15 +1612,10 @@ public class SearchManager * @see #startSearch */ public void stopSearch() { - if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing); - if (!mIsShowing) return; + if (DBG) debug("stopSearch()"); try { mService.stopSearch(); - // onDismiss will also clear this, but we do it here too since onDismiss() is - // called asynchronously. - mIsShowing = false; } catch (RemoteException ex) { - Log.e(TAG, "stopSearch() failed: " + ex); } } @@ -1648,8 +1629,13 @@ public class SearchManager * @hide */ public boolean isVisible() { - if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing); - return mIsShowing; + if (DBG) debug("isVisible()"); + try { + return mService.isVisible(); + } catch (RemoteException e) { + Log.e(TAG, "isVisible() failed: " + e); + return false; + } } /** @@ -1701,7 +1687,6 @@ public class SearchManager private final Runnable mFireOnDismiss = new Runnable() { public void run() { if (DBG) debug("mFireOnDismiss"); - mIsShowing = false; if (mDismissListener != null) { mDismissListener.onDismiss(); } @@ -1711,7 +1696,6 @@ public class SearchManager private final Runnable mFireOnCancel = new Runnable() { public void run() { if (DBG) debug("mFireOnCancel"); - // doesn't need to clear mIsShowing since onDismiss() always gets called too if (mCancelListener != null) { mCancelListener.onCancel(); } diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 58e66b634ba3..593b7b736cfa 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -23,25 +23,24 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; import android.server.search.SearchableInfo; import android.text.Html; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; +import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.ImageView; import android.widget.ResourceCursorAdapter; import android.widget.TextView; +import android.widget.Filter; import java.io.FileNotFoundException; import java.io.IOException; @@ -62,7 +61,8 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private SearchDialog mSearchDialog; private SearchableInfo mSearchable; private Context mProviderContext; - private WeakHashMap<String, Drawable> mOutsideDrawablesCache; + private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache; + private SparseArray<Drawable.ConstantState> mBackgroundsCache; private boolean mGlobalSearchMode; // Cached column indexes, updated when the cursor changes. @@ -91,8 +91,16 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private final Runnable mStartSpinnerRunnable; private final Runnable mStopSpinnerRunnable; - public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable, - WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) { + /** + * The amount of time we delay in the filter when the user presses the delete key. + * @see Filter#setDelayer(android.widget.Filter.Delayer). + */ + private static final long DELETE_KEY_POST_DELAY = 500L; + + public SuggestionsAdapter(Context context, SearchDialog searchDialog, + SearchableInfo searchable, + WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache, + boolean globalSearchMode) { super(context, com.android.internal.R.layout.search_dropdown_item_icons_2line, null, // no initial cursor @@ -106,6 +114,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mProviderContext = mSearchable.getProviderContext(mContext, activityContext); mOutsideDrawablesCache = outsideDrawablesCache; + mBackgroundsCache = new SparseArray<Drawable.ConstantState>(); mGlobalSearchMode = globalSearchMode; mStartSpinnerRunnable = new Runnable() { @@ -119,6 +128,18 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mSearchDialog.setWorking(false); } }; + + // delay 500ms when deleting + getFilter().setDelayer(new Filter.Delayer() { + + private int mPreviousLength = 0; + + public long getPostingDelay(CharSequence constraint) { + long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0; + mPreviousLength = constraint.length(); + return delay; + } + }); } /** @@ -256,7 +277,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View v = new SuggestionItemView(context, cursor); + View v = super.newView(context, cursor, parent); v.setTag(new ChildViewCache(v)); return v; } @@ -301,18 +322,13 @@ class SuggestionsAdapter extends ResourceCursorAdapter { if (mBackgroundColorCol != -1) { backgroundColor = cursor.getInt(mBackgroundColorCol); } - ((SuggestionItemView)view).setColor(backgroundColor); + Drawable background = getItemBackground(backgroundColor); + view.setBackgroundDrawable(background); final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol)); - String text1 = null; - if (mText1Col >= 0) { - text1 = cursor.getString(mText1Col); - } - String text2 = null; - if (mText2Col >= 0) { - text2 = cursor.getString(mText2Col); - } - ((SuggestionItemView)view).setTextStrings(text1, text2, isHtml, mProviderContext); + setViewText(cursor, views.mText1, mText1Col, isHtml); + setViewText(cursor, views.mText2, mText2Col, isHtml); + if (views.mIcon1 != null) { setViewDrawable(views.mIcon1, getIcon1(cursor)); } @@ -321,6 +337,65 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } } + /** + * Gets a drawable with no color when selected or pressed, and the given color when + * neither selected nor pressed. + * + * @return A drawable, or {@code null} if the given color is transparent. + */ + private Drawable getItemBackground(int backgroundColor) { + if (backgroundColor == 0) { + return null; + } else { + Drawable.ConstantState cachedBg = mBackgroundsCache.get(backgroundColor); + if (cachedBg != null) { + if (DBG) Log.d(LOG_TAG, "Background cache hit for color " + backgroundColor); + return cachedBg.newDrawable(); + } + if (DBG) Log.d(LOG_TAG, "Creating new background for color " + backgroundColor); + ColorDrawable transparent = new ColorDrawable(0); + ColorDrawable background = new ColorDrawable(backgroundColor); + StateListDrawable newBg = new StateListDrawable(); + newBg.addState(new int[]{android.R.attr.state_selected}, transparent); + newBg.addState(new int[]{android.R.attr.state_pressed}, transparent); + newBg.addState(new int[]{}, background); + mBackgroundsCache.put(backgroundColor, newBg.getConstantState()); + return newBg; + } + } + + 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); + if (isHtml && looksLikeHtml(str)) { + text = Html.fromHtml(str); + } else { + text = str; + } + } + // 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); + } + } + + private static boolean looksLikeHtml(String str) { + if (TextUtils.isEmpty(str)) return false; + for (int i = str.length() - 1; i >= 0; i--) { + char c = str.charAt(i); + if (c == '<' || c == '&') return true; + } + return false; + } + private Drawable getIcon1(Cursor cursor) { if (mIconName1Col < 0) { return null; @@ -449,12 +524,13 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } // First, check the cache. - Drawable drawable = mOutsideDrawablesCache.get(drawableId); - if (drawable != null) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(drawableId); + if (cached != null) { if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId); - return drawable; + return cached.newDrawable(); } + Drawable drawable = null; try { // Not cached, try using it as a plain resource ID in the provider's context. int resourceId = Integer.parseInt(drawableId); @@ -486,7 +562,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // If we got a drawable for this resource id, then stick it in the // map so we don't do this lookup again. if (drawable != null) { - mOutsideDrawablesCache.put(drawableId, drawable); + mOutsideDrawablesCache.put(drawableId, drawable.getConstantState()); } } catch (Resources.NotFoundException nfe) { if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId); @@ -541,12 +617,14 @@ class SuggestionsAdapter extends ResourceCursorAdapter { String componentIconKey = component.flattenToShortString(); // Using containsKey() since we also store null values. if (mOutsideDrawablesCache.containsKey(componentIconKey)) { - return mOutsideDrawablesCache.get(componentIconKey); + Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); + return cached == null ? null : cached.newDrawable(); } // Then try the activity or application icon Drawable drawable = getActivityIcon(component); // Stick it in the cache so we don't do this lookup again. - mOutsideDrawablesCache.put(componentIconKey, drawable); + Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState(); + mOutsideDrawablesCache.put(componentIconKey, toCache); return drawable; } @@ -594,179 +672,4 @@ class SuggestionsAdapter extends ResourceCursorAdapter { return cursor.getString(col); } - /** - * A parent viewgroup class which holds the actual suggestion item as a child. - * - * The sole purpose of this class is to draw the given background color when the item is in - * normal state and not draw the background color when it is pressed, so that when pressed the - * list view's selection highlight will be displayed properly (if we draw our background it - * 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 - super(context); - mBackgroundColor = 0; // transparent by default. - - // For our layout use the default list item height from the current theme. - TypedValue lineHeight = new TypedValue(); - context.getTheme().resolveAttribute( - com.android.internal.R.attr.searchResultListItemHeight, lineHeight, true); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - AbsListView.LayoutParams layout = new AbsListView.LayoutParams( - AbsListView.LayoutParams.FILL_PARENT, - (int)lineHeight.getDimension(metrics)); - - setLayoutParams(layout); - - // Initialize the child view - mView = SuggestionsAdapter.super.newView(context, cursor, this); - if (mView != null) { - addView(mView, layout.width, layout.height); - mView.setVisibility(View.VISIBLE); - } - } - - 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); - } - super.dispatchDraw(canvas); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mView != null) { - mView.measure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (mView != null) { - mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); - } - } - } - } diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java index 26712a10f0ce..f1bbedef9024 100755 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -64,11 +64,9 @@ public class AppWidgetProvider extends BroadcastReceiver { } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); - if (extras != null) { - int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); - if (appWidgetIds != null && appWidgetIds.length > 0) { - this.onDeleted(context, appWidgetIds); - } + if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) { + final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); + this.onDeleted(context, new int[] { appWidgetId }); } } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 8530c355b935..a2e0ba0a4c26 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -57,6 +57,9 @@ public class AppWidgetProviderInfo implements Parcelable { * * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in * the AppWidget meta-data file. + * + * <p class="note"><b>Note:</b> Updates requested with <code>updatePeriodMillis</code> + * will not be delivered more than once every 30 minutes.</p> */ public int updatePeriodMillis; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index c942a27e8cd1..a64c6d72d43c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -74,6 +74,14 @@ public class BluetoothDevice { /** An existing bond was explicitly revoked */ public static final int UNBOND_REASON_REMOVED = 6; + /* The user will be prompted to enter a pin */ + public static final int PAIRING_VARIANT_PIN = 0; + /* The user will be prompted to enter a passkey */ + public static final int PAIRING_VARIANT_PASSKEY = 1; + /* The user will be prompted to confirm the passkey displayed on the screen */ + public static final int PAIRING_VARIANT_CONFIRMATION = 2; + + private static final String TAG = "BluetoothDevice"; private final IBluetoothDevice mService; @@ -358,9 +366,24 @@ public class BluetoothDevice { } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } - public boolean cancelPin(String address) { + + public boolean setPasskey(String address, int passkey) { + try { + return mService.setPasskey(address, passkey); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + public boolean setPairingConfirmation(String address, boolean confirm) { + try { + return mService.setPairingConfirmation(address, confirm); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + public boolean cancelPairingUserInput(String address) { try { - return mService.cancelPin(address); + return mService.cancelPairingUserInput(address); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java index 344601b0b89a..d6c79b404909 100644 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -57,6 +57,10 @@ public interface BluetoothIntent { "android.bluetooth.intent.BOND_PREVIOUS_STATE"; public static final String REASON = "android.bluetooth.intent.REASON"; + public static final String PAIRING_VARIANT = + "android.bluetooth.intent.PAIRING_VARIANT"; + public static final String PASSKEY = + "android.bluetooth.intent.PASSKEY"; /** Broadcast when the local Bluetooth device state changes, for example * when Bluetooth is enabled. Will contain int extra's BLUETOOTH_STATE and diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 96b93f9b18ee..f8316a5bac9a 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -52,7 +52,7 @@ public final class BluetoothUuid { } public static boolean isHandsfree(UUID uuid) { - return uuid.equals(Handsfree) || uuid.equals(HandsfreeAudioGateway); + return uuid.equals(Handsfree); } public static boolean isHeadset(UUID uuid) { diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl index c249c817a464..a78752bff5e5 100644 --- a/core/java/android/bluetooth/IBluetoothDevice.aidl +++ b/core/java/android/bluetooth/IBluetoothDevice.aidl @@ -54,5 +54,8 @@ interface IBluetoothDevice int getRemoteServiceChannel(in String address, String uuid); boolean setPin(in String address, in byte[] pin); - boolean cancelPin(in String address); + boolean setPasskey(in String address, int passkey); + boolean setPairingConfirmation(in String address, boolean confirm); + boolean cancelPairingUserInput(in String address); + } diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java index 3266c07a7ff3..a3daa01e8317 100644 --- a/core/java/android/content/AbstractTableMerger.java +++ b/core/java/android/content/AbstractTableMerger.java @@ -369,30 +369,33 @@ public abstract class AbstractTableMerger // An existing server item has changed // If serverSyncVersion is null, there is no edit URL; // server won't let this change be written. - // Just hold onto it, I guess, in case the server permissions - // change later. - if (serverSyncVersion != null) { - boolean recordChanged = (localSyncVersion == null) || - !serverSyncVersion.equals(localSyncVersion); - if (recordChanged) { - if (localSyncDirty) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "remote record " + serverSyncId - + " conflicts with local _sync_id " + localSyncID - + ", local _id " + localRowId); - } - conflict = true; - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, - "remote record " + - serverSyncId + - " updates local _sync_id " + - localSyncID + ", local _id " + - localRowId); - } - update = true; + boolean recordChanged = (localSyncVersion == null) || + (serverSyncVersion == null) || + !serverSyncVersion.equals(localSyncVersion); + if (recordChanged) { + if (localSyncDirty) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "remote record " + serverSyncId + + " conflicts with local _sync_id " + localSyncID + + ", local _id " + localRowId); } + conflict = true; + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "remote record " + + serverSyncId + + " updates local _sync_id " + + localSyncID + ", local _id " + + localRowId); + } + update = true; + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "Skipping update: localSyncVersion: " + localSyncVersion + + ", serverSyncVersion: " + serverSyncVersion); } } } else { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 54d2e76a86b6..5be8100d3e24 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -75,10 +75,10 @@ import java.util.Set; * <p>Some examples of action/data pairs are:</p> * * <ul> - * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/1</i></b> -- Display + * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/people/1</i></b> -- Display * information about the person whose identifier is "1".</p> * </li> - * <li> <p><b>{@link #ACTION_DIAL} <i>content://contacts/1</i></b> -- Display + * <li> <p><b>{@link #ACTION_DIAL} <i>content://contacts/people/1</i></b> -- Display * the phone dialer with the person filled in.</p> * </li> * <li> <p><b>{@link #ACTION_VIEW} <i>tel:123</i></b> -- Display @@ -89,10 +89,10 @@ import java.util.Set; * <li> <p><b>{@link #ACTION_DIAL} <i>tel:123</i></b> -- Display * the phone dialer with the given number filled in.</p> * </li> - * <li> <p><b>{@link #ACTION_EDIT} <i>content://contacts/1</i></b> -- Edit + * <li> <p><b>{@link #ACTION_EDIT} <i>content://contacts/people/1</i></b> -- Edit * information about the person whose identifier is "1".</p> * </li> - * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/</i></b> -- Display + * <li> <p><b>{@link #ACTION_VIEW} <i>content://contacts/people/</i></b> -- Display * a list of people, which the user can browse through. This example is a * typical top-level entry into the Contacts application, showing you the * list of people. Selecting a particular person to view would result in a @@ -156,7 +156,7 @@ import java.util.Set; * defined in the Intent class, but applications can also define their own. * These strings use java style scoping, to ensure they are unique -- for * example, the standard {@link #ACTION_VIEW} is called - * "android.app.action.VIEW".</p> + * "android.intent.action.VIEW".</p> * * <p>Put together, the set of actions, data types, categories, and extra data * defines a language for the system allowing for the expression of phrases @@ -347,7 +347,7 @@ import java.util.Set; * <li> <p><b>{ action=android.app.action.MAIN, * category=android.app.category.LAUNCHER }</b> is the actual intent * used by the Launcher to populate its top-level list.</p> - * <li> <p><b>{ action=android.app.action.VIEW + * <li> <p><b>{ action=android.intent.action.VIEW * data=content://com.google.provider.NotePad/notes }</b> * displays a list of all the notes under * "content://com.google.provider.NotePad/notes", which @@ -399,7 +399,7 @@ import java.util.Set; * NoteEditor activity:</p> * * <ul> - * <li> <p><b>{ action=android.app.action.VIEW + * <li> <p><b>{ action=android.intent.action.VIEW * data=content://com.google.provider.NotePad/notes/<var>{ID}</var> }</b> * shows the user the content of note <var>{ID}</var>.</p> * <li> <p><b>{ action=android.app.action.EDIT @@ -1684,6 +1684,53 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_REBOOT = "android.intent.action.REBOOT"; + /** + * Broadcast Action: Triggers the platform Text-To-Speech engine to + * start the activity that installs the resource files on the device + * that are required for TTS to be operational. Since the installation + * of the data can be interrupted or declined by the user, the application + * shouldn't expect successful installation upon return from that intent, + * and if need be, should check installation status with + * {@link #ACTION_TTS_CHECK_TTS_DATA}. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TTS_INSTALL_TTS_DATA = + "android.intent.action.INSTALL_TTS_DATA"; + + /** + * Broadcast Action: Starts the activity from the platform Text-To-Speech + * engine to verify the proper installation and availability of the + * resource files on the system. Upon completion, the activity will + * return one of the following codes: + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_PASS}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_FAIL}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_BAD_DATA}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_MISSING_DATA}, or + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_MISSING_VOLUME}. + * <p> Moreover, the data received in the activity result will contain the following + * fields: + * <ul> + * <li>{@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_ROOT_DIRECTORY} which + * indicates the path to the location of the resource files</li>, + * <li>{@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_FILES} which contains + * the list of all the resource files</li>, + * <li>and {@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_FILES_INFO} which + * contains, for each resource file, the description of the language covered by + * the file in the xxx-YYY format, where xxx is the 3-letter ISO language code, + * and YYY is the 3-letter ISO country code.</li> + * </ul> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TTS_CHECK_TTS_DATA = + "android.intent.action.CHECK_TTS_DATA"; + + /** + * Broadcast Action: The TextToSpeech synthesizer has completed processing + * all of the text in the speech queue. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = + "android.intent.action.TTS_QUEUE_PROCESSING_COMPLETED"; /** * Broadcast Action: a remote intent is to be broadcasted. @@ -1699,16 +1746,6 @@ public class Intent implements Parcelable { public static final String ACTION_REMOTE_INTENT = "android.intent.action.REMOTE_INTENT"; - /** - * @hide - * TODO: This will be unhidden in a later CL. - * Broadcast Action: The TextToSpeech synthesizer has completed processing - * all of the text in the speech queue. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = - "android.intent.action.TTS_QUEUE_PROCESSING_COMPLETED"; - // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index f73b39438307..d54e26091d24 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -1588,7 +1588,7 @@ class SyncManager implements OnAccountsUpdatedListener { ContentResolver.SYNC_EXTRAS_MANUAL, false); final boolean syncAutomatically = mSyncStorageEngine.getSyncAutomatically(op.account, op.authority) - || mSyncStorageEngine.getMasterSyncAutomatically(); + && mSyncStorageEngine.getMasterSyncAutomatically(); boolean syncAllowed = manualSync || (backgroundDataUsageAllowed && syncAutomatically); if (!syncAllowed) { diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 9d2efb54647d..8cc06426c599 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -110,6 +110,8 @@ public class SyncStorageEngine extends Handler { private static final int MSG_WRITE_STATISTICS = 2; private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour + + private static final boolean SYNC_ENABLED_DEFAULT = false; public static class PendingOperation { final Account account; @@ -158,7 +160,7 @@ public class SyncStorageEngine extends Handler { this.account = account; this.authority = authority; this.ident = ident; - enabled = true; + enabled = SYNC_ENABLED_DEFAULT; } } @@ -376,23 +378,30 @@ public class SyncStorageEngine extends Handler { } public void setSyncAutomatically(Account account, String providerName, boolean sync) { + boolean wasEnabled; synchronized (mAuthorities) { - AuthorityInfo authority = getAuthorityLocked(account, providerName, - "setSyncAutomatically"); - if (authority != null) { - authority.enabled = sync; - } + AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); + wasEnabled = authority.enabled; + authority.enabled = sync; writeAccountInfoLocked(); } - + + if (!wasEnabled && sync) { + mContext.getContentResolver().requestSync(account, providerName, new Bundle()); + } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } public void setMasterSyncAutomatically(boolean flag) { + boolean old; synchronized (mAuthorities) { + old = mMasterSyncAutomatically; mMasterSyncAutomatically = flag; writeAccountInfoLocked(); } + if (!old && flag) { + mContext.getContentResolver().requestSync(null, null, new Bundle()); + } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 903f4820b101..cebb696867b0 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -979,12 +979,12 @@ public class PackageParser { /** * TODO: enable this before code freeze. b/1967935 * * - */ if ((densities == null || densities.length == 0) && (pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { pkg.supportsDensities = ApplicationInfo.ANY_DENSITIES_ARRAY; } + */ return pkg; } diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index ebe556e3cd65..6e34cc8a24dc 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -38,7 +38,12 @@ public class CompatibilityInfo { private static final String TAG = "CompatibilityInfo"; /** default compatibility info object for compatible applications */ - public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo(); + public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() { + @Override + public void setExpandable(boolean expandable) { + throw new UnsupportedOperationException("trying to change default compatibility info"); + } + }; /** * The default width of the screen in portrait mode. @@ -80,6 +85,11 @@ public class CompatibilityInfo { private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE; /** + * The effective screen density we have selected for this application. + */ + public final int applicationDensity; + + /** * Application's scale. */ public final float applicationScale; @@ -102,30 +112,36 @@ public class CompatibilityInfo { } float packageDensityScale = -1.0f; + int packageDensity = 0; if (appInfo.supportsDensities != null) { int minDiff = Integer.MAX_VALUE; for (int density : appInfo.supportsDensities) { - if (density == ApplicationInfo.ANY_DENSITY) { + if (density == ApplicationInfo.ANY_DENSITY) { + packageDensity = DisplayMetrics.DENSITY_DEVICE; packageDensityScale = 1.0f; break; } - int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density); + int tmpDiff = Math.abs(DisplayMetrics.DENSITY_DEVICE - density); if (tmpDiff == 0) { + packageDensity = DisplayMetrics.DENSITY_DEVICE; packageDensityScale = 1.0f; break; } // prefer higher density (appScale>1.0), unless that's only option. if (tmpDiff < minDiff && packageDensityScale < 1.0f) { - packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density; + packageDensity = density; + packageDensityScale = DisplayMetrics.DENSITY_DEVICE / (float) density; minDiff = tmpDiff; } } } if (packageDensityScale > 0.0f) { + applicationDensity = packageDensity; applicationScale = packageDensityScale; } else { + applicationDensity = DisplayMetrics.DENSITY_DEFAULT; applicationScale = - DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY; + DisplayMetrics.DENSITY_DEVICE / (float) DisplayMetrics.DENSITY_DEFAULT; } applicationInvertedScale = 1.0f / applicationScale; @@ -134,9 +150,11 @@ public class CompatibilityInfo { } } - private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) { + private CompatibilityInfo(int appFlags, int compFlags, + int dens, float scale, float invertedScale) { this.appFlags = appFlags; mCompatibilityFlags = compFlags; + applicationDensity = dens; applicationScale = scale; applicationInvertedScale = invertedScale; } @@ -146,6 +164,7 @@ public class CompatibilityInfo { | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS, EXPANDABLE | CONFIGURED_EXPANDABLE, + DisplayMetrics.DENSITY_DEVICE, 1.0f, 1.0f); } @@ -155,7 +174,7 @@ public class CompatibilityInfo { */ public CompatibilityInfo copy() { CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags, - applicationScale, applicationInvertedScale); + applicationDensity, applicationScale, applicationInvertedScale); return info; } @@ -191,7 +210,7 @@ public class CompatibilityInfo { @Override public String toString() { return "CompatibilityInfo{scale=" + applicationScale + - ", compatibility flag=" + mCompatibilityFlags + "}"; + ", supports screen=" + supportsScreen() + "}"; } /** @@ -205,8 +224,7 @@ public class CompatibilityInfo { if (DBG) Log.d(TAG, "no translation required"); return null; } - if (!isScalingRequired() || - (params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) { + if (!isScalingRequired()) { return null; } return new Translator(); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 8de938d58df2..2354519d6ad6 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -34,6 +34,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.util.LongSparseArray; +import android.view.Display; import java.io.IOException; import java.io.InputStream; @@ -86,7 +87,8 @@ public class Resources { /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); PluralRules mPluralRule; - private final CompatibilityInfo mCompatibilityInfo; + private CompatibilityInfo mCompatibilityInfo; + private Display mDefaultDisplay; private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>() { @Override @@ -1384,6 +1386,15 @@ public class Resources { } /** + * This is just for testing. + * @hide + */ + public void setCompatibilityInfo(CompatibilityInfo ci) { + mCompatibilityInfo = ci; + updateConfiguration(mConfiguration, mMetrics); + } + + /** * Return a resource identifier for the given resource name. A fully * qualified resource name is of the form "package:type/entry". The first * two components (package and type) are optional if defType and @@ -1915,6 +1926,24 @@ public class Resources { + Integer.toHexString(id)); } + /** + * Returns the display adjusted for the Resources' metrics. + * @hide + */ + public Display getDefaultDisplay(Display defaultDisplay) { + if (mDefaultDisplay == null) { + if (!mCompatibilityInfo.isScalingRequired() && mCompatibilityInfo.supportsScreen()) { + // the app supports the display. just use the default one. + mDefaultDisplay = defaultDisplay; + } else { + // display needs adjustment. + mDefaultDisplay = Display.createMetricsBasedDisplay( + defaultDisplay.getDisplayId(), mMetrics); + } + } + return mDefaultDisplay; + } + private TypedArray getCachedStyledAttributes(int len) { synchronized (mTmpValue) { TypedArray attrs = mCachedStyledAttributes; diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 091bc1700988..40d2c869d30a 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -56,7 +56,9 @@ public class Camera { private PictureCallback mRawImageCallback; private PictureCallback mJpegCallback; private PreviewCallback mPreviewCallback; + private PictureCallback mPostviewCallback; private AutoFocusCallback mAutoFocusCallback; + private ZoomCallback mZoomCallback; private ErrorCallback mErrorCallback; private boolean mOneShot; @@ -72,6 +74,8 @@ public class Camera { mRawImageCallback = null; mJpegCallback = null; mPreviewCallback = null; + mPostviewCallback = null; + mZoomCallback = null; Looper looper; if ((looper = Looper.myLooper()) != null) { @@ -245,13 +249,15 @@ public class Camera { return; case CAMERA_MSG_RAW_IMAGE: - if (mRawImageCallback != null) + if (mRawImageCallback != null) { mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera); + } return; case CAMERA_MSG_COMPRESSED_IMAGE: - if (mJpegCallback != null) + if (mJpegCallback != null) { mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera); + } return; case CAMERA_MSG_PREVIEW_FRAME: @@ -263,15 +269,29 @@ public class Camera { } return; + case CAMERA_MSG_POSTVIEW_FRAME: + if (mPostviewCallback != null) { + mPostviewCallback.onPictureTaken((byte[])msg.obj, mCamera); + } + return; + case CAMERA_MSG_FOCUS: - if (mAutoFocusCallback != null) + if (mAutoFocusCallback != null) { mAutoFocusCallback.onAutoFocus(msg.arg1 == 0 ? false : true, mCamera); + } + return; + + case CAMERA_MSG_ZOOM: + if (mZoomCallback != null) { + mZoomCallback.onZoomUpdate(msg.arg1, mCamera); + } return; case CAMERA_MSG_ERROR : Log.e(TAG, "Error " + msg.arg1); - if (mErrorCallback != null) + if (mErrorCallback != null) { mErrorCallback.onError(msg.arg1, mCamera); + } return; default: @@ -364,13 +384,63 @@ public class Camera { */ public final void takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg) { + takePicture(shutter, raw, null, jpeg); + } + private native final void native_takePicture(); + + /** + * Triggers an asynchronous image capture. The camera service + * will initiate a series of callbacks to the application as the + * image capture progresses. The shutter callback occurs after + * the image is captured. This can be used to trigger a sound + * to let the user know that image has been captured. The raw + * callback occurs when the raw image data is available. The + * postview callback occurs when a scaled, fully processed + * postview image is available (NOTE: not all hardware supports + * this). The jpeg callback occurs when the compressed image is + * available. If the application does not need a particular + * callback, a null can be passed instead of a callback method. + * + * @param shutter callback after the image is captured, may be null + * @param raw callback with raw image data, may be null + * @param postview callback with postview image data, may be null + * @param jpeg callback with jpeg image data, may be null + */ + public final void takePicture(ShutterCallback shutter, PictureCallback raw, + PictureCallback postview, PictureCallback jpeg) { mShutterCallback = shutter; mRawImageCallback = raw; + mPostviewCallback = postview; mJpegCallback = jpeg; native_takePicture(); } - private native final void native_takePicture(); + /** + * Handles the zoom callback. + */ + public interface ZoomCallback + { + /** + * Callback for zoom updates + * @param zoomLevel new zoom level in 1/1000 increments, + * e.g. a zoom of 3.2x is stored as 3200. Accuracy of the + * value is dependent on the hardware implementation. Not + * all devices will generate this callback. + * @param camera the Camera service object + */ + void onZoomUpdate(int zoomLevel, Camera camera); + }; + + /** + * Registers a callback to be invoked when the zoom + * level is updated by the camera driver. + * @param cb the callback to run + */ + public final void setZoomCallback(ZoomCallback cb) + { + mZoomCallback = cb; + } + // These match the enum in include/ui/Camera.h /** Unspecified camerar error. @see #ErrorCallback */ public static final int CAMERA_ERROR_UNKNOWN = 1; diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java index 1d0db2bc70d6..0b30e589c4a7 100644 --- a/core/java/android/net/http/ConnectionThread.java +++ b/core/java/android/net/http/ConnectionThread.java @@ -69,6 +69,7 @@ class ConnectionThread extends Thread { */ public void run() { android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_DEFAULT + android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE); // these are used to get performance data. When it is not in the timing, diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java index 3fb3d3f0e8a9..1b6568e7f118 100644 --- a/core/java/android/net/http/Request.java +++ b/core/java/android/net/http/Request.java @@ -67,9 +67,6 @@ class Request { /** Set if I'm using a proxy server */ HttpHost mProxyHost; - /** True if request is .html, .js, .css */ - boolean mHighPriority; - /** True if request has been cancelled */ volatile boolean mCancelled = false; @@ -102,17 +99,15 @@ class Request { * @param eventHandler request will make progress callbacks on * this interface * @param headers reqeust headers - * @param highPriority true for .html, css, .cs */ Request(String method, HttpHost host, HttpHost proxyHost, String path, InputStream bodyProvider, int bodyLength, EventHandler eventHandler, - Map<String, String> headers, boolean highPriority) { + Map<String, String> headers) { mEventHandler = eventHandler; mHost = host; mProxyHost = proxyHost; mPath = path; - mHighPriority = highPriority; mBodyProvider = bodyProvider; mBodyLength = bodyLength; @@ -356,7 +351,7 @@ class Request { * for debugging */ public String toString() { - return (mHighPriority ? "P*" : "") + mPath; + return mPath; } @@ -422,8 +417,7 @@ class Request { } return status >= HttpStatus.SC_OK && status != HttpStatus.SC_NO_CONTENT - && status != HttpStatus.SC_NOT_MODIFIED - && status != HttpStatus.SC_RESET_CONTENT; + && status != HttpStatus.SC_NOT_MODIFIED; } /** diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java index 6a97951fecbd..190ae7ab6378 100644 --- a/core/java/android/net/http/RequestHandle.java +++ b/core/java/android/net/http/RequestHandle.java @@ -419,6 +419,6 @@ public class RequestHandle { mRequest = mRequestQueue.queueRequest( mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler, mBodyProvider, - mBodyLength, mRequest.mHighPriority).mRequest; + mBodyLength).mRequest; } } diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java index 4d3e7c3804cb..b6f295edf834 100644 --- a/core/java/android/net/http/RequestQueue.java +++ b/core/java/android/net/http/RequestQueue.java @@ -52,47 +52,10 @@ public class RequestQueue implements RequestFeeder { private Context mContext; - private static class RequestSet { - private final LinkedList<Request> mHighPriority; - private final LinkedList<Request> mLowPriority; - - RequestSet() { - mHighPriority = new LinkedList<Request>(); - mLowPriority = new LinkedList<Request>(); - } - - void add(Request req, boolean head) { - LinkedList l = mLowPriority; - if (req.mHighPriority) { - l = mHighPriority; - } - if (head) { - l.addFirst(req); - } else { - l.add(req); - } - } - - Request removeFirst() { - if (!mHighPriority.isEmpty()) { - return mHighPriority.removeFirst(); - } else if (!mLowPriority.isEmpty()) { - return mLowPriority.removeFirst(); - } - return null; - } - - boolean isEmpty() { - return mHighPriority.isEmpty() && mLowPriority.isEmpty(); - } - }; /** * Requests, indexed by HttpHost (scheme, host, port) */ - private LinkedHashMap<HttpHost, RequestSet> mPending; - - /* Support for notifying a client when queue is empty */ - private boolean mClientWaiting = false; + private LinkedHashMap<HttpHost, LinkedList<Request>> mPending; /** true if connected */ boolean mNetworkConnected = true; @@ -382,7 +345,7 @@ public class RequestQueue implements RequestFeeder { public RequestQueue(Context context, int connectionCount) { mContext = context; - mPending = new LinkedHashMap<HttpHost, RequestSet>(32); + mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32); mActivePool = new ActivePool(connectionCount); mActivePool.startup(); @@ -472,16 +435,14 @@ public class RequestQueue implements RequestFeeder { * data. Callbacks will be made on the supplied instance. * @param bodyProvider InputStream providing HTTP body, null if none * @param bodyLength length of body, must be 0 if bodyProvider is null - * @param highPriority If true, queues before low priority - * requests if possible */ public RequestHandle queueRequest( String url, String method, Map<String, String> headers, EventHandler eventHandler, - InputStream bodyProvider, int bodyLength, boolean highPriority) { + InputStream bodyProvider, int bodyLength) { WebAddress uri = new WebAddress(url); return queueRequest(url, uri, method, headers, eventHandler, - bodyProvider, bodyLength, highPriority); + bodyProvider, bodyLength); } /** @@ -494,14 +455,11 @@ public class RequestQueue implements RequestFeeder { * data. Callbacks will be made on the supplied instance. * @param bodyProvider InputStream providing HTTP body, null if none * @param bodyLength length of body, must be 0 if bodyProvider is null - * @param highPriority If true, queues before low priority - * requests if possible */ public RequestHandle queueRequest( String url, WebAddress uri, String method, Map<String, String> headers, EventHandler eventHandler, - InputStream bodyProvider, int bodyLength, - boolean highPriority) { + InputStream bodyProvider, int bodyLength) { if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri); @@ -516,7 +474,7 @@ public class RequestQueue implements RequestFeeder { // set up request req = new Request(method, httpHost, mProxyHost, uri.mPath, bodyProvider, - bodyLength, eventHandler, headers, highPriority); + bodyLength, eventHandler, headers); queueRequest(req, false); @@ -558,24 +516,19 @@ public class RequestQueue implements RequestFeeder { HttpLog.v("dump()"); StringBuilder dump = new StringBuilder(); int count = 0; - Iterator<Map.Entry<HttpHost, RequestSet>> iter; + Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter; // mActivePool.log(dump); if (!mPending.isEmpty()) { iter = mPending.entrySet().iterator(); while (iter.hasNext()) { - Map.Entry<HttpHost, RequestSet> entry = iter.next(); + Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next(); String hostName = entry.getKey().getHostName(); StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " "); - RequestSet reqList = entry.getValue(); - ListIterator reqIter = reqList.mHighPriority.listIterator(0); - while (iter.hasNext()) { - Request request = (Request)iter.next(); - line.append(request + " "); - } - reqIter = reqList.mLowPriority.listIterator(0); + LinkedList<Request> reqList = entry.getValue(); + ListIterator reqIter = reqList.listIterator(0); while (iter.hasNext()) { Request request = (Request)iter.next(); line.append(request + " "); @@ -607,9 +560,10 @@ public class RequestQueue implements RequestFeeder { Request ret = null; if (mNetworkConnected && mPending.containsKey(host)) { - RequestSet reqList = mPending.get(host); - ret = reqList.removeFirst(); - if (reqList.isEmpty()) { + LinkedList<Request> reqList = mPending.get(host); + if (!reqList.isEmpty()) { + ret = reqList.removeFirst(); + } else { mPending.remove(host); } } @@ -640,14 +594,18 @@ public class RequestQueue implements RequestFeeder { protected synchronized void queueRequest(Request request, boolean head) { HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost; - RequestSet reqList; + LinkedList<Request> reqList; if (mPending.containsKey(host)) { reqList = mPending.get(host); } else { - reqList = new RequestSet(); + reqList = new LinkedList<Request>(); mPending.put(host, reqList); } - reqList.add(request, head); + if (head) { + reqList.addFirst(request); + } else { + reqList.add(request); + } } @@ -660,14 +618,15 @@ public class RequestQueue implements RequestFeeder { } /* helper */ - private Request removeFirst(LinkedHashMap<HttpHost, RequestSet> requestQueue) { + private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) { Request ret = null; - Iterator<Map.Entry<HttpHost, RequestSet>> iter = requestQueue.entrySet().iterator(); + Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator(); if (iter.hasNext()) { - Map.Entry<HttpHost, RequestSet> entry = iter.next(); - RequestSet reqList = entry.getValue(); - ret = reqList.removeFirst(); - if (reqList.isEmpty()) { + Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next(); + LinkedList<Request> reqList = entry.getValue(); + if (!reqList.isEmpty()) { + ret = reqList.removeFirst(); + } else { requestQueue.remove(entry.getKey()); } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 528def5c4011..e203fd590258 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -508,6 +508,19 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBatteryUptime(long curTime); /** + * @deprecated use getRadioDataUptime + */ + public long getRadioDataUptimeMs() { + return getRadioDataUptime() / 1000; + } + + /** + * Returns the time that the radio was on for data transfers. + * @return the uptime in microseconds while unplugged + */ + public abstract long getRadioDataUptime(); + + /** * Returns the current battery realtime in microseconds. * * @param curTime the amount of elapsed realtime in microseconds. @@ -1128,7 +1141,14 @@ public abstract class BatteryStats implements Parcelable { } if (!didOne) sb.append("No activity"); pw.println(sb.toString()); - + + sb.setLength(0); + sb.append(prefix); + sb.append(" Radio data uptime when unplugged: "); + sb.append(getRadioDataUptime() / 1000); + sb.append(" ms"); + pw.println(sb.toString()); + sb.setLength(0); sb.append(prefix); sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 480519386e48..980cff39801f 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -739,7 +739,7 @@ public class Process { public static final native void sendSignal(int pid, int signal); /** @hide */ - public static final native int getFreeMemory(); + public static final native long getFreeMemory(); /** @hide */ public static final native void readProcLines(String path, diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java new file mode 100644 index 000000000000..46725d31e04d --- /dev/null +++ b/core/java/android/pim/vcard/ContactStruct.java @@ -0,0 +1,1244 @@ +/* + * 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.pim.vcard; + +import android.content.AbstractSyncableContentProvider; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.net.Uri; +import android.provider.Contacts; +import android.provider.Contacts.ContactMethods; +import android.provider.Contacts.Extensions; +import android.provider.Contacts.GroupMembership; +import android.provider.Contacts.Organizations; +import android.provider.Contacts.People; +import android.provider.Contacts.Phones; +import android.provider.Contacts.Photos; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +/** + * This class bridges between data structure of Contact app and VCard data. + */ +public class ContactStruct { + private static final String LOG_TAG = "ContactStruct"; + + /** + * @hide only for testing + */ + static public class PhoneData { + public final int type; + public final String data; + public final String label; + // isPrimary is changable only when there's no appropriate one existing in + // the original VCard. + public boolean isPrimary; + public PhoneData(int type, String data, String label, boolean isPrimary) { + this.type = type; + this.data = data; + this.label = label; + this.isPrimary = isPrimary; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PhoneData) { + return false; + } + PhoneData phoneData = (PhoneData)obj; + return (type == phoneData.type && data.equals(phoneData.data) && + label.equals(phoneData.label) && isPrimary == phoneData.isPrimary); + } + + @Override + public String toString() { + return String.format("type: %d, data: %s, label: %s, isPrimary: %s", + type, data, label, isPrimary); + } + } + + /** + * @hide only for testing + */ + static public class ContactMethod { + // Contacts.KIND_EMAIL, Contacts.KIND_POSTAL + public final int kind; + // e.g. Contacts.ContactMethods.TYPE_HOME, Contacts.PhoneColumns.TYPE_HOME + // If type == Contacts.PhoneColumns.TYPE_CUSTOM, label is used. + public final int type; + public final String data; + // Used only when TYPE is TYPE_CUSTOM. + public final String label; + // isPrimary is changable only when there's no appropriate one existing in + // the original VCard. + public boolean isPrimary; + public ContactMethod(int kind, int type, String data, String label, + boolean isPrimary) { + this.kind = kind; + this.type = type; + this.data = data; + this.label = data; + this.isPrimary = isPrimary; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ContactMethod) { + return false; + } + ContactMethod contactMethod = (ContactMethod)obj; + return (kind == contactMethod.kind && type == contactMethod.type && + data.equals(contactMethod.data) && label.equals(contactMethod.label) && + isPrimary == contactMethod.isPrimary); + } + + @Override + public String toString() { + return String.format("kind: %d, type: %d, data: %s, label: %s, isPrimary: %s", + kind, type, data, label, isPrimary); + } + } + + /** + * @hide only for testing + */ + static public class OrganizationData { + public final int type; + public final String companyName; + // can be changed in some VCard format. + public String positionName; + // isPrimary is changable only when there's no appropriate one existing in + // the original VCard. + public boolean isPrimary; + public OrganizationData(int type, String companyName, String positionName, + boolean isPrimary) { + this.type = type; + this.companyName = companyName; + this.positionName = positionName; + this.isPrimary = isPrimary; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof OrganizationData) { + return false; + } + OrganizationData organization = (OrganizationData)obj; + return (type == organization.type && companyName.equals(organization.companyName) && + positionName.equals(organization.positionName) && + isPrimary == organization.isPrimary); + } + + @Override + public String toString() { + return String.format("type: %d, company: %s, position: %s, isPrimary: %s", + type, companyName, positionName, isPrimary); + } + } + + static class Property { + private String mPropertyName; + private Map<String, Collection<String>> mParameterMap = + new HashMap<String, Collection<String>>(); + private List<String> mPropertyValueList = new ArrayList<String>(); + private byte[] mPropertyBytes; + + public Property() { + clear(); + } + + public void setPropertyName(final String propertyName) { + mPropertyName = propertyName; + } + + public void addParameter(final String paramName, final String paramValue) { + Collection<String> values; + if (mParameterMap.containsKey(paramName)) { + if (paramName.equals("TYPE")) { + values = new HashSet<String>(); + } else { + values = new ArrayList<String>(); + } + mParameterMap.put(paramName, values); + } else { + values = mParameterMap.get(paramName); + } + } + + public void addToPropertyValueList(final String propertyValue) { + mPropertyValueList.add(propertyValue); + } + + public void setPropertyBytes(final byte[] propertyBytes) { + mPropertyBytes = propertyBytes; + } + + public final Collection<String> getParameters(String type) { + return mParameterMap.get(type); + } + + public final List<String> getPropertyValueList() { + return mPropertyValueList; + } + + public void clear() { + mPropertyName = null; + mParameterMap.clear(); + mPropertyValueList.clear(); + } + } + + private String mName; + private String mPhoneticName; + // private String mPhotoType; + private byte[] mPhotoBytes; + private List<String> mNotes; + private List<PhoneData> mPhoneList; + private List<ContactMethod> mContactMethodList; + private List<OrganizationData> mOrganizationList; + private Map<String, List<String>> mExtensionMap; + + private int mNameOrderType; + + /* private variables bellow is for temporary use. */ + + // For name, there are three fields in vCard: FN, N, NAME. + // We prefer FN, which is a required field in vCard 3.0 , but not in vCard 2.1. + // Next, we prefer NAME, which is defined only in vCard 3.0. + // Finally, we use N, which is a little difficult to parse. + private String mTmpFullName; + private String mTmpNameFromNProperty; + + // Some vCard has "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", and + // "X-PHONETIC-LAST-NAME" + private String mTmpXPhoneticFirstName; + private String mTmpXPhoneticMiddleName; + private String mTmpXPhoneticLastName; + + // Each Column of four properties has ISPRIMARY field + // (See android.provider.Contacts) + // If false even after the following loop, we choose the first + // entry as a "primary" entry. + private boolean mPrefIsSet_Address; + private boolean mPrefIsSet_Phone; + private boolean mPrefIsSet_Email; + private boolean mPrefIsSet_Organization; + + public ContactStruct() { + mNameOrderType = VCardConfig.NAME_ORDER_TYPE_DEFAULT; + } + + public ContactStruct(int nameOrderType) { + mNameOrderType = nameOrderType; + } + + /** + * @hide only for test + */ + public ContactStruct(String name, + String phoneticName, + byte[] photoBytes, + List<String> notes, + List<PhoneData> phoneList, + List<ContactMethod> contactMethodList, + List<OrganizationData> organizationList, + Map<String, List<String>> extensionMap) { + mName = name; + mPhoneticName = phoneticName; + mPhotoBytes = photoBytes; + mContactMethodList = contactMethodList; + mOrganizationList = organizationList; + mExtensionMap = extensionMap; + } + + /** + * @hide only for test + */ + public String getName() { + return mName; + } + + /** + * @hide only for test + */ + public String getPhoneticName() { + return mPhoneticName; + } + + /** + * @hide only for test + */ + public final byte[] getPhotoBytes() { + return mPhotoBytes; + } + + /** + * @hide only for test + */ + public final List<String> getNotes() { + return mNotes; + } + + /** + * @hide only for test + */ + public final List<PhoneData> getPhoneList() { + return mPhoneList; + } + + /** + * @hide only for test + */ + public final List<ContactMethod> getContactMethodList() { + return mContactMethodList; + } + + /** + * @hide only for test + */ + public final List<OrganizationData> getOrganizationList() { + return mOrganizationList; + } + + /** + * @hide only for test + */ + public final Map<String, List<String>> getExtensionMap() { + return mExtensionMap; + } + + /** + * Add a phone info to phoneList. + * @param data phone number + * @param type type col of content://contacts/phones + * @param label lable col of content://contacts/phones + */ + private void addPhone(int type, String data, String label, boolean isPrimary){ + if (mPhoneList == null) { + mPhoneList = new ArrayList<PhoneData>(); + } + StringBuilder builder = new StringBuilder(); + String trimed = data.trim(); + int length = trimed.length(); + for (int i = 0; i < length; i++) { + char ch = trimed.charAt(i); + if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) { + builder.append(ch); + } + } + + PhoneData phoneData = new PhoneData(type, + PhoneNumberUtils.formatNumber(builder.toString()), + label, isPrimary); + + mPhoneList.add(phoneData); + } + + /** + * Add a contactmethod info to contactmethodList. + * @param kind integer value defined in Contacts.java + * (e.g. Contacts.KIND_EMAIL) + * @param type type col of content://contacts/contact_methods + * @param data contact data + * @param label extra string used only when kind is Contacts.KIND_CUSTOM. + */ + private void addContactmethod(int kind, int type, String data, + String label, boolean isPrimary){ + if (mContactMethodList == null) { + mContactMethodList = new ArrayList<ContactMethod>(); + } + mContactMethodList.add(new ContactMethod(kind, type, data, label, isPrimary)); + } + + /** + * Add a Organization info to organizationList. + */ + private void addOrganization(int type, String companyName, String positionName, + boolean isPrimary) { + if (mOrganizationList == null) { + mOrganizationList = new ArrayList<OrganizationData>(); + } + mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary)); + } + + /** + * Set "position" value to the appropriate data. If there's more than one + * OrganizationData objects, the value is set to the last one. If there's no + * OrganizationData object, a new OrganizationData is created, whose company name is + * empty. + * + * TODO: incomplete logic. fix this: + * + * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not + * know how to handle it in general cases... + * ---- + * TITLE:Software Engineer + * ORG:Google + * ---- + */ + private void setPosition(String positionValue) { + if (mOrganizationList == null) { + mOrganizationList = new ArrayList<OrganizationData>(); + } + int size = mOrganizationList.size(); + if (size == 0) { + addOrganization(Contacts.OrganizationColumns.TYPE_OTHER, "", null, false); + size = 1; + } + OrganizationData lastData = mOrganizationList.get(size - 1); + lastData.positionName = positionValue; + } + + private void addExtension(String propName, Map<String, Collection<String>> paramMap, + List<String> propValueList) { + if (propValueList.size() == 0) { + return; + } + // Now store the string into extensionMap. + List<String> list; + if (mExtensionMap == null) { + mExtensionMap = new HashMap<String, List<String>>(); + } + if (!mExtensionMap.containsKey(propName)){ + list = new ArrayList<String>(); + mExtensionMap.put(propName, list); + } else { + list = mExtensionMap.get(propName); + } + + list.add(encodeProperty(propName, paramMap, propValueList)); + } + + private String encodeProperty(String propName, Map<String, Collection<String>> paramMap, + List<String> propValueList) { + // PropertyNode#toString() is for reading, not for parsing in the future. + // We construct appropriate String here. + StringBuilder builder = new StringBuilder(); + if (propName.length() > 0) { + builder.append("propName:["); + builder.append(propName); + builder.append("],"); + } + + if (paramMap.size() > 0) { + builder.append("paramMap:["); + int size = paramMap.size(); + int i = 0; + for (Map.Entry<String, Collection<String>> entry : paramMap.entrySet()) { + String key = entry.getKey(); + for (String value : entry.getValue()) { + // Assuming param-key does not contain NON-ASCII nor symbols. + // TODO: check it. + // + // According to vCard 3.0: + // param-name = iana-token / x-name + builder.append(key); + + // param-value may contain any value including NON-ASCIIs. + // We use the following replacing rule. + // \ -> \\ + // , -> \, + // In String#replaceAll(), "\\\\" means a single backslash. + builder.append("="); + + // TODO: fix this. + builder.append(value.replaceAll("\\\\", "\\\\\\\\").replaceAll(",", "\\\\,")); + if (i < size -1) { + builder.append(","); + } + i++; + } + } + + builder.append("],"); + } + + int size = propValueList.size(); + if (size > 0) { + builder.append("propValue:["); + List<String> list = propValueList; + for (int i = 0; i < size; i++) { + // TODO: fix this. + builder.append(list.get(i).replaceAll("\\\\", "\\\\\\\\").replaceAll(",", "\\\\,")); + if (i < size -1) { + builder.append(","); + } + } + builder.append("],"); + } + + return builder.toString(); + } + + private static String getNameFromNProperty(List<String> elems, int nameOrderType) { + // Family, Given, Middle, Prefix, Suffix. (1 - 5) + int size = elems.size(); + if (size > 1) { + StringBuilder builder = new StringBuilder(); + boolean builderIsEmpty = true; + // Prefix + if (size > 3 && elems.get(3).length() > 0) { + builder.append(elems.get(3)); + builderIsEmpty = false; + } + String first, second; + if (nameOrderType == VCardConfig.NAME_ORDER_TYPE_JAPANESE) { + first = elems.get(0); + second = elems.get(1); + } else { + first = elems.get(1); + second = elems.get(0); + } + if (first.length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(first); + builderIsEmpty = false; + } + // Middle name + if (size > 2 && elems.get(2).length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(elems.get(2)); + builderIsEmpty = false; + } + if (second.length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(second); + builderIsEmpty = false; + } + // Suffix + if (size > 4 && elems.get(4).length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(elems.get(4)); + builderIsEmpty = false; + } + return builder.toString(); + } else if (size == 1) { + return elems.get(0); + } else { + return ""; + } + } + + public void addProperty(Property property) { + String propName = property.mPropertyName; + final Map<String, Collection<String>> paramMap = property.mParameterMap; + final List<String> propValueList = property.mPropertyValueList; + byte[] propBytes = property.mPropertyBytes; + + if (propValueList.size() == 0) { + return; + } + + String propValue = listToString(propValueList); + + if (propName.equals("VERSION")) { + // vCard version. Ignore this. + } else if (propName.equals("FN")) { + mTmpFullName = propValue; + } else if (propName.equals("NAME") && mTmpFullName == null) { + // Only in vCard 3.0. Use this if FN does not exist. + // Though, note that vCard 3.0 requires FN. + mTmpFullName = propValue; + } else if (propName.equals("N")) { + mTmpNameFromNProperty = getNameFromNProperty(propValueList, mNameOrderType); + } else if (propName.equals("SORT-STRING")) { + mPhoneticName = propValue; + } else if (propName.equals("SOUND")) { + if ("X-IRMC-N".equals(paramMap.get("TYPE")) && mPhoneticName == null) { + // Some Japanese mobile phones use this field for phonetic name, + // since vCard 2.1 does not have "SORT-STRING" type. + // Also, in some cases, the field has some ';'s in it. + // We remove them. + StringBuilder builder = new StringBuilder(); + String value = propValue; + int length = value.length(); + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if (ch != ';') { + builder.append(ch); + } + } + if (builder.length() > 0) { + mPhoneticName = builder.toString(); + } + } else { + addExtension(propName, paramMap, propValueList); + } + } else if (propName.equals("ADR")) { + boolean valuesAreAllEmpty = true; + for (String value : propValueList) { + if (value.length() > 0) { + valuesAreAllEmpty = false; + break; + } + } + if (valuesAreAllEmpty) { + return; + } + + int kind = Contacts.KIND_POSTAL; + int type = -1; + String label = ""; + boolean isPrimary = false; + Collection<String> typeCollection = paramMap.get("TYPE"); + if (typeCollection != null) { + for (String typeString : typeCollection) { + if (typeString.equals("PREF") && !mPrefIsSet_Address) { + // Only first "PREF" is considered. + mPrefIsSet_Address = true; + isPrimary = true; + } else if (typeString.equalsIgnoreCase("HOME")) { + type = Contacts.ContactMethodsColumns.TYPE_HOME; + label = ""; + } else if (typeString.equalsIgnoreCase("WORK") || + typeString.equalsIgnoreCase("COMPANY")) { + // "COMPANY" seems emitted by Windows Mobile, which is not + // specifically supported by vCard 2.1. We assume this is same + // as "WORK". + type = Contacts.ContactMethodsColumns.TYPE_WORK; + label = ""; + } else if (typeString.equalsIgnoreCase("POSTAL")) { + kind = Contacts.KIND_POSTAL; + } else if (typeString.equalsIgnoreCase("PARCEL") || + typeString.equalsIgnoreCase("DOM") || + typeString.equalsIgnoreCase("INTL")) { + // We do not have a kind or type matching these. + // TODO: fix this. We may need to split entries into two. + // (e.g. entries for KIND_POSTAL and KIND_PERCEL) + } else if (typeString.toUpperCase().startsWith("X-") && + type < 0) { + type = Contacts.ContactMethodsColumns.TYPE_CUSTOM; + label = typeString.substring(2); + } else if (type < 0) { + // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters + // emit non-standard types. We do not handle their values now. + type = Contacts.ContactMethodsColumns.TYPE_CUSTOM; + label = typeString; + } + } + } + // We use "HOME" as default + if (type < 0) { + type = Contacts.ContactMethodsColumns.TYPE_HOME; + } + + // adr-value = 0*6(text-value ";") text-value + // ; PO Box, Extended Address, Street, Locality, Region, Postal + // ; Code, Country Name + String address; + int size = propValueList.size(); + if (size > 1) { + StringBuilder builder = new StringBuilder(); + boolean builderIsEmpty = true; + if (Locale.getDefault().getCountry().equals(Locale.JAPAN.getCountry())) { + // In Japan, the order is reversed. + for (int i = size - 1; i >= 0; i--) { + String addressPart = propValueList.get(i); + if (addressPart.length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(addressPart); + builderIsEmpty = false; + } + } + } else { + for (int i = 0; i < size; i++) { + String addressPart = propValueList.get(i); + if (addressPart.length() > 0) { + if (!builderIsEmpty) { + builder.append(' '); + } + builder.append(addressPart); + builderIsEmpty = false; + } + } + } + address = builder.toString().trim(); + } else { + address = propValue; + } + addContactmethod(kind, type, address, label, isPrimary); + } else if (propName.equals("ORG")) { + // vCard specification does not specify other types. + int type = Contacts.OrganizationColumns.TYPE_WORK; + boolean isPrimary = false; + + Collection<String> typeCollection = paramMap.get("TYPE"); + if (typeCollection != null) { + for (String typeString : typeCollection) { + if (typeString.equals("PREF") && !mPrefIsSet_Organization) { + // vCard specification officially does not have PREF in ORG. + // This is just for safety. + mPrefIsSet_Organization = true; + isPrimary = true; + } + // XXX: Should we cope with X- words? + } + } + + int size = propValueList.size(); + StringBuilder builder = new StringBuilder(); + for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) { + builder.append(iter.next()); + if (iter.hasNext()) { + builder.append(' '); + } + } + + addOrganization(type, builder.toString(), "", isPrimary); + } else if (propName.equals("TITLE")) { + setPosition(propValue); + } else if (propName.equals("ROLE")) { + setPosition(propValue); + } else if ((propName.equals("PHOTO") || (propName.equals("LOGO")) && mPhotoBytes == null)) { + // We prefer PHOTO to LOGO. + Collection<String> paramMapValue = paramMap.get("VALUE"); + if (paramMapValue != null && paramMapValue.contains("URL")) { + // TODO: do something. + } else { + // Assume PHOTO is stored in BASE64. In that case, + // data is already stored in propValue_bytes in binary form. + // It should be automatically done by VBuilder (VDataBuilder/VCardDatabuilder) + mPhotoBytes = propBytes; + /* + Collection<String> typeCollection = paramMap.get("TYPE"); + if (typeCollection != null) { + if (typeCollection.size() > 1) { + StringBuilder builder = new StringBuilder(); + int size = typeCollection.size(); + int i = 0; + for (String type : typeCollection) { + builder.append(type); + if (i < size - 1) { + builder.append(','); + } + i++; + } + Log.w(LOG_TAG, "There is more than TYPE: " + builder.toString()); + } + mPhotoType = typeCollection.iterator().next(); + }*/ + } + } else if (propName.equals("EMAIL")) { + int type = -1; + String label = null; + boolean isPrimary = false; + Collection<String> typeCollection = paramMap.get("TYPE"); + if (typeCollection != null) { + for (String typeString : typeCollection) { + if (typeString.equals("PREF") && !mPrefIsSet_Email) { + // Only first "PREF" is considered. + mPrefIsSet_Email = true; + isPrimary = true; + } else if (typeString.equalsIgnoreCase("HOME")) { + type = Contacts.ContactMethodsColumns.TYPE_HOME; + } else if (typeString.equalsIgnoreCase("WORK")) { + type = Contacts.ContactMethodsColumns.TYPE_WORK; + } else if (typeString.equalsIgnoreCase("CELL")) { + // We do not have Contacts.ContactMethodsColumns.TYPE_MOBILE yet. + type = Contacts.ContactMethodsColumns.TYPE_CUSTOM; + label = Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME; + } else if (typeString.toUpperCase().startsWith("X-") && + type < 0) { + type = Contacts.ContactMethodsColumns.TYPE_CUSTOM; + label = typeString.substring(2); + } else if (type < 0) { + // vCard 3.0 allows iana-token. + // We may have INTERNET (specified in vCard spec), + // SCHOOL, etc. + type = Contacts.ContactMethodsColumns.TYPE_CUSTOM; + label = typeString; + } + } + } + if (type < 0) { + type = Contacts.ContactMethodsColumns.TYPE_OTHER; + } + addContactmethod(Contacts.KIND_EMAIL, type, propValue,label, isPrimary); + } else if (propName.equals("TEL")) { + int type = -1; + String label = null; + boolean isPrimary = false; + boolean isFax = false; + Collection<String> typeCollection = paramMap.get("TYPE"); + if (typeCollection != null) { + for (String typeString : typeCollection) { + if (typeString.equals("PREF") && !mPrefIsSet_Phone) { + // Only first "PREF" is considered. + mPrefIsSet_Phone = true; + isPrimary = true; + } else if (typeString.equalsIgnoreCase("HOME")) { + type = Contacts.PhonesColumns.TYPE_HOME; + } else if (typeString.equalsIgnoreCase("WORK")) { + type = Contacts.PhonesColumns.TYPE_WORK; + } else if (typeString.equalsIgnoreCase("CELL")) { + type = Contacts.PhonesColumns.TYPE_MOBILE; + } else if (typeString.equalsIgnoreCase("PAGER")) { + type = Contacts.PhonesColumns.TYPE_PAGER; + } else if (typeString.equalsIgnoreCase("FAX")) { + isFax = true; + } else if (typeString.equalsIgnoreCase("VOICE") || + typeString.equalsIgnoreCase("MSG")) { + // Defined in vCard 3.0. Ignore these because they + // conflict with "HOME", "WORK", etc. + // XXX: do something? + } else if (typeString.toUpperCase().startsWith("X-") && + type < 0) { + type = Contacts.PhonesColumns.TYPE_CUSTOM; + label = typeString.substring(2); + } else if (type < 0){ + // We may have MODEM, CAR, ISDN, etc... + type = Contacts.PhonesColumns.TYPE_CUSTOM; + label = typeString; + } + } + } + if (type < 0) { + type = Contacts.PhonesColumns.TYPE_HOME; + } + if (isFax) { + if (type == Contacts.PhonesColumns.TYPE_HOME) { + type = Contacts.PhonesColumns.TYPE_FAX_HOME; + } else if (type == Contacts.PhonesColumns.TYPE_WORK) { + type = Contacts.PhonesColumns.TYPE_FAX_WORK; + } + } + + addPhone(type, propValue, label, isPrimary); + } else if (propName.equals("NOTE")) { + if (mNotes == null) { + mNotes = new ArrayList<String>(1); + } + mNotes.add(propValue); + } else if (propName.equals("BDAY")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("URL")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("REV")) { + // Revision of this VCard entry. I think we can ignore this. + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("UID")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("KEY")) { + // Type is X509 or PGP? I don't know how to handle this... + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("MAILER")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("TZ")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("GEO")) { + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("NICKNAME")) { + // vCard 3.0 only. + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("CLASS")) { + // vCard 3.0 only. + // e.g. CLASS:CONFIDENTIAL + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("PROFILE")) { + // VCard 3.0 only. Must be "VCARD". I think we can ignore this. + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("CATEGORIES")) { + // VCard 3.0 only. + // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("SOURCE")) { + // VCard 3.0 only. + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("PRODID")) { + // VCard 3.0 only. + // To specify the identifier for the product that created + // the vCard object. + addExtension(propName, paramMap, propValueList); + } else if (propName.equals("X-PHONETIC-FIRST-NAME")) { + mTmpXPhoneticFirstName = propValue; + } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) { + mTmpXPhoneticMiddleName = propValue; + } else if (propName.equals("X-PHONETIC-LAST-NAME")) { + mTmpXPhoneticLastName = propValue; + } else { + // Unknown X- words and IANA token. + addExtension(propName, paramMap, propValueList); + } + } + + public String displayString() { + if (mName.length() > 0) { + return mName; + } + if (mContactMethodList != null && mContactMethodList.size() > 0) { + for (ContactMethod contactMethod : mContactMethodList) { + if (contactMethod.kind == Contacts.KIND_EMAIL && contactMethod.isPrimary) { + return contactMethod.data; + } + } + } + if (mPhoneList != null && mPhoneList.size() > 0) { + for (PhoneData phoneData : mPhoneList) { + if (phoneData.isPrimary) { + return phoneData.data; + } + } + } + return ""; + } + + /** + * Consolidate several fielsds (like mName) using name candidates, + */ + public void consolidateFields() { + if (mTmpFullName != null) { + mName = mTmpFullName; + } else if(mTmpNameFromNProperty != null) { + mName = mTmpNameFromNProperty; + } else { + mName = ""; + } + + if (mPhoneticName == null && + (mTmpXPhoneticFirstName != null || mTmpXPhoneticMiddleName != null || + mTmpXPhoneticLastName != null)) { + // Note: In Europe, this order should be "LAST FIRST MIDDLE". See the comment around + // NAME_ORDER_TYPE_* for more detail. + String first; + String second; + if (mNameOrderType == VCardConfig.NAME_ORDER_TYPE_JAPANESE) { + first = mTmpXPhoneticLastName; + second = mTmpXPhoneticFirstName; + } else { + first = mTmpXPhoneticFirstName; + second = mTmpXPhoneticLastName; + } + StringBuilder builder = new StringBuilder(); + if (first != null) { + builder.append(first); + } + if (mTmpXPhoneticMiddleName != null) { + builder.append(mTmpXPhoneticMiddleName); + } + if (second != null) { + builder.append(second); + } + mPhoneticName = builder.toString(); + } + + // Remove unnecessary white spaces. + // It is found that some mobile phone emits phonetic name with just one white space + // when a user does not specify one. + // This logic is effective toward such kind of weird data. + if (mPhoneticName != null) { + mPhoneticName = mPhoneticName.trim(); + } + + // If there is no "PREF", we choose the first entries as primary. + if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) { + mPhoneList.get(0).isPrimary = true; + } + + if (!mPrefIsSet_Address && mContactMethodList != null) { + for (ContactMethod contactMethod : mContactMethodList) { + if (contactMethod.kind == Contacts.KIND_POSTAL) { + contactMethod.isPrimary = true; + break; + } + } + } + if (!mPrefIsSet_Email && mContactMethodList != null) { + for (ContactMethod contactMethod : mContactMethodList) { + if (contactMethod.kind == Contacts.KIND_EMAIL) { + contactMethod.isPrimary = true; + break; + } + } + } + if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) { + mOrganizationList.get(0).isPrimary = true; + } + + } + + private void pushIntoContentProviderOrResolver(Object contentSomething, + long myContactsGroupId) { + ContentResolver resolver = null; + AbstractSyncableContentProvider provider = null; + if (contentSomething instanceof ContentResolver) { + resolver = (ContentResolver)contentSomething; + } else if (contentSomething instanceof AbstractSyncableContentProvider) { + provider = (AbstractSyncableContentProvider)contentSomething; + } else { + Log.e(LOG_TAG, "Unsupported object came."); + return; + } + + ContentValues contentValues = new ContentValues(); + contentValues.put(People.NAME, mName); + contentValues.put(People.PHONETIC_NAME, mPhoneticName); + + if (mNotes != null && mNotes.size() > 0) { + if (mNotes.size() > 1) { + StringBuilder builder = new StringBuilder(); + for (String note : mNotes) { + builder.append(note); + builder.append("\n"); + } + contentValues.put(People.NOTES, builder.toString()); + } else { + contentValues.put(People.NOTES, mNotes.get(0)); + } + } + + Uri personUri; + long personId = 0; + if (resolver != null) { + personUri = Contacts.People.createPersonInMyContactsGroup(resolver, contentValues); + if (personUri != null) { + personId = ContentUris.parseId(personUri); + } + } else { + personUri = provider.insert(People.CONTENT_URI, contentValues); + if (personUri != null) { + personId = ContentUris.parseId(personUri); + ContentValues values = new ContentValues(); + values.put(GroupMembership.PERSON_ID, personId); + values.put(GroupMembership.GROUP_ID, myContactsGroupId); + Uri resultUri = provider.insert(GroupMembership.CONTENT_URI, values); + if (resultUri == null) { + Log.e(LOG_TAG, "Faild to insert the person to MyContact."); + provider.delete(personUri, null, null); + personUri = null; + } + } + } + + if (personUri == null) { + Log.e(LOG_TAG, "Failed to create the contact."); + return; + } + + if (mPhotoBytes != null) { + if (resolver != null) { + People.setPhotoData(resolver, personUri, mPhotoBytes); + } else { + Uri photoUri = Uri.withAppendedPath(personUri, Contacts.Photos.CONTENT_DIRECTORY); + ContentValues values = new ContentValues(); + values.put(Photos.DATA, mPhotoBytes); + provider.update(photoUri, values, null, null); + } + } + + long primaryPhoneId = -1; + if (mPhoneList != null && mPhoneList.size() > 0) { + for (PhoneData phoneData : mPhoneList) { + ContentValues values = new ContentValues(); + values.put(Contacts.PhonesColumns.TYPE, phoneData.type); + if (phoneData.type == Contacts.PhonesColumns.TYPE_CUSTOM) { + values.put(Contacts.PhonesColumns.LABEL, phoneData.label); + } + // Already formatted. + values.put(Contacts.PhonesColumns.NUMBER, phoneData.data); + + // Not sure about Contacts.PhonesColumns.NUMBER_KEY ... + values.put(Contacts.PhonesColumns.ISPRIMARY, 1); + values.put(Contacts.Phones.PERSON_ID, personId); + Uri phoneUri; + if (resolver != null) { + phoneUri = resolver.insert(Phones.CONTENT_URI, values); + } else { + phoneUri = provider.insert(Phones.CONTENT_URI, values); + } + if (phoneData.isPrimary) { + primaryPhoneId = Long.parseLong(phoneUri.getLastPathSegment()); + } + } + } + + long primaryOrganizationId = -1; + if (mOrganizationList != null && mOrganizationList.size() > 0) { + for (OrganizationData organizationData : mOrganizationList) { + ContentValues values = new ContentValues(); + // Currently, we do not use TYPE_CUSTOM. + values.put(Contacts.OrganizationColumns.TYPE, + organizationData.type); + values.put(Contacts.OrganizationColumns.COMPANY, + organizationData.companyName); + values.put(Contacts.OrganizationColumns.TITLE, + organizationData.positionName); + values.put(Contacts.OrganizationColumns.ISPRIMARY, 1); + values.put(Contacts.OrganizationColumns.PERSON_ID, personId); + + Uri organizationUri; + if (resolver != null) { + organizationUri = resolver.insert(Organizations.CONTENT_URI, values); + } else { + organizationUri = provider.insert(Organizations.CONTENT_URI, values); + } + if (organizationData.isPrimary) { + primaryOrganizationId = Long.parseLong(organizationUri.getLastPathSegment()); + } + } + } + + long primaryEmailId = -1; + if (mContactMethodList != null && mContactMethodList.size() > 0) { + for (ContactMethod contactMethod : mContactMethodList) { + ContentValues values = new ContentValues(); + values.put(Contacts.ContactMethodsColumns.KIND, contactMethod.kind); + values.put(Contacts.ContactMethodsColumns.TYPE, contactMethod.type); + if (contactMethod.type == Contacts.ContactMethodsColumns.TYPE_CUSTOM) { + values.put(Contacts.ContactMethodsColumns.LABEL, contactMethod.label); + } + values.put(Contacts.ContactMethodsColumns.DATA, contactMethod.data); + values.put(Contacts.ContactMethodsColumns.ISPRIMARY, 1); + values.put(Contacts.ContactMethods.PERSON_ID, personId); + + if (contactMethod.kind == Contacts.KIND_EMAIL) { + Uri emailUri; + if (resolver != null) { + emailUri = resolver.insert(ContactMethods.CONTENT_URI, values); + } else { + emailUri = provider.insert(ContactMethods.CONTENT_URI, values); + } + if (contactMethod.isPrimary) { + primaryEmailId = Long.parseLong(emailUri.getLastPathSegment()); + } + } else { // probably KIND_POSTAL + if (resolver != null) { + resolver.insert(ContactMethods.CONTENT_URI, values); + } else { + provider.insert(ContactMethods.CONTENT_URI, values); + } + } + } + } + + if (mExtensionMap != null && mExtensionMap.size() > 0) { + ArrayList<ContentValues> contentValuesArray; + if (resolver != null) { + contentValuesArray = new ArrayList<ContentValues>(); + } else { + contentValuesArray = null; + } + for (Entry<String, List<String>> entry : mExtensionMap.entrySet()) { + String key = entry.getKey(); + List<String> list = entry.getValue(); + for (String value : list) { + ContentValues values = new ContentValues(); + values.put(Extensions.NAME, key); + values.put(Extensions.VALUE, value); + values.put(Extensions.PERSON_ID, personId); + if (resolver != null) { + contentValuesArray.add(values); + } else { + provider.insert(Extensions.CONTENT_URI, values); + } + } + } + if (resolver != null) { + resolver.bulkInsert(Extensions.CONTENT_URI, + contentValuesArray.toArray(new ContentValues[0])); + } + } + + if (primaryPhoneId >= 0 || primaryOrganizationId >= 0 || primaryEmailId >= 0) { + ContentValues values = new ContentValues(); + if (primaryPhoneId >= 0) { + values.put(People.PRIMARY_PHONE_ID, primaryPhoneId); + } + if (primaryOrganizationId >= 0) { + values.put(People.PRIMARY_ORGANIZATION_ID, primaryOrganizationId); + } + if (primaryEmailId >= 0) { + values.put(People.PRIMARY_EMAIL_ID, primaryEmailId); + } + if (resolver != null) { + resolver.update(personUri, values, null, null); + } else { + provider.update(personUri, values, null, null); + } + } + } + + /** + * Push this object into database in the resolver. + */ + public void pushIntoContentResolver(ContentResolver resolver) { + pushIntoContentProviderOrResolver(resolver, 0); + } + + /** + * Push this object into AbstractSyncableContentProvider object. + * {@link #consolidateFields() must be called before this method is called} + * @hide + */ + public void pushIntoAbstractSyncableContentProvider( + AbstractSyncableContentProvider provider, long myContactsGroupId) { + boolean successful = false; + provider.beginBatch(); + try { + pushIntoContentProviderOrResolver(provider, myContactsGroupId); + successful = true; + } finally { + provider.endBatch(successful); + } + } + + public boolean isIgnorable() { + return TextUtils.isEmpty(mName) && + TextUtils.isEmpty(mPhoneticName) && + (mPhoneList == null || mPhoneList.size() == 0) && + (mContactMethodList == null || mContactMethodList.size() == 0); + } + + private String listToString(List<String> list){ + final int size = list.size(); + if (size > 1) { + StringBuilder builder = new StringBuilder(); + int i = 0; + for (String type : list) { + builder.append(type); + if (i < size - 1) { + builder.append(";"); + } + } + return builder.toString(); + } else if (size == 1) { + return list.get(0); + } else { + return ""; + } + } +} diff --git a/core/java/android/pim/vcard/EntryCommitter.java b/core/java/android/pim/vcard/EntryCommitter.java new file mode 100644 index 000000000000..e26fac5dbd70 --- /dev/null +++ b/core/java/android/pim/vcard/EntryCommitter.java @@ -0,0 +1,88 @@ +/* + * 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.pim.vcard; + +import android.content.AbstractSyncableContentProvider; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.IContentProvider; +import android.provider.Contacts; +import android.util.Log; + +/** + * EntryHandler implementation which commits the entry to Contacts Provider + */ +public class EntryCommitter implements EntryHandler { + public static String LOG_TAG = "vcard.EntryComitter"; + + private ContentResolver mContentResolver; + + // Ideally, this should be ContactsProvider but it seems Class loader cannot find it, + // even when it is subclass of ContactsProvider... + private AbstractSyncableContentProvider mProvider; + private long mMyContactsGroupId; + + private long mTimeToCommit; + + public EntryCommitter(ContentResolver resolver) { + mContentResolver = resolver; + + tryGetOriginalProvider(); + } + + public void onFinal() { + if (VCardConfig.showPerformanceLog()) { + Log.d(LOG_TAG, + String.format("time to commit entries: %ld ms", mTimeToCommit)); + } + } + + private void tryGetOriginalProvider() { + final ContentResolver resolver = mContentResolver; + + if ((mMyContactsGroupId = Contacts.People.tryGetMyContactsGroupId(resolver)) == 0) { + Log.e(LOG_TAG, "Could not get group id of MyContact"); + return; + } + + IContentProvider iProviderForName = resolver.acquireProvider(Contacts.CONTENT_URI); + ContentProvider contentProvider = + ContentProvider.coerceToLocalContentProvider(iProviderForName); + if (contentProvider == null) { + Log.e(LOG_TAG, "Fail to get ContentProvider object."); + return; + } + + if (!(contentProvider instanceof AbstractSyncableContentProvider)) { + Log.e(LOG_TAG, + "Acquired ContentProvider object is not AbstractSyncableContentProvider."); + return; + } + + mProvider = (AbstractSyncableContentProvider)contentProvider; + } + + public void onEntryCreated(final ContactStruct contactStruct) { + long start = System.currentTimeMillis(); + if (mProvider != null) { + contactStruct.pushIntoAbstractSyncableContentProvider( + mProvider, mMyContactsGroupId); + } else { + contactStruct.pushIntoContentResolver(mContentResolver); + } + mTimeToCommit += System.currentTimeMillis() - start; + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/EntryHandler.java b/core/java/android/pim/vcard/EntryHandler.java new file mode 100644 index 000000000000..4015cb5b98b0 --- /dev/null +++ b/core/java/android/pim/vcard/EntryHandler.java @@ -0,0 +1,33 @@ +/* + * 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.pim.vcard; + +/** + * Unlike VCardBuilderBase, this (and VCardDataBuilder) assumes + * "each VCard entry should be correctly parsed and passed to each EntryHandler object", + */ +public interface EntryHandler { + /** + * Able to be use this method for showing performance log, etc. + * TODO: better name? + */ + public void onFinal(); + + /** + * The method called when one VCard entry is successfully created + */ + public void onEntryCreated(final ContactStruct entry); +} diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java new file mode 100644 index 000000000000..e1c4b338e501 --- /dev/null +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -0,0 +1,64 @@ +/* + * 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.pim.vcard; + +import java.util.List; + +public interface VCardBuilder { + void start(); + + void end(); + + /** + * BEGIN:VCARD + */ + void startRecord(String type); + + /** END:VXX */ + void endRecord(); + + void startProperty(); + + void endProperty(); + + /** + * @param group + */ + void propertyGroup(String group); + + /** + * @param name + * N <br> + * N + */ + void propertyName(String name); + + /** + * @param type + * LANGUAGE \ ENCODING <br> + * ;LANGUage= \ ;ENCODING= + */ + void propertyParamType(String type); + + /** + * @param value + * FR-EN \ GBK <br> + * FR-EN \ GBK + */ + void propertyParamValue(String value); + + void propertyValues(List<String> values); +} diff --git a/core/java/android/pim/vcard/VCardBuilderCollection.java b/core/java/android/pim/vcard/VCardBuilderCollection.java new file mode 100644 index 000000000000..e3985b600e69 --- /dev/null +++ b/core/java/android/pim/vcard/VCardBuilderCollection.java @@ -0,0 +1,99 @@ +/* + * 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.pim.vcard; + +import java.util.Collection; +import java.util.List; + +public class VCardBuilderCollection implements VCardBuilder { + + private final Collection<VCardBuilder> mVCardBuilderCollection; + + public VCardBuilderCollection(Collection<VCardBuilder> vBuilderCollection) { + mVCardBuilderCollection = vBuilderCollection; + } + + public Collection<VCardBuilder> getVCardBuilderBaseCollection() { + return mVCardBuilderCollection; + } + + public void start() { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.start(); + } + } + + public void end() { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.end(); + } + } + + public void startRecord(String type) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.startRecord(type); + } + } + + public void endRecord() { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.endRecord(); + } + } + + public void startProperty() { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.startProperty(); + } + } + + + public void endProperty() { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.endProperty(); + } + } + + public void propertyGroup(String group) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.propertyGroup(group); + } + } + + public void propertyName(String name) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.propertyName(name); + } + } + + public void propertyParamType(String type) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.propertyParamType(type); + } + } + + public void propertyParamValue(String value) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.propertyParamValue(value); + } + } + + public void propertyValues(List<String> values) { + for (VCardBuilder builder : mVCardBuilderCollection) { + builder.propertyValues(values); + } + } +} diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java new file mode 100644 index 000000000000..fef9dba4cc32 --- /dev/null +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -0,0 +1,59 @@ +/* + * 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.pim.vcard; + +/** + * The class representing VCard related configurations + */ +public class VCardConfig { + static final int LOG_LEVEL_NONE = 0; + static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1; + static final int LOG_LEVEL_SHOW_WARNING = 0x2; + static final int LOG_LEVEL_VERBOSE = + LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING; + + // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and + // decode the unicode to the original charset. If not, this setting will cause some bug. + public static final String DEFAULT_CHARSET = "iso-8859-1"; + + // TODO: use this flag + public static boolean IGNORE_CASE_EXCEPT_VALUE = true; + + protected static final int LOG_LEVEL = LOG_LEVEL_PERFORMANCE_MEASUREMENT; + + // Note: phonetic name probably should be "LAST FIRST MIDDLE" for European languages, and + // space should be added between each element while it should not be in Japanese. + // But unfortunately, we currently do not have the data and are not sure whether we should + // support European version of name ordering. + // + // TODO: Implement the logic described above if we really need European version of + // phonetic name handling. Also, adding the appropriate test case of vCard would be + // highly appreciated. + public static final int NAME_ORDER_TYPE_ENGLISH = 0; + public static final int NAME_ORDER_TYPE_JAPANESE = 1; + + public static final int NAME_ORDER_TYPE_DEFAULT = NAME_ORDER_TYPE_ENGLISH; + + /** + * @hide temporal. may be deleted + */ + public static boolean showPerformanceLog() { + return (LOG_LEVEL & LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0; + } + + private VCardConfig() { + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java new file mode 100644 index 000000000000..4025f6c164ca --- /dev/null +++ b/core/java/android/pim/vcard/VCardDataBuilder.java @@ -0,0 +1,319 @@ +/* + * 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.pim.vcard; + +import android.util.CharsetUtils; +import android.util.Log; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.net.QuotedPrintableCodec; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * VBuilder for VCard. VCard may contain big photo images encoded by BASE64, + * If we store all VNode entries in memory like VDataBuilder.java, + * OutOfMemoryError may be thrown. Thus, this class push each VCard entry into + * ContentResolver immediately. + */ +public class VCardDataBuilder implements VCardBuilder { + static private String LOG_TAG = "VCardDataBuilder"; + + /** + * If there's no other information available, this class uses this charset for encoding + * byte arrays. + */ + static public String TARGET_CHARSET = "UTF-8"; + + private ContactStruct.Property mCurrentProperty = new ContactStruct.Property(); + private ContactStruct mCurrentContactStruct; + private String mParamType; + + /** + * The charset using which VParser parses the text. + */ + private String mSourceCharset; + + /** + * The charset with which byte array is encoded to String. + */ + private String mTargetCharset; + private boolean mStrictLineBreakParsing; + + private int mNameOrderType; + + // Just for testing. + private long mTimePushIntoContentResolver; + + private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>(); + + public VCardDataBuilder() { + this(null, null, false, VCardConfig.NAME_ORDER_TYPE_DEFAULT); + } + + /** + * @hide + */ + public VCardDataBuilder(int nameOrderType) { + this(null, null, false, nameOrderType); + } + + /** + * @hide + */ + public VCardDataBuilder(String charset, + boolean strictLineBreakParsing, + int nameOrderType) { + this(null, charset, strictLineBreakParsing, nameOrderType); + } + + /** + * @hide + */ + public VCardDataBuilder(String sourceCharset, + String targetCharset, + boolean strictLineBreakParsing, + int nameOrderType) { + if (sourceCharset != null) { + mSourceCharset = sourceCharset; + } else { + mSourceCharset = VCardConfig.DEFAULT_CHARSET; + } + if (targetCharset != null) { + mTargetCharset = targetCharset; + } else { + mTargetCharset = TARGET_CHARSET; + } + mStrictLineBreakParsing = strictLineBreakParsing; + mNameOrderType = nameOrderType; + } + + public void addEntryHandler(EntryHandler entryHandler) { + mEntryHandlers.add(entryHandler); + } + + public void start() { + } + + public void end() { + for (EntryHandler entryHandler : mEntryHandlers) { + entryHandler.onFinal(); + } + } + + /** + * Assume that VCard is not nested. In other words, this code does not accept + */ + public void startRecord(String type) { + // TODO: add the method clear() instead of using null for reducing GC? + if (mCurrentContactStruct != null) { + // This means startRecord() is called inside startRecord() - endRecord() block. + // TODO: should throw some Exception + Log.e(LOG_TAG, "Nested VCard code is not supported now."); + } + if (!type.equalsIgnoreCase("VCARD")) { + // TODO: add test case for this + Log.e(LOG_TAG, "This is not VCARD!"); + } + + mCurrentContactStruct = new ContactStruct(mNameOrderType); + } + + public void endRecord() { + mCurrentContactStruct.consolidateFields(); + for (EntryHandler entryHandler : mEntryHandlers) { + entryHandler.onEntryCreated(mCurrentContactStruct); + } + mCurrentContactStruct = null; + } + + public void startProperty() { + mCurrentProperty.clear(); + } + + public void endProperty() { + mCurrentContactStruct.addProperty(mCurrentProperty); + } + + public void propertyName(String name) { + mCurrentProperty.setPropertyName(name); + } + + public void propertyGroup(String group) { + // ContactStruct does not support Group. + } + + public void propertyParamType(String type) { + if (mParamType != null) { + Log.e(LOG_TAG, + "propertyParamType() is called more than once " + + "before propertyParamValue() is called"); + } + mParamType = type; + } + + public void propertyParamValue(String value) { + if (mParamType == null) { + mParamType = "TYPE"; + } + mCurrentProperty.addParameter(mParamType, value); + mParamType = null; + } + + private String encodeString(String originalString, String targetCharset) { + if (mSourceCharset.equalsIgnoreCase(targetCharset)) { + return originalString; + } + Charset charset = Charset.forName(mSourceCharset); + ByteBuffer byteBuffer = charset.encode(originalString); + // byteBuffer.array() "may" return byte array which is larger than + // byteBuffer.remaining(). Here, we keep on the safe side. + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return null; + } + } + + private String handleOneValue(String value, String targetCharset, String encoding) { + if (encoding != null) { + if (encoding.equals("BASE64") || encoding.equals("B")) { + mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes())); + return value; + } else if (encoding.equals("QUOTED-PRINTABLE")) { + // "= " -> " ", "=\t" -> "\t". + // Previous code had done this replacement. Keep on the safe side. + StringBuilder builder = new StringBuilder(); + int length = value.length(); + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if (ch == '=' && i < length - 1) { + char nextCh = value.charAt(i + 1); + if (nextCh == ' ' || nextCh == '\t') { + + builder.append(nextCh); + i++; + continue; + } + } + builder.append(ch); + } + String quotedPrintable = builder.toString(); + + String[] lines; + if (mStrictLineBreakParsing) { + lines = quotedPrintable.split("\r\n"); + } else { + builder = new StringBuilder(); + length = quotedPrintable.length(); + ArrayList<String> list = new ArrayList<String>(); + for (int i = 0; i < length; i++) { + char ch = quotedPrintable.charAt(i); + if (ch == '\n') { + list.add(builder.toString()); + builder = new StringBuilder(); + } else if (ch == '\r') { + list.add(builder.toString()); + builder = new StringBuilder(); + if (i < length - 1) { + char nextCh = quotedPrintable.charAt(i + 1); + if (nextCh == '\n') { + i++; + } + } + } else { + builder.append(ch); + } + } + String finalLine = builder.toString(); + if (finalLine.length() > 0) { + list.add(finalLine); + } + lines = list.toArray(new String[0]); + } + + builder = new StringBuilder(); + for (String line : lines) { + if (line.endsWith("=")) { + line = line.substring(0, line.length() - 1); + } + builder.append(line); + } + byte[] bytes; + try { + bytes = builder.toString().getBytes(mSourceCharset); + } catch (UnsupportedEncodingException e1) { + Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset); + bytes = builder.toString().getBytes(); + } + + try { + bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes); + } catch (DecoderException e) { + Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e); + return ""; + } + + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return new String(bytes); + } + } + // Unknown encoding. Fall back to default. + } + return encodeString(value, targetCharset); + } + + public void propertyValues(List<String> values) { + if (values == null || values.size() == 0) { + return; + } + + final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET"); + String charset = + ((charsetCollection != null) ? charsetCollection.iterator().next() : null); + String targetCharset = CharsetUtils.nameForDefaultVendor(charset); + + final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING"); + String encoding = + ((encodingCollection != null) ? encodingCollection.iterator().next() : null); + + if (targetCharset == null || targetCharset.length() == 0) { + targetCharset = mTargetCharset; + } + + for (String value : values) { + mCurrentProperty.addToPropertyValueList( + handleOneValue(value, targetCharset, encoding)); + } + } + + public void showPerformanceInfo() { + Log.d(LOG_TAG, "time for insert ContactStruct to database: " + + mTimePushIntoContentResolver + " ms"); + } +} diff --git a/core/java/android/pim/vcard/VCardEntryCounter.java b/core/java/android/pim/vcard/VCardEntryCounter.java new file mode 100644 index 000000000000..f99b46c83ede --- /dev/null +++ b/core/java/android/pim/vcard/VCardEntryCounter.java @@ -0,0 +1,60 @@ +/* + * 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.pim.vcard; + +import java.util.List; + +public class VCardEntryCounter implements VCardBuilder { + private int mCount; + + public int getCount() { + return mCount; + } + + public void start() { + } + + public void end() { + } + + public void startRecord(String type) { + } + + public void endRecord() { + mCount++; + } + + public void startProperty() { + } + + public void endProperty() { + } + + public void propertyGroup(String group) { + } + + public void propertyName(String name) { + } + + public void propertyParamType(String type) { + } + + public void propertyParamValue(String value) { + } + + public void propertyValues(List<String> values) { + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java new file mode 100644 index 000000000000..b5e504982b36 --- /dev/null +++ b/core/java/android/pim/vcard/VCardParser.java @@ -0,0 +1,90 @@ +/* + * 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.pim.vcard; + +import android.pim.vcard.exception.VCardException; + +import java.io.IOException; +import java.io.InputStream; + +public abstract class VCardParser { + + protected boolean mCanceled; + + /** + * Parses the given stream and send the VCard data into VCardBuilderBase object. + * + * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets + * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is + * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1, + * In some exreme case, some VCard may have different charsets in one VCard (though + * we do not see any device which emits such kind of malicious data) + * + * In order to avoid "misunderstanding" charset as much as possible, this method + * use "ISO-8859-1" for reading the stream. When charset is specified in some property + * (with "CHARSET=..." attribute), the string is decoded to raw bytes and encoded to + * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit + * characters, which is not completely sure. In some cases, this "decoding-encoding" + * scheme may fail. To avoid the case, + * + * We recommend you to use VCardSourceDetector and detect which kind of source the + * VCard comes from and explicitly specify a charset using the result. + * + * @param is The source to parse. + * @param builder The VCardBuilderBase object which used to construct data. If you want to + * include multiple VCardBuilderBase objects in this field, consider using + * {#link VCardBuilderCollection} class. + * @return Returns true for success. Otherwise returns false. + * @throws IOException, VCardException + */ + public abstract boolean parse(InputStream is, VCardBuilder builder) + throws IOException, VCardException; + + /** + * The method variants which accept charset. + * + * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use + * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese + * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses + * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification + * (e.g. W53K). + * + * @param is The source to parse. + * @param charset Charset to be used. + * @param builder The VCardBuilderBase object. + * @return Returns true when successful. Otherwise returns false. + * @throws IOException, VCardException + */ + public abstract boolean parse(InputStream is, String charset, VCardBuilder builder) + throws IOException, VCardException; + + /** + * The method variants which tells this object the operation is already canceled. + * XXX: Is this really necessary? + * @hide + */ + public abstract void parse(InputStream is, String charset, + VCardBuilder builder, boolean canceled) + throws IOException, VCardException; + + /** + * Cancel parsing. + * Actual cancel is done after the end of the current one vcard entry parsing. + */ + public void cancel() { + mCanceled = true; + } +} diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java new file mode 100644 index 000000000000..17a138fab981 --- /dev/null +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -0,0 +1,948 @@ +/* + * 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.pim.vcard; + +import android.pim.vcard.exception.VCardException; +import android.pim.vcard.exception.VCardNestedException; +import android.pim.vcard.exception.VCardNotSupportedException; +import android.pim.vcard.exception.VCardVersionException; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +/** + * This class is used to parse vcard. Please refer to vCard Specification 2.1. + */ +public class VCardParser_V21 extends VCardParser { + private static final String LOG_TAG = "VCardParser_V21"; + + /** Store the known-type */ + private static final HashSet<String> sKnownTypeSet = new HashSet<String>( + Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK", + "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS", + "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK", + "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL", + "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF", + "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF", + "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI", + "WAVE", "AIFF", "PCM", "X509", "PGP")); + + /** Store the known-value */ + private static final HashSet<String> sKnownValueSet = new HashSet<String>( + Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")); + + /** Store the property names available in vCard 2.1 */ + private static final HashSet<String> sAvailablePropertyNameV21 = + new HashSet<String>(Arrays.asList( + "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", + "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", + "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")); + + // Though vCard 2.1 specification does not allow "B" encoding, some data may have it. + // We allow it for safety... + private static final HashSet<String> sAvailableEncodingV21 = + new HashSet<String>(Arrays.asList( + "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B")); + + // Used only for parsing END:VCARD. + private String mPreviousLine; + + /** The builder to build parsed data */ + protected VCardBuilder mBuilder = null; + + /** The encoding type */ + protected String mEncoding = null; + + protected final String sDefaultEncoding = "8BIT"; + + // Should not directly read a line from this. Use getLine() instead. + protected BufferedReader mReader; + + // In some cases, vCard is nested. Currently, we only consider the most interior vCard data. + // See v21_foma_1.vcf in test directory for more information. + private int mNestCount; + + // In order to reduce warning message as much as possible, we hold the value which made Logger + // emit a warning message. + protected HashSet<String> mWarningValueMap = new HashSet<String>(); + + // Just for debugging + private long mTimeTotal; + private long mTimeStartRecord; + private long mTimeEndRecord; + private long mTimeStartProperty; + private long mTimeEndProperty; + private long mTimeParseItems; + private long mTimeParseItem1; + private long mTimeParseItem2; + private long mTimeParseItem3; + private long mTimeHandlePropertyValue1; + private long mTimeHandlePropertyValue2; + private long mTimeHandlePropertyValue3; + + /** + * Create a new VCard parser. + */ + public VCardParser_V21() { + super(); + } + + public VCardParser_V21(VCardSourceDetector detector) { + super(); + if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) { + mNestCount = 1; + } + } + + /** + * Parse the file at the given position + * vcard_file = [wsls] vcard [wsls] + */ + protected void parseVCardFile() throws IOException, VCardException { + boolean firstReading = true; + while (true) { + if (mCanceled) { + break; + } + if (!parseOneVCard(firstReading)) { + break; + } + firstReading = false; + } + + if (mNestCount > 0) { + boolean useCache = true; + for (int i = 0; i < mNestCount; i++) { + readEndVCard(useCache, true); + useCache = false; + } + } + } + + protected String getVersion() { + return "2.1"; + } + + /** + * @return true when the propertyName is a valid property name. + */ + protected boolean isValidPropertyName(String propertyName) { + if (!(sAvailablePropertyNameV21.contains(propertyName.toUpperCase()) || + propertyName.startsWith("X-")) && + !mWarningValueMap.contains(propertyName)) { + mWarningValueMap.add(propertyName); + Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName); + } + return true; + } + + /** + * @return true when the encoding is a valid encoding. + */ + protected boolean isValidEncoding(String encoding) { + return sAvailableEncodingV21.contains(encoding.toUpperCase()); + } + + /** + * @return String. It may be null, or its length may be 0 + * @throws IOException + */ + protected String getLine() throws IOException { + return mReader.readLine(); + } + + /** + * @return String with it's length > 0 + * @throws IOException + * @throws VCardException when the stream reached end of line + */ + protected String getNonEmptyLine() throws IOException, VCardException { + String line; + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException("Reached end of buffer."); + } else if (line.trim().length() > 0) { + return line; + } + } + } + + /** + * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF + * items *CRLF + * "END" [ws] ":" [ws] "VCARD" + */ + private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException { + boolean allowGarbage = false; + if (firstReading) { + if (mNestCount > 0) { + for (int i = 0; i < mNestCount; i++) { + if (!readBeginVCard(allowGarbage)) { + return false; + } + allowGarbage = true; + } + } + } + + if (!readBeginVCard(allowGarbage)) { + return false; + } + long start; + if (mBuilder != null) { + start = System.currentTimeMillis(); + mBuilder.startRecord("VCARD"); + mTimeStartRecord += System.currentTimeMillis() - start; + } + start = System.currentTimeMillis(); + parseItems(); + mTimeParseItems += System.currentTimeMillis() - start; + readEndVCard(true, false); + if (mBuilder != null) { + start = System.currentTimeMillis(); + mBuilder.endRecord(); + mTimeEndRecord += System.currentTimeMillis() - start; + } + return true; + } + + /** + * @return True when successful. False when reaching the end of line + * @throws IOException + * @throws VCardException + */ + protected boolean readBeginVCard(boolean allowGarbage) + throws IOException, VCardException { + String line; + do { + while (true) { + line = getLine(); + if (line == null) { + return false; + } else if (line.trim().length() > 0) { + break; + } + } + String[] strArray = line.split(":", 2); + int length = strArray.length; + + // Though vCard 2.1/3.0 specification does not allow lower cases, + // some data may have them, so we allow it (Actually, previous code + // had explicitly allowed "BEGIN:vCard" though there's no example). + // + // TODO: ignore non vCard entry (e.g. vcalendar). + // XXX: Not sure, but according to VDataBuilder.java, vcalendar + // entry + // may be nested. Just seeking "END:SOMETHING" may not be enough. + // e.g. + // BEGIN:VCARD + // ... (Valid. Must parse this) + // END:VCARD + // BEGIN:VSOMETHING + // ... (Must ignore this) + // BEGIN:VSOMETHING2 + // ... (Must ignore this) + // END:VSOMETHING2 + // ... (Must ignore this!) + // END:VSOMETHING + // BEGIN:VCARD + // ... (Valid. Must parse this) + // END:VCARD + // INVALID_STRING (VCardException should be thrown) + if (length == 2 && + strArray[0].trim().equalsIgnoreCase("BEGIN") && + strArray[1].trim().equalsIgnoreCase("VCARD")) { + return true; + } else if (!allowGarbage) { + if (mNestCount > 0) { + mPreviousLine = line; + return false; + } else { + throw new VCardException( + "Expected String \"BEGIN:VCARD\" did not come " + + "(Instead, \"" + line + "\" came)"); + } + } + } while(allowGarbage); + + throw new VCardException("Reached where must not be reached."); + } + + /** + * The arguments useCache and allowGarbase are usually true and false accordingly when + * this function is called outside this function itself. + * + * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine() + * is used. + * @param allowGarbage When true, ignore non "END:VCARD" line. + * @throws IOException + * @throws VCardException + */ + protected void readEndVCard(boolean useCache, boolean allowGarbage) + throws IOException, VCardException { + String line; + do { + if (useCache) { + // Though vCard specification does not allow lower cases, + // some data may have them, so we allow it. + line = mPreviousLine; + } else { + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException("Expected END:VCARD was not found."); + } else if (line.trim().length() > 0) { + break; + } + } + } + + String[] strArray = line.split(":", 2); + if (strArray.length == 2 && + strArray[0].trim().equalsIgnoreCase("END") && + strArray[1].trim().equalsIgnoreCase("VCARD")) { + return; + } else if (!allowGarbage) { + throw new VCardException("END:VCARD != \"" + mPreviousLine + "\""); + } + useCache = false; + } while (allowGarbage); + } + + /** + * items = *CRLF item + * / item + */ + protected void parseItems() throws IOException, VCardException { + /* items *CRLF item / item */ + boolean ended = false; + + if (mBuilder != null) { + long start = System.currentTimeMillis(); + mBuilder.startProperty(); + mTimeStartProperty += System.currentTimeMillis() - start; + } + ended = parseItem(); + if (mBuilder != null && !ended) { + long start = System.currentTimeMillis(); + mBuilder.endProperty(); + mTimeEndProperty += System.currentTimeMillis() - start; + } + + while (!ended) { + // follow VCARD ,it wont reach endProperty + if (mBuilder != null) { + long start = System.currentTimeMillis(); + mBuilder.startProperty(); + mTimeStartProperty += System.currentTimeMillis() - start; + } + ended = parseItem(); + if (mBuilder != null && !ended) { + long start = System.currentTimeMillis(); + mBuilder.endProperty(); + mTimeEndProperty += System.currentTimeMillis() - start; + } + } + } + + /** + * item = [groups "."] name [params] ":" value CRLF + * / [groups "."] "ADR" [params] ":" addressparts CRLF + * / [groups "."] "ORG" [params] ":" orgparts CRLF + * / [groups "."] "N" [params] ":" nameparts CRLF + * / [groups "."] "AGENT" [params] ":" vcard CRLF + */ + protected boolean parseItem() throws IOException, VCardException { + mEncoding = sDefaultEncoding; + + String line = getNonEmptyLine(); + long start = System.currentTimeMillis(); + + String[] propertyNameAndValue = separateLineAndHandleGroup(line); + if (propertyNameAndValue == null) { + return true; + } + if (propertyNameAndValue.length != 2) { + throw new VCardException("Invalid line \"" + line + "\""); + } + String propertyName = propertyNameAndValue[0].toUpperCase(); + String propertyValue = propertyNameAndValue[1]; + + mTimeParseItem1 += System.currentTimeMillis() - start; + + if (propertyName.equals("ADR") || + propertyName.equals("ORG") || + propertyName.equals("N")) { + start = System.currentTimeMillis(); + handleMultiplePropertyValue(propertyName, propertyValue); + mTimeParseItem3 += System.currentTimeMillis() - start; + return false; + } else if (propertyName.equals("AGENT")) { + handleAgent(propertyValue); + return false; + } else if (isValidPropertyName(propertyName)) { + if (propertyName.equals("BEGIN")) { + if (propertyValue.equals("VCARD")) { + throw new VCardNestedException("This vCard has nested vCard data in it."); + } else { + throw new VCardException("Unknown BEGIN type: " + propertyValue); + } + } else if (propertyName.equals("VERSION") && + !propertyValue.equals(getVersion())) { + throw new VCardVersionException("Incompatible version: " + + propertyValue + " != " + getVersion()); + } + start = System.currentTimeMillis(); + handlePropertyValue(propertyName, propertyValue); + mTimeParseItem2 += System.currentTimeMillis() - start; + return false; + } + + throw new VCardException("Unknown property name: \"" + + propertyName + "\""); + } + + static private final int STATE_GROUP_OR_PROPNAME = 0; + static private final int STATE_PARAMS = 1; + // vCard 3.1 specification allows double-quoted param-value, while vCard 2.1 does not. + // This is just for safety. + static private final int STATE_PARAMS_IN_DQUOTE = 2; + + protected String[] separateLineAndHandleGroup(String line) throws VCardException { + int length = line.length(); + int state = STATE_GROUP_OR_PROPNAME; + int nameIndex = 0; + + String[] propertyNameAndValue = new String[2]; + + for (int i = 0; i < length; i++) { + char ch = line.charAt(i); + switch (state) { + case STATE_GROUP_OR_PROPNAME: + if (ch == ':') { + String propertyName = line.substring(nameIndex, i); + if (propertyName.equalsIgnoreCase("END")) { + mPreviousLine = line; + return null; + } + if (mBuilder != null) { + mBuilder.propertyName(propertyName); + } + propertyNameAndValue[0] = propertyName; + if (i < length - 1) { + propertyNameAndValue[1] = line.substring(i + 1); + } else { + propertyNameAndValue[1] = ""; + } + return propertyNameAndValue; + } else if (ch == '.') { + String groupName = line.substring(nameIndex, i); + if (mBuilder != null) { + mBuilder.propertyGroup(groupName); + } + nameIndex = i + 1; + } else if (ch == ';') { + String propertyName = line.substring(nameIndex, i); + if (propertyName.equalsIgnoreCase("END")) { + mPreviousLine = line; + return null; + } + if (mBuilder != null) { + mBuilder.propertyName(propertyName); + } + propertyNameAndValue[0] = propertyName; + nameIndex = i + 1; + state = STATE_PARAMS; + } + break; + case STATE_PARAMS: + if (ch == '"') { + state = STATE_PARAMS_IN_DQUOTE; + } else if (ch == ';') { + handleParams(line.substring(nameIndex, i)); + nameIndex = i + 1; + } else if (ch == ':') { + handleParams(line.substring(nameIndex, i)); + if (i < length - 1) { + propertyNameAndValue[1] = line.substring(i + 1); + } else { + propertyNameAndValue[1] = ""; + } + return propertyNameAndValue; + } + break; + case STATE_PARAMS_IN_DQUOTE: + if (ch == '"') { + state = STATE_PARAMS; + } + break; + } + } + + throw new VCardException("Invalid line: \"" + line + "\""); + } + + + /** + * params = ";" [ws] paramlist + * paramlist = paramlist [ws] ";" [ws] param + * / param + * param = "TYPE" [ws] "=" [ws] ptypeval + * / "VALUE" [ws] "=" [ws] pvalueval + * / "ENCODING" [ws] "=" [ws] pencodingval + * / "CHARSET" [ws] "=" [ws] charsetval + * / "LANGUAGE" [ws] "=" [ws] langval + * / "X-" word [ws] "=" [ws] word + * / knowntype + */ + protected void handleParams(String params) throws VCardException { + String[] strArray = params.split("=", 2); + if (strArray.length == 2) { + String paramName = strArray[0].trim(); + String paramValue = strArray[1].trim(); + if (paramName.equals("TYPE")) { + handleType(paramValue); + } else if (paramName.equals("VALUE")) { + handleValue(paramValue); + } else if (paramName.equals("ENCODING")) { + handleEncoding(paramValue); + } else if (paramName.equals("CHARSET")) { + handleCharset(paramValue); + } else if (paramName.equals("LANGUAGE")) { + handleLanguage(paramValue); + } else if (paramName.startsWith("X-")) { + handleAnyParam(paramName, paramValue); + } else { + throw new VCardException("Unknown type \"" + paramName + "\""); + } + } else { + handleType(strArray[0]); + } + } + + /** + * ptypeval = knowntype / "X-" word + */ + protected void handleType(String ptypeval) { + String upperTypeValue = ptypeval; + if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) && + !mWarningValueMap.contains(ptypeval)) { + mWarningValueMap.add(ptypeval); + Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval); + } + if (mBuilder != null) { + mBuilder.propertyParamType("TYPE"); + mBuilder.propertyParamValue(upperTypeValue); + } + } + + /** + * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word + */ + protected void handleValue(String pvalueval) throws VCardException { + if (sKnownValueSet.contains(pvalueval.toUpperCase()) || + pvalueval.startsWith("X-")) { + if (mBuilder != null) { + mBuilder.propertyParamType("VALUE"); + mBuilder.propertyParamValue(pvalueval); + } + } else { + throw new VCardException("Unknown value \"" + pvalueval + "\""); + } + } + + /** + * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word + */ + protected void handleEncoding(String pencodingval) throws VCardException { + if (isValidEncoding(pencodingval) || + pencodingval.startsWith("X-")) { + if (mBuilder != null) { + mBuilder.propertyParamType("ENCODING"); + mBuilder.propertyParamValue(pencodingval); + } + mEncoding = pencodingval; + } else { + throw new VCardException("Unknown encoding \"" + pencodingval + "\""); + } + } + + /** + * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521), + * but some vCard contains other charset, so we allow them. + */ + protected void handleCharset(String charsetval) { + if (mBuilder != null) { + mBuilder.propertyParamType("CHARSET"); + mBuilder.propertyParamValue(charsetval); + } + } + + /** + * See also Section 7.1 of RFC 1521 + */ + protected void handleLanguage(String langval) throws VCardException { + String[] strArray = langval.split("-"); + if (strArray.length != 2) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + String tmp = strArray[0]; + int length = tmp.length(); + for (int i = 0; i < length; i++) { + if (!isLetter(tmp.charAt(i))) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + } + tmp = strArray[1]; + length = tmp.length(); + for (int i = 0; i < length; i++) { + if (!isLetter(tmp.charAt(i))) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + } + if (mBuilder != null) { + mBuilder.propertyParamType("LANGUAGE"); + mBuilder.propertyParamValue(langval); + } + } + + /** + * Mainly for "X-" type. This accepts any kind of type without check. + */ + protected void handleAnyParam(String paramName, String paramValue) { + if (mBuilder != null) { + mBuilder.propertyParamType(paramName); + mBuilder.propertyParamValue(paramValue); + } + } + + protected void handlePropertyValue( + String propertyName, String propertyValue) throws + IOException, VCardException { + if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { + long start = System.currentTimeMillis(); + String result = getQuotedPrintable(propertyValue); + if (mBuilder != null) { + ArrayList<String> v = new ArrayList<String>(); + v.add(result); + mBuilder.propertyValues(v); + } + mTimeHandlePropertyValue2 += System.currentTimeMillis() - start; + } else if (mEncoding.equalsIgnoreCase("BASE64") || + mEncoding.equalsIgnoreCase("B")) { + long start = System.currentTimeMillis(); + // It is very rare, but some BASE64 data may be so big that + // OutOfMemoryError occurs. To ignore such cases, use try-catch. + try { + String result = getBase64(propertyValue); + if (mBuilder != null) { + ArrayList<String> v = new ArrayList<String>(); + v.add(result); + mBuilder.propertyValues(v); + } + } catch (OutOfMemoryError error) { + Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!"); + if (mBuilder != null) { + mBuilder.propertyValues(null); + } + } + mTimeHandlePropertyValue3 += System.currentTimeMillis() - start; + } else { + if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT") + || mEncoding.equalsIgnoreCase("8BIT") + || mEncoding.toUpperCase().startsWith("X-"))) { + Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\"."); + } + + long start = System.currentTimeMillis(); + if (mBuilder != null) { + ArrayList<String> v = new ArrayList<String>(); + v.add(maybeUnescapeText(propertyValue)); + mBuilder.propertyValues(v); + } + mTimeHandlePropertyValue1 += System.currentTimeMillis() - start; + } + } + + protected String getQuotedPrintable(String firstString) throws IOException, VCardException { + // Specifically, there may be some padding between = and CRLF. + // See the following: + // + // qp-line := *(qp-segment transport-padding CRLF) + // qp-part transport-padding + // qp-segment := qp-section *(SPACE / TAB) "=" + // ; Maximum length of 76 characters + // + // e.g. (from RFC 2045) + // Now's the time = + // for all folk to come= + // to the aid of their country. + if (firstString.trim().endsWith("=")) { + // remove "transport-padding" + int pos = firstString.length() - 1; + while(firstString.charAt(pos) != '=') { + } + StringBuilder builder = new StringBuilder(); + builder.append(firstString.substring(0, pos + 1)); + builder.append("\r\n"); + String line; + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException( + "File ended during parsing quoted-printable String"); + } + if (line.trim().endsWith("=")) { + // remove "transport-padding" + pos = line.length() - 1; + while(line.charAt(pos) != '=') { + } + builder.append(line.substring(0, pos + 1)); + builder.append("\r\n"); + } else { + builder.append(line); + break; + } + } + return builder.toString(); + } else { + return firstString; + } + } + + protected String getBase64(String firstString) throws IOException, VCardException { + StringBuilder builder = new StringBuilder(); + builder.append(firstString); + + while (true) { + String line = getLine(); + if (line == null) { + throw new VCardException( + "File ended during parsing BASE64 binary"); + } + if (line.length() == 0) { + break; + } + builder.append(line); + } + + return builder.toString(); + } + + /** + * Mainly for "ADR", "ORG", and "N" + * We do not care the number of strnosemi here. + * + * addressparts = 0*6(strnosemi ";") strnosemi + * ; PO Box, Extended Addr, Street, Locality, Region, + * Postal Code, Country Name + * orgparts = *(strnosemi ";") strnosemi + * ; First is Organization Name, + * remainder are Organization Units. + * nameparts = 0*4(strnosemi ";") strnosemi + * ; Family, Given, Middle, Prefix, Suffix. + * ; Example:Public;John;Q.;Reverend Dr.;III, Esq. + * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi + * ; To include a semicolon in this string, it must be escaped + * ; with a "\" character. + * + * We are not sure whether we should add "\" CRLF to each value. + * For now, we exclude them. + */ + protected void handleMultiplePropertyValue( + String propertyName, String propertyValue) throws IOException, VCardException { + // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some data have it. + if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { + propertyValue = getQuotedPrintable(propertyValue); + } + + if (mBuilder != null) { + // TODO: limit should be set in accordance with propertyName? + StringBuilder builder = new StringBuilder(); + ArrayList<String> list = new ArrayList<String>(); + int length = propertyValue.length(); + for (int i = 0; i < length; i++) { + char ch = propertyValue.charAt(i); + if (ch == '\\' && i < length - 1) { + char nextCh = propertyValue.charAt(i + 1); + String unescapedString = maybeUnescape(nextCh); + if (unescapedString != null) { + builder.append(unescapedString); + i++; + } else { + builder.append(ch); + } + } else if (ch == ';') { + list.add(builder.toString()); + builder = new StringBuilder(); + } else { + builder.append(ch); + } + } + list.add(builder.toString()); + mBuilder.propertyValues(list); + } + } + + /** + * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all. + * + * item = ... + * / [groups "."] "AGENT" + * [params] ":" vcard CRLF + * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF + * items *CRLF "END" [ws] ":" [ws] "VCARD" + * + */ + protected void handleAgent(String propertyValue) throws VCardException { + throw new VCardNotSupportedException("AGENT Property is not supported now."); + /* This is insufficient support. Also, AGENT Property is very rare. + Ignore it for now. + TODO: fix this. + + String[] strArray = propertyValue.split(":", 2); + if (!(strArray.length == 2 || + strArray[0].trim().equalsIgnoreCase("BEGIN") && + strArray[1].trim().equalsIgnoreCase("VCARD"))) { + throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\""); + } + parseItems(); + readEndVCard(); + */ + } + + /** + * For vCard 3.0. + */ + protected String maybeUnescapeText(String text) { + return text; + } + + /** + * Returns unescaped String if the character should be unescaped. Return null otherwise. + * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be. + */ + protected String maybeUnescape(char ch) { + // Original vCard 2.1 specification does not allow transformation + // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of + // this class allowed them, so keep it as is. + if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') { + return String.valueOf(ch); + } else { + return null; + } + } + + @Override + public boolean parse(InputStream is, VCardBuilder builder) + throws IOException, VCardException { + return parse(is, VCardConfig.DEFAULT_CHARSET, builder); + } + + @Override + public boolean parse(InputStream is, String charset, VCardBuilder builder) + throws IOException, VCardException { + // TODO: make this count error entries instead of just throwing VCardException. + + { + // TODO: If we really need to allow only CRLF as line break, + // we will have to develop our own BufferedReader(). + final InputStreamReader tmpReader = new InputStreamReader(is, charset); + if (VCardConfig.showPerformanceLog()) { + mReader = new CustomBufferedReader(tmpReader); + } else { + mReader = new BufferedReader(tmpReader); + } + } + + mBuilder = builder; + + long start = System.currentTimeMillis(); + if (mBuilder != null) { + mBuilder.start(); + } + parseVCardFile(); + if (mBuilder != null) { + mBuilder.end(); + } + mTimeTotal += System.currentTimeMillis() - start; + + if (VCardConfig.showPerformanceLog()) { + showPerformanceInfo(); + } + + return true; + } + + @Override + public void parse(InputStream is, String charset, VCardBuilder builder, boolean canceled) + throws IOException, VCardException { + mCanceled = canceled; + parse(is, charset, builder); + } + + private void showPerformanceInfo() { + Log.d(LOG_TAG, "total parsing time: " + mTimeTotal + " ms"); + if (mReader instanceof CustomBufferedReader) { + Log.d(LOG_TAG, "total readLine time: " + + ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms"); + } + Log.d(LOG_TAG, "mTimeStartRecord: " + mTimeStartRecord + " ms"); + Log.d(LOG_TAG, "mTimeEndRecord: " + mTimeEndRecord + " ms"); + Log.d(LOG_TAG, "mTimeParseItem1: " + mTimeParseItem1 + " ms"); + Log.d(LOG_TAG, "mTimeParseItem2: " + mTimeParseItem2 + " ms"); + Log.d(LOG_TAG, "mTimeParseItem3: " + mTimeParseItem3 + " ms"); + Log.d(LOG_TAG, "mTimeHandlePropertyValue1: " + mTimeHandlePropertyValue1 + " ms"); + Log.d(LOG_TAG, "mTimeHandlePropertyValue2: " + mTimeHandlePropertyValue2 + " ms"); + Log.d(LOG_TAG, "mTimeHandlePropertyValue3: " + mTimeHandlePropertyValue3 + " ms"); + } + + private boolean isLetter(char ch) { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + return true; + } + return false; + } +} + +class CustomBufferedReader extends BufferedReader { + private long mTime; + + public CustomBufferedReader(Reader in) { + super(in); + } + + @Override + public String readLine() throws IOException { + long start = System.currentTimeMillis(); + String ret = super.readLine(); + long end = System.currentTimeMillis(); + mTime += end - start; + return ret; + } + + public long getTotalmillisecond() { + return mTime; + } +} diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java new file mode 100644 index 000000000000..634d9f5d20dd --- /dev/null +++ b/core/java/android/pim/vcard/VCardParser_V30.java @@ -0,0 +1,306 @@ +/* + * 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.pim.vcard; + +import android.pim.vcard.exception.VCardException; +import android.util.Log; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; + +/** + * This class is used to parse vcard3.0. <br> + * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426) + */ +public class VCardParser_V30 extends VCardParser_V21 { + private static final String LOG_TAG = "VCardParser_V30"; + + private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>( + Arrays.asList( + "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", + "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", + "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1 + "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS", + "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0 + + // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety. + private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>( + Arrays.asList("7BIT", "8BIT", "BASE64", "B")); + + // Although RFC 2426 specifies some property must not have parameters, we allow it, + // since there may be some careers which violates the RFC... + private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>(); + + private String mPreviousLine; + + @Override + protected String getVersion() { + return "3.0"; + } + + @Override + protected boolean isValidPropertyName(String propertyName) { + if (!(sAcceptablePropsWithParam.contains(propertyName) || + acceptablePropsWithoutParam.contains(propertyName) || + propertyName.startsWith("X-")) && + !mWarningValueMap.contains(propertyName)) { + mWarningValueMap.add(propertyName); + Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName); + } + return true; + } + + @Override + protected boolean isValidEncoding(String encoding) { + return sAcceptableEncodingV30.contains(encoding.toUpperCase()); + } + + @Override + protected String getLine() throws IOException { + if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } else { + return mReader.readLine(); + } + } + + /** + * vCard 3.0 requires that the line with space at the beginning of the line + * must be combined with previous line. + */ + @Override + protected String getNonEmptyLine() throws IOException, VCardException { + String line; + StringBuilder builder = null; + while (true) { + line = mReader.readLine(); + if (line == null) { + if (builder != null) { + return builder.toString(); + } else if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } + throw new VCardException("Reached end of buffer."); + } else if (line.length() == 0) { + if (builder != null) { + return builder.toString(); + } else if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } + } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') { + if (builder != null) { + // See Section 5.8.1 of RFC 2425 (MIME-DIR document). + // Following is the excerpts from it. + // + // DESCRIPTION:This is a long description that exists on a long line. + // + // Can be represented as: + // + // DESCRIPTION:This is a long description + // that exists on a long line. + // + // It could also be represented as: + // + // DESCRIPTION:This is a long descrip + // tion that exists o + // n a long line. + builder.append(line.substring(1)); + } else if (mPreviousLine != null) { + builder = new StringBuilder(); + builder.append(mPreviousLine); + mPreviousLine = null; + builder.append(line.substring(1)); + } else { + throw new VCardException("Space exists at the beginning of the line"); + } + } else { + if (mPreviousLine == null) { + mPreviousLine = line; + if (builder != null) { + return builder.toString(); + } + } else { + String ret = mPreviousLine; + mPreviousLine = line; + return ret; + } + } + } + } + + + /** + * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF + * 1*(contentline) + * ;A vCard object MUST include the VERSION, FN and N types. + * [group "."] "END" ":" "VCARD" 1*CRLF + */ + @Override + protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { + // TODO: vCard 3.0 supports group. + return super.readBeginVCard(allowGarbage); + } + + @Override + protected void readEndVCard(boolean useCache, boolean allowGarbage) + throws IOException, VCardException { + // TODO: vCard 3.0 supports group. + super.readEndVCard(useCache, allowGarbage); + } + + /** + * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not. + */ + @Override + protected void handleParams(String params) throws VCardException { + try { + super.handleParams(params); + } catch (VCardException e) { + // maybe IANA type + String[] strArray = params.split("=", 2); + if (strArray.length == 2) { + handleAnyParam(strArray[0], strArray[1]); + } else { + // Must not come here in the current implementation. + throw new VCardException( + "Unknown params value: " + params); + } + } + } + + @Override + protected void handleAnyParam(String paramName, String paramValue) { + // vCard 3.0 accept comma-separated multiple values, but + // current PropertyNode does not accept it. + // For now, we do not split the values. + // + // TODO: fix this. + super.handleAnyParam(paramName, paramValue); + } + + /** + * vCard 3.0 defines + * + * param = param-name "=" param-value *("," param-value) + * param-name = iana-token / x-name + * param-value = ptext / quoted-string + * quoted-string = DQUOTE QSAFE-CHAR DQUOTE + */ + @Override + protected void handleType(String ptypevalues) { + String[] ptypeArray = ptypevalues.split(","); + mBuilder.propertyParamType("TYPE"); + for (String value : ptypeArray) { + int length = value.length(); + if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) { + mBuilder.propertyParamValue(value.substring(1, value.length() - 1)); + } else { + mBuilder.propertyParamValue(value); + } + } + } + + @Override + protected void handleAgent(String propertyValue) throws VCardException { + // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0. + // + // e.g. + // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n + // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n + // ET:jfriday@host.com\nEND:VCARD\n + // + // TODO: fix this. + // + // issue: + // vCard 3.0 also allows this as an example. + // + // AGENT;VALUE=uri: + // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com + // + // This is not VCARD. Should we support this? + throw new VCardException("AGENT in vCard 3.0 is not supported yet."); + } + + /** + * vCard 3.0 does not require two CRLF at the last of BASE64 data. + * It only requires that data should be MIME-encoded. + */ + @Override + protected String getBase64(String firstString) throws IOException, VCardException { + StringBuilder builder = new StringBuilder(); + builder.append(firstString); + + while (true) { + String line = getLine(); + if (line == null) { + throw new VCardException( + "File ended during parsing BASE64 binary"); + } + if (line.length() == 0) { + break; + } else if (!line.startsWith(" ") && !line.startsWith("\t")) { + mPreviousLine = line; + break; + } + builder.append(line); + } + + return builder.toString(); + } + + /** + * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N") + * ; \\ encodes \, \n or \N encodes newline + * ; \; encodes ;, \, encodes , + * + * Note: Apple escape ':' into '\:' while does not escape '\' + */ + @Override + protected String maybeUnescapeText(String text) { + StringBuilder builder = new StringBuilder(); + int length = text.length(); + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + if (ch == '\\' && i < length - 1) { + char next_ch = text.charAt(++i); + if (next_ch == 'n' || next_ch == 'N') { + builder.append("\r\n"); + } else { + builder.append(next_ch); + } + } else { + builder.append(ch); + } + } + return builder.toString(); + } + + @Override + protected String maybeUnescape(char ch) { + if (ch == 'n' || ch == 'N') { + return "\r\n"; + } else { + return String.valueOf(ch); + } + } +} diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java new file mode 100644 index 000000000000..7e2be2b12eed --- /dev/null +++ b/core/java/android/pim/vcard/VCardSourceDetector.java @@ -0,0 +1,137 @@ +/* + * 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.pim.vcard; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Class which tries to detects the source of the vCard from its properties. + * Currently this implementation is very premature. + * @hide + */ +public class VCardSourceDetector implements VCardBuilder { + // Should only be used in package. + static final int TYPE_UNKNOWN = 0; + static final int TYPE_APPLE = 1; + static final int TYPE_JAPANESE_MOBILE_PHONE = 2; // Used in Japanese mobile phones. + static final int TYPE_FOMA = 3; // Used in some Japanese FOMA mobile phones. + static final int TYPE_WINDOWS_MOBILE_JP = 4; + // TODO: Excel, etc. + + private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList( + "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME", + "X-ABADR", "X-ABUID")); + + private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList( + "X-GNO", "X-GN", "X-REDUCTION")); + + private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList( + "X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC")); + + // Note: these signes appears before the signs of the other type (e.g. "X-GN"). + // In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES. + private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList( + "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED", + "X-SD-DESCRIPTION")); + private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE"; + + private int mType = TYPE_UNKNOWN; + // Some mobile phones (like FOMA) tells us the charset of the data. + private boolean mNeedParseSpecifiedCharset; + private String mSpecifiedCharset; + + public void start() { + } + + public void end() { + } + + public void startRecord(String type) { + } + + public void startProperty() { + mNeedParseSpecifiedCharset = false; + } + + public void endProperty() { + } + + public void endRecord() { + } + + public void propertyGroup(String group) { + } + + public void propertyName(String name) { + if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) { + mType = TYPE_FOMA; + mNeedParseSpecifiedCharset = true; + return; + } + if (mType != TYPE_UNKNOWN) { + return; + } + if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) { + mType = TYPE_WINDOWS_MOBILE_JP; + } else if (FOMA_SIGNS.contains(name)) { + mType = TYPE_FOMA; + } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) { + mType = TYPE_JAPANESE_MOBILE_PHONE; + } else if (APPLE_SIGNS.contains(name)) { + mType = TYPE_APPLE; + } + } + + public void propertyParamType(String type) { + } + + public void propertyParamValue(String value) { + } + + public void propertyValues(List<String> values) { + if (mNeedParseSpecifiedCharset && values.size() > 0) { + mSpecifiedCharset = values.get(0); + } + } + + int getType() { + return mType; + } + + /** + * Return charset String guessed from the source's properties. + * This method must be called after parsing target file(s). + * @return Charset String. Null is returned if guessing the source fails. + */ + public String getEstimatedCharset() { + if (mSpecifiedCharset != null) { + return mSpecifiedCharset; + } + switch (mType) { + case TYPE_WINDOWS_MOBILE_JP: + case TYPE_FOMA: + case TYPE_JAPANESE_MOBILE_PHONE: + return "SHIFT_JIS"; + case TYPE_APPLE: + return "UTF-8"; + default: + return null; + } + } +} diff --git a/core/java/android/pim/vcard/exception/VCardException.java b/core/java/android/pim/vcard/exception/VCardException.java new file mode 100644 index 000000000000..e557219f235a --- /dev/null +++ b/core/java/android/pim/vcard/exception/VCardException.java @@ -0,0 +1,35 @@ +/* + * 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.pim.vcard.exception; + +public class VCardException extends java.lang.Exception { + /** + * Constructs a VCardException object + */ + public VCardException() { + super(); + } + + /** + * Constructs a VCardException object + * + * @param message the error message + */ + public VCardException(String message) { + super(message); + } + +} diff --git a/core/java/android/pim/vcard/exception/VCardNestedException.java b/core/java/android/pim/vcard/exception/VCardNestedException.java new file mode 100644 index 000000000000..503c2fbcf536 --- /dev/null +++ b/core/java/android/pim/vcard/exception/VCardNestedException.java @@ -0,0 +1,29 @@ +/* + * 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.pim.vcard.exception; + +/** + * VCardException thrown when VCard is nested without VCardParser's being notified. + */ +public class VCardNestedException extends VCardNotSupportedException { + public VCardNestedException() { + super(); + } + public VCardNestedException(String message) { + super(message); + } +} diff --git a/core/java/android/pim/vcard/exception/VCardNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardNotSupportedException.java new file mode 100644 index 000000000000..616aa7763b0e --- /dev/null +++ b/core/java/android/pim/vcard/exception/VCardNotSupportedException.java @@ -0,0 +1,33 @@ +/* + * 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.pim.vcard.exception; + +/** + * The exception which tells that the input VCard is probably valid from the view of + * specification but not supported in the current framework for now. + * + * This is a kind of a good news from the view of development. + * It may be good to ask users to send a report with the VCard example + * for the future development. + */ +public class VCardNotSupportedException extends VCardException { + public VCardNotSupportedException() { + super(); + } + public VCardNotSupportedException(String message) { + super(message); + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/exception/VCardVersionException.java b/core/java/android/pim/vcard/exception/VCardVersionException.java new file mode 100644 index 000000000000..9fe8b7f92af9 --- /dev/null +++ b/core/java/android/pim/vcard/exception/VCardVersionException.java @@ -0,0 +1,29 @@ +/* + * 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.pim.vcard.exception; + +/** + * VCardException used only when the version of the vCard is different. + */ +public class VCardVersionException extends VCardException { + public VCardVersionException() { + super(); + } + public VCardVersionException(String message) { + super(message); + } +} diff --git a/core/java/android/pim/vcard/exception/package.html b/core/java/android/pim/vcard/exception/package.html new file mode 100644 index 000000000000..26b8a328b132 --- /dev/null +++ b/core/java/android/pim/vcard/exception/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +{@hide} +</BODY> +</HTML>
\ No newline at end of file diff --git a/core/java/android/pim/vcard/package.html b/core/java/android/pim/vcard/package.html new file mode 100644 index 000000000000..26b8a328b132 --- /dev/null +++ b/core/java/android/pim/vcard/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +{@hide} +</BODY> +</HTML>
\ No newline at end of file diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 718a120ded94..b779d59b533a 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -79,13 +79,7 @@ public final class ContactsContract { } } - public interface AggregatesColumns { - /** - * The display name for the contact. - * <P>Type: TEXT</P> - */ - public static final String DISPLAY_NAME = "display_name"; - + public interface ContactOptionsColumns { /** * The number of times a person has been contacted * <P>Type: INTEGER</P> @@ -116,6 +110,14 @@ public final class ContactsContract { * <P>Type: INTEGER (0 for false, 1 for true)</P> */ public static final String SEND_TO_VOICEMAIL = "send_to_voicemail"; + } + + public interface AggregatesColumns { + /** + * The display name for the contact. + * <P>Type: TEXT</P> + */ + public static final String DISPLAY_NAME = "display_name"; /** * Reference to the row in the data table holding the primary phone number. @@ -146,7 +148,8 @@ public final class ContactsContract { * Constants for the aggregates table, which contains a record per group * of contact representing the same person. */ - public static final class Aggregates implements BaseColumns, AggregatesColumns { + public static final class Aggregates implements BaseColumns, AggregatesColumns, + ContactOptionsColumns { /** * This utility class cannot be instantiated */ @@ -187,6 +190,8 @@ public final class ContactsContract { public static final Uri CONTENT_SUMMARY_STREQUENT_FILTER_URI = Uri.withAppendedPath( CONTENT_SUMMARY_STREQUENT_URI, "filter"); + public static final Uri CONTENT_SUMMARY_GROUP_URI = Uri.withAppendedPath( + CONTENT_SUMMARY_URI, "group"); /** * The MIME type of {@link #CONTENT_URI} providing a directory of * people. @@ -244,7 +249,7 @@ public final class ContactsContract { /** * Constants for the contacts table, which contains the base contact information. */ - public static final class Contacts implements BaseColumns { + public static final class Contacts implements BaseColumns, ContactOptionsColumns { /** * This utility class cannot be instantiated */ @@ -322,6 +327,29 @@ public final class ContactsContract { public static final String DIRTY = "dirty"; /** + * The aggregation mode for this contact. + * <P>Type: INTEGER</P> + */ + public static final String AGGREGATION_MODE = "aggregation_mode"; + + /** + * Aggregation mode: aggregate asynchronously. + */ + public static final int AGGREGATION_MODE_DEFAULT = 0; + + /** + * Aggregation mode: aggregate at the time the contact is inserted/updated. + */ + public static final int AGGREGATION_MODE_IMMEDITATE = 1; + + /** + * Aggregation mode: never aggregate this contact (note that the contact will not + * have a corresponding Aggregate and therefore will not be included in Aggregates + * query results.) + */ + public static final int AGGREGATION_MODE_DISABLED = 2; + + /** * A sub-directory of a single contact that contains all of their {@link Data} rows. * To access this directory append */ @@ -572,9 +600,20 @@ public final class ContactsContract { } /** + * The base types that all "Typed" data kinds support. + */ + public interface BaseTypes { + + /** + * A custom type. The custom label should be supplied by user. + */ + public static int TYPE_CUSTOM = 0; + } + + /** * Columns common across the specific types. */ - private interface CommonColumns { + private interface CommonColumns extends BaseTypes{ /** * The type of data, for example Home or Work. * <P>Type: INTEGER</P> @@ -595,17 +634,6 @@ public final class ContactsContract { } /** - * The base types that all "Typed" data kinds support. - */ - public interface BaseTypes { - - /** - * A custom type. The custom label should be supplied by user. - */ - public static int TYPE_CUSTOM = 0; - } - - /** * Parts of the name. */ public static final class StructuredName { @@ -671,18 +699,12 @@ public final class ContactsContract { /** * A nickname. */ - public static final class Nickname implements BaseTypes { + public static final class Nickname implements CommonColumns { private Nickname() {} /** Mime-type used when storing this in data table. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/nickname"; - /** - * The type of data, for example Home or Work. - * <P>Type: INTEGER</P> - */ - public static final String TYPE = "data1"; - public static final int TYPE_DEFAULT = 1; public static final int TYPE_OTHER_NAME = 2; public static final int TYPE_MAINDEN_NAME = 3; @@ -692,19 +714,13 @@ public final class ContactsContract { /** * The name itself */ - public static final String NAME = "data2"; - - /** - * The user provided label, only used if TYPE is {@link #TYPE_CUSTOM}. - * <P>Type: TEXT</P> - */ - public static final String LABEL = "data3"; + public static final String NAME = DATA; } /** * Common data definition for telephone numbers. */ - public static final class Phone implements BaseCommonColumns, CommonColumns, BaseTypes { + public static final class Phone implements BaseCommonColumns, CommonColumns { private Phone() {} /** Mime-type used when storing this in data table. */ @@ -745,13 +761,13 @@ public final class ContactsContract { * The phone number as the user entered it. * <P>Type: TEXT</P> */ - public static final String NUMBER = "data2"; + public static final String NUMBER = DATA; } /** * Common data definition for email addresses. */ - public static final class Email implements BaseCommonColumns, CommonColumns, BaseTypes { + public static final class Email implements BaseCommonColumns, CommonColumns { private Email() {} /** Mime-type used when storing this in data table. */ @@ -765,7 +781,7 @@ public final class ContactsContract { /** * Common data definition for postal addresses. */ - public static final class Postal implements BaseCommonColumns, CommonColumns, BaseTypes { + public static final class Postal implements BaseCommonColumns, CommonColumns { private Postal() {} /** Mime-type used when storing this in data table. */ @@ -793,7 +809,7 @@ public final class ContactsContract { /** * Common data definition for IM addresses. */ - public static final class Im implements BaseCommonColumns, CommonColumns, BaseTypes { + public static final class Im implements BaseCommonColumns, CommonColumns { private Im() {} /** Mime-type used when storing this in data table. */ @@ -853,33 +869,20 @@ public final class ContactsContract { /** * Common data definition for organizations. */ - public static final class Organization implements BaseCommonColumns, BaseTypes { + public static final class Organization implements BaseCommonColumns, CommonColumns { private Organization() {} /** Mime-type used when storing this in data table. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/organization"; - /** - * The type of data, for example Home or Work. - * <P>Type: INTEGER</P> - */ - public static final String TYPE = "data1"; - - public static final int TYPE_HOME = 1; - public static final int TYPE_WORK = 2; - public static final int TYPE_OTHER = 3; - - /** - * The user provided label, only used if TYPE is {@link #TYPE_CUSTOM}. - * <P>Type: TEXT</P> - */ - public static final String LABEL = "data2"; + public static final int TYPE_WORK = 1; + public static final int TYPE_OTHER = 2; /** * The company as the user entered it. * <P>Type: TEXT</P> */ - public static final String COMPANY = "data3"; + public static final String COMPANY = DATA; /** * The position title at this company as the user entered it. @@ -933,20 +936,18 @@ public final class ContactsContract { "vnd.android.cursor.item/group_membership"; /** - * The row id of the group that this group membership refers to. Either this or the - * GROUP_SOURCE_ID must be set. If they are both set then they must refer to the same - * group. + * The row id of the group that this group membership refers to. Exactly one of + * this or {@link #GROUP_SOURCE_ID} must be set when inserting a row. * <P>Type: INTEGER</P> */ public static final String GROUP_ROW_ID = "data1"; /** - * The source id of the group that this membership refers to. Either this or the - * GROUP_ROW_ID must be set. If they are both set then they must refer to the same - * group. + * The sourceid of the group that this group membership refers to. Exactly one of + * this or {@link #GROUP_ROW_ID} must be set when inserting a row. * <P>Type: STRING</P> */ - public static final String GROUP_SOURCE_ID = "data2"; + public static final String GROUP_SOURCE_ID = "group_sourceid"; } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 6f707cb84dc8..df3001d37698 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2714,6 +2714,12 @@ public final class Settings { public static final String GMAIL_NUM_RETRY_UPHILL_OP = "gmail_discard_error_uphill_op"; /** + * Controls if the protocol buffer version of the protocol will use a multipart request for + * attachment uploads. Value must be an integer where non-zero means true. Defaults to 0. + */ + public static final String GMAIL_USE_MULTIPART_PROTOBUF = "gmail_use_multipart_protobuf"; + + /** * the transcoder URL for mobile devices. */ public static final String TRANSCODER_URL = "mobile_transcoder_url"; @@ -2814,7 +2820,7 @@ public final class Settings { "gtalk_nosync_heartbeat_ping_interval_ms"; /** - * The maximum heartbeat interval used while on the WIFI network. + * The maximum heartbeat interval used while on the WIFI network. */ public static final String GTALK_SERVICE_WIFI_MAX_HEARTBEAT_INTERVAL_MS = "gtalk_wifi_max_heartbeat_ping_interval_ms"; @@ -2885,7 +2891,38 @@ public final class Settings { */ public static final String PUSH_MESSAGING_REGISTRATION_URL = "push_messaging_registration_url"; - + + /** + * This is gdata url to lookup album and picture info from picasa web. + */ + public static final String GTALK_PICASA_ALBUM_URL = + "gtalk_picasa_album_url"; + + /** + * This is the url to lookup picture info from flickr. + */ + public static final String GTALK_FLICKR_PHOTO_INFO_URL = + "gtalk_flickr_photo_info_url"; + + /** + * This is the url to lookup an actual picture from flickr. + */ + public static final String GTALK_FLICKR_PHOTO_URL = + "gtalk_flickr_photo_url"; + + /** + * This is the gdata url to lookup info on a youtube video. + */ + public static final String GTALK_YOUTUBE_VIDEO_URL = + "gtalk_youtube_video_url"; + + + /** + * This is the url for getting the app token for server-to-device data messaging. + */ + public static final String DATA_MESSAGE_GET_APP_TOKEN_URL = + "data_messaging_get_app_token_url"; + /** * Enable use of ssl session caching. * 'db' - save each session in a (per process) database diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 1cf7be9c157c..722a7cc0b4a8 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -104,7 +104,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { break; } } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) { - if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF) { + if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF && + isSinkDevice(address)) { // This device is a preferred sink. Make an A2DP connection // after a delay. We delay to avoid connection collisions, // and to give other profiles such as HFP a chance to @@ -185,6 +186,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return -1; } + private boolean isSinkDevice(String address) { + String uuids[] = mBluetoothService.getRemoteUuids(address); + UUID uuid; + for (String deviceUuid: uuids) { + uuid = UUID.fromString(deviceUuid); + if (BluetoothUuid.isAudioSink(uuid)) { + return true; + } + } + return false; + } + private synchronized boolean addAudioSink (String address) { String path = mBluetoothService.getObjectPathFromAddress(address); String propValues[] = (String []) getSinkPropertiesNative(path); diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 77b1b1d5e513..b780f419504e 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -285,6 +285,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { if (isEnabled()) { SystemService.start("hsag"); SystemService.start("hfag"); + SystemService.start("opush"); } break; case MESSAGE_FINISH_DISABLE: @@ -958,7 +959,38 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { return setPinNative(address, pinString, data.intValue()); } - public synchronized boolean cancelPin(String address) { + public synchronized boolean setPasskey(String address, int passkey) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (passkey < 0 || passkey > 999999 || !BluetoothDevice.checkBluetoothAddress(address)) { + return false; + } + address = address.toUpperCase(); + Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); + if (data == null) { + Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " + + "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + + " or by bluez.\n"); + return false; + } + return setPasskeyNative(address, passkey, data.intValue()); + } + + public synchronized boolean setPairingConfirmation(String address, boolean confirm) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + address = address.toUpperCase(); + Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); + if (data == null) { + Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " + + "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + + " or by bluez.\n"); + return false; + } + return setPairingConfirmationNative(address, confirm, data.intValue()); + } + + public synchronized boolean cancelPairingUserInput(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!BluetoothDevice.checkBluetoothAddress(address)) { @@ -967,12 +999,12 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { address = address.toUpperCase(); Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); if (data == null) { - Log.w(TAG, "cancelPin(" + address + ") called but no native data available, " + - "ignoring. Maybe the PasskeyAgent Request was already cancelled by the remote " + - "or by bluez.\n"); + Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " + + "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " + + "by the remote or by bluez.\n"); return false; } - return cancelPinNative(address, data.intValue()); + return cancelPairingUserInputNative(address, data.intValue()); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1159,7 +1191,10 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { private native int getDeviceServiceChannelNative(String objectPath, String uuid, int attributeId); - private native boolean cancelPinNative(String address, int nativeData); + private native boolean cancelPairingUserInputNative(String address, int nativeData); private native boolean setPinNative(String address, String pin, int nativeData); + private native boolean setPasskeyNative(String address, int passkey, int nativeData); + private native boolean setPairingConfirmationNative(String address, boolean confirm, + int nativeData); } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 38eb4d72a495..dc84d1f376c9 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -317,23 +317,53 @@ class BluetoothEventLoop { } mBluetoothService.setRemoteDeviceProperty(address, name, uuid); } - } - private void onRequestPinCode(String objectPath, int nativeData) { + private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { String address = mBluetoothService.getAddressFromObjectPath(objectPath); if (address == null) { - Log.e(TAG, "Unable to get device address in onRequestPinCode, returning null"); - return; + Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + + "returning null"); + return null; } address = address.toUpperCase(); mPasskeyAgentRequestData.put(address, new Integer(nativeData)); if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) { // shutdown path - mBluetoothService.cancelPin(address); - return; + mBluetoothService.cancelPairingUserInput(address); + return null; } + return address; + } + + private void onRequestConfirmation(String objectPath, int passkey, int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); + intent.putExtra(BluetoothIntent.ADDRESS, address); + intent.putExtra(BluetoothIntent.PASSKEY, passkey); + intent.putExtra(BluetoothIntent.PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_CONFIRMATION); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + return; + } + + private void onRequestPasskey(String objectPath, int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); + intent.putExtra(BluetoothIntent.ADDRESS, address); + intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PASSKEY); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + return; + } + + private void onRequestPinCode(String objectPath, int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDING) { @@ -358,6 +388,7 @@ class BluetoothEventLoop { } Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); + intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); return; } @@ -371,9 +402,7 @@ class BluetoothEventLoop { boolean authorized = false; UUID uuid = UUID.fromString(deviceUuid); - if (mBluetoothService.isEnabled() && (BluetoothUuid.isAudioSink(uuid) || - BluetoothUuid.isAudioSource(uuid) || - BluetoothUuid.isAdvAudioDist(uuid))) { + if (mBluetoothService.isEnabled() && BluetoothUuid.isAudioSink(uuid)) { BluetoothA2dp a2dp = new BluetoothA2dp(mContext); authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { @@ -388,9 +417,9 @@ class BluetoothEventLoop { } private void onAgentCancel() { - // We immediately response to DBUS Authorize() so this should not - // usually happen - log("onAgentCancel"); + Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + return; } private void onRestartRequired() { diff --git a/core/java/android/server/search/SearchDialogWrapper.java b/core/java/android/server/search/SearchDialogWrapper.java index 67be6a663514..d3ef5de8634f 100644 --- a/core/java/android/server/search/SearchDialogWrapper.java +++ b/core/java/android/server/search/SearchDialogWrapper.java @@ -45,11 +45,9 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { private static final String TAG = "SearchManagerService"; private static final boolean DBG = false; - private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; - private static final String SEARCH_UI_THREAD_NAME = "SearchDialog"; private static final int SEARCH_UI_THREAD_PRIORITY = - android.os.Process.THREAD_PRIORITY_FOREGROUND; + android.os.Process.THREAD_PRIORITY_DEFAULT; // Takes no arguments private static final int MSG_INIT = 0; @@ -88,12 +86,11 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { // Identity of currently resumed activity. private int mResumedIdent = 0; - - // Allows disabling of search dialog for stress testing runs - private final boolean mDisabledOnBoot; // True if we have registered our receivers. private boolean mReceiverRegistered; + + private volatile boolean mVisible = false; /** * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will @@ -104,8 +101,6 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { public SearchDialogWrapper(Context context) { mContext = context; - mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY)); - // Create the search UI thread HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY); t.start(); @@ -115,6 +110,10 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { mSearchUiThread.sendEmptyMessage(MSG_INIT); } + public boolean isVisible() { + return mVisible; + } + /** * Initializes the search UI. * Must be called from the search UI thread. @@ -151,8 +150,10 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - performStopSearch(); + if (!"search".equals(intent.getStringExtra("reason"))) { + if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + performStopSearch(); + } } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED); performOnConfigurationChanged(); @@ -205,7 +206,7 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { * Can be called from any thread. */ public void activityResuming(int ident) { - if (DBG) debug("startSearch()"); + if (DBG) debug("activityResuming(ident=" + ident + ")"); Message msg = Message.obtain(); msg.what = MSG_ACTIVITY_RESUMING; msg.arg1 = ident; @@ -256,20 +257,6 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { } - void updateDialogVisibility() { - if (mStartedIdent != 0) { - // mResumedIdent == 0 means we have just booted and the user - // hasn't yet gone anywhere. - if (mResumedIdent == 0 || mStartedIdent == mResumedIdent) { - if (DBG) Log.v(TAG, "******************* DIALOG: show"); - mSearchDialog.show(); - } else { - if (DBG) Log.v(TAG, "******************* DIALOG: hide"); - mSearchDialog.hide(); - } - } - } - /** * Actually launches the search UI. * This must be called on the search UI thread. @@ -283,19 +270,20 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { int ident) { if (DBG) debug("performStartSearch()"); - if (mDisabledOnBoot) { - Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY - + " system property is set."); - return; - } - registerBroadcastReceiver(); mCallback = searchManagerCallback; + + // clean up any hidden dialog that we were waiting to resume + if (mStartedIdent != 0) { + mSearchDialog.dismiss(); + } + mStartedIdent = ident; if (DBG) Log.v(TAG, "******************* DIALOG: start"); + mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, globalSearch); - updateDialogVisibility(); + mVisible = true; } /** @@ -306,6 +294,7 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { if (DBG) debug("performStopSearch()"); if (DBG) Log.v(TAG, "******************* DIALOG: cancel"); mSearchDialog.cancel(); + mVisible = false; mStartedIdent = 0; } @@ -317,7 +306,21 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { if (DBG) debug("performResumingActivity(): mStartedIdent=" + mStartedIdent + ", resuming: " + ident); this.mResumedIdent = ident; - updateDialogVisibility(); + if (mStartedIdent != 0) { + if (mStartedIdent == mResumedIdent) { + // we are resuming into the activity where we previously hid the dialog, bring it + // back + if (DBG) Log.v(TAG, "******************* DIALOG: show"); + mSearchDialog.show(); + mVisible = true; + } else { + // resuming into some other activity; hide ourselves in case we ever come back + // so we can show ourselves quickly again + if (DBG) Log.v(TAG, "******************* DIALOG: hide"); + mSearchDialog.hide(); + mVisible = false; + } + } } /** @@ -333,27 +336,38 @@ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { */ public void onDismiss(DialogInterface dialog) { if (DBG) debug("onDismiss()"); - if (mCallback != null) { - try { - // should be safe to do on the search UI thread, since it's a oneway interface - mCallback.onDismiss(); - } catch (DeadObjectException ex) { - // The process that hosted the callback has died, do nothing - } catch (RemoteException ex) { - Log.e(TAG, "onDismiss() failed: " + ex); - } - // we don't need the callback anymore, release it - mCallback = null; - } + mStartedIdent = 0; + mVisible = false; + callOnDismiss(); + + // we don't need the callback anymore, release it + mCallback = null; unregisterBroadcastReceiver(); } + /** * Called by {@link SearchDialog} when the user or activity cancels search. * Whenever this method is called, {@link #onDismiss} is always called afterwards. */ public void onCancel(DialogInterface dialog) { if (DBG) debug("onCancel()"); + callOnCancel(); + } + + private void callOnDismiss() { + if (mCallback == null) return; + try { + // should be safe to do on the search UI thread, since it's a oneway interface + mCallback.onDismiss(); + } catch (DeadObjectException ex) { + // The process that hosted the callback has died, do nothing + } catch (RemoteException ex) { + Log.e(TAG, "onDismiss() failed: " + ex); + } + } + + private void callOnCancel() { if (mCallback != null) { try { // should be safe to do on the search UI thread, since it's a oneway interface diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 762991260dad..fdeb8f9e3fc2 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -238,4 +238,8 @@ public class SearchManagerService extends ISearchManager.Stub { getSearchDialog().stopSearch(); } + public boolean isVisible() { + return mSearchDialog != null && mSearchDialog.isVisible(); + } + } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index a2e70b8193f7..bb6b4b0385d8 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -113,43 +113,131 @@ public class TextToSpeech { /** * Internal constants for the TTS functionality * - * {@hide} */ public class Engine { // default values for a TTS engine when settings are not found in the provider + /** + * {@hide} + */ public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x + /** + * {@hide} + */ public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x + /** + * {@hide} + */ public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false + /** + * {@hide} + */ public static final String FALLBACK_TTS_DEFAULT_SYNTH = "com.svox.pico"; // default values for rendering public static final int TTS_DEFAULT_STREAM = AudioManager.STREAM_MUSIC; // return codes for a TTS engine's check data activity + /** + * Indicates success when checking the installation status of the resources used by the + * text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_PASS = 1; + /** + * Indicates failure when checking the installation status of the resources used by the + * text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_FAIL = 0; + /** + * Indicates erroneous data when checking the installation status of the resources used by + * the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_BAD_DATA = -1; + /** + * Indicates missing resources when checking the installation status of the resources used + * by the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; - public static final int CHECK_VOICE_DATA_MISSING_DATA_NO_SDCARD = -3; + /** + * Indicates missing storage volume when checking the installation status of the resources + * used by the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ + public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3; // return codes for a TTS engine's check data activity + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the path to its resources. + */ public static final String VOICE_DATA_ROOT_DIRECTORY = "dataRoot"; + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the file names of its resources under the + * resource path. + */ public static final String VOICE_DATA_FILES = "dataFiles"; + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the locale associated with each resource file. + */ public static final String VOICE_DATA_FILES_INFO = "dataFilesInfo"; - // keys for the parameters passed with speak commands + // keys for the parameters passed with speak commands. Hidden keys are used internally + // to maintain engine state for each TextToSpeech instance. + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_RATE = "rate"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_LANGUAGE = "language"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_COUNTRY = "country"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_VARIANT = "variant"; + /** + * Parameter key to specify the audio stream type to be used when speaking text + * or playing back a file. + */ public static final String TTS_KEY_PARAM_STREAM = "streamType"; + /** + * Parameter key to identify an utterance in the completion listener after text has been + * spoken, a file has been played back or a silence duration has elapsed. + */ public static final String TTS_KEY_PARAM_UTTERANCE_ID = "utteranceId"; + + // key positions in the array of cached parameters + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_RATE = 0; + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_LANGUAGE = 2; + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_COUNTRY = 4; + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_VARIANT = 6; + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_STREAM = 8; + /** + * {@hide} + */ protected static final int TTS_PARAM_POSITION_UTTERANCE_ID = 10; + /** + * {@hide} + */ protected static final int TTS_NB_CACHED_PARAMS = 6; } @@ -362,6 +450,109 @@ public class TextToSpeech { /** + * Adds a mapping between a string of text and a sound resource in a + * package. + * + * @see #TTS.playEarcon(String earcon, int queueMode, String[] params) + * + * @param earcon The name of the earcon + * Example: <b><code>"[tick]"</code></b><br/> + * + * @param packagename + * Pass the packagename of the application that contains the + * resource. If the resource is in your own application (this is + * the most common case), then put the packagename of your + * application here.<br/> + * Example: <b>"com.google.marvin.compass"</b><br/> + * The packagename can be found in the AndroidManifest.xml of + * your application. + * <p> + * <code><manifest xmlns:android="..." + * package="<b>com.google.marvin.compass</b>"></code> + * </p> + * + * @param resourceId + * Example: <b><code>R.raw.tick_snd</code></b> + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int addEarcon(String earcon, String packagename, int resourceId) { + synchronized(mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + mITts.addEarcon(mPackageName, earcon, packagename, resourceId); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "IllegalStateException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } + return TTS_ERROR; + } + } + + + /** + * Adds a mapping between a string of text and a sound file. Using this, it + * is possible to add custom earcons. + * + * @param earcon + * The name of the earcon + * @param filename + * The full path to the sound file (for example: + * "/sdcard/mysounds/tick.wav") + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int addEarcon(String earcon, String filename) { + synchronized (mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + mITts.addEarconFile(mPackageName, earcon, filename); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "IllegalStateException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } + return TTS_ERROR; + } + } + + + /** * Speaks the string using the specified queuing strategy and speech * parameters. Note that the speech parameters are not universally supported * by all engines and will be treated as a hint. The TTS library will try to @@ -629,14 +820,14 @@ public class TextToSpeech { if (speechRate > 0) { int rate = (int)(speechRate*100); mCachedParams[Engine.TTS_PARAM_POSITION_RATE + 1] = String.valueOf(rate); - result = mITts.setSpeechRate(mPackageName, rate); + // the rate is not set here, instead it is cached so it will be associated + // with all upcoming utterances. + if (speechRate > 0.0f) { + result = TTS_SUCCESS; + } else { + result = TTS_ERROR; + } } - } catch (RemoteException e) { - // TTS died; restart it. - Log.e("TextToSpeech.java - setSpeechRate", "RemoteException"); - e.printStackTrace(); - mStarted = false; - initTts(); } catch (NullPointerException e) { // TTS died; restart it. Log.e("TextToSpeech.java - setSpeechRate", "NullPointerException"); @@ -716,7 +907,9 @@ public class TextToSpeech { * @param loc * The locale describing the language to be used. * - * @return Code indicating the support status for the locale. See the TTS_LANG_ codes. + * @return code indicating the support status for the locale. See {@link #TTS_LANG_AVAILABLE}, + * {@link #TTS_LANG_COUNTRY_AVAILABLE}, {@link #TTS_LANG_COUNTRY_VAR_AVAILABLE}, + * {@link #TTS_LANG_MISSING_DATA} and {@link #TTS_LANG_NOT_SUPPORTED}. */ public int setLanguage(Locale loc) { synchronized (mStartLock) { @@ -728,6 +921,7 @@ public class TextToSpeech { mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1] = loc.getISO3Language(); mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1] = loc.getISO3Country(); mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] = loc.getVariant(); + result = mITts.setLanguage(mPackageName, mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1], mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1], @@ -803,8 +997,9 @@ public class TextToSpeech { * @param loc * The Locale describing the language to be used. * - * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, - * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE. + * @return code indicating the support status for the locale. See {@link #TTS_LANG_AVAILABLE}, + * {@link #TTS_LANG_COUNTRY_AVAILABLE}, {@link #TTS_LANG_COUNTRY_VAR_AVAILABLE}, + * {@link #TTS_LANG_MISSING_DATA} and {@link #TTS_LANG_NOT_SUPPORTED}. */ public int isLanguageAvailable(Locale loc) { synchronized (mStartLock) { diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 70e12970e6c2..380e5fd8d197 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -25,6 +25,7 @@ import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -40,6 +41,7 @@ import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.SubscriptSpan; import android.text.style.SuperscriptSpan; +import android.text.style.TextAppearanceSpan; import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; @@ -49,6 +51,7 @@ import com.android.internal.util.XmlUtils; import java.io.IOException; import java.io.StringReader; import java.nio.CharBuffer; +import java.util.HashMap; /** * This class processes HTML strings into displayable styled text. @@ -633,54 +636,25 @@ class HtmlToSpannedConverter implements ContentHandler { if (where != len) { Font f = (Font) obj; - if (f.mColor != null) { - int c = -1; - - if (f.mColor.equalsIgnoreCase("aqua")) { - c = 0x00FFFF; - } else if (f.mColor.equalsIgnoreCase("black")) { - c = 0x000000; - } else if (f.mColor.equalsIgnoreCase("blue")) { - c = 0x0000FF; - } else if (f.mColor.equalsIgnoreCase("fuchsia")) { - c = 0xFF00FF; - } else if (f.mColor.equalsIgnoreCase("green")) { - c = 0x008000; - } else if (f.mColor.equalsIgnoreCase("grey")) { - c = 0x808080; - } else if (f.mColor.equalsIgnoreCase("lime")) { - c = 0x00FF00; - } else if (f.mColor.equalsIgnoreCase("maroon")) { - c = 0x800000; - } else if (f.mColor.equalsIgnoreCase("navy")) { - c = 0x000080; - } else if (f.mColor.equalsIgnoreCase("olive")) { - c = 0x808000; - } else if (f.mColor.equalsIgnoreCase("purple")) { - c = 0x800080; - } else if (f.mColor.equalsIgnoreCase("red")) { - c = 0xFF0000; - } else if (f.mColor.equalsIgnoreCase("silver")) { - c = 0xC0C0C0; - } else if (f.mColor.equalsIgnoreCase("teal")) { - c = 0x008080; - } else if (f.mColor.equalsIgnoreCase("white")) { - c = 0xFFFFFF; - } else if (f.mColor.equalsIgnoreCase("yellow")) { - c = 0xFFFF00; + if (!TextUtils.isEmpty(f.mColor)) { + if (f.mColor.startsWith("@")) { + Resources res = Resources.getSystem(); + String name = f.mColor.substring(1); + int colorRes = res.getIdentifier(name, "color", "android"); + if (colorRes != 0) { + ColorStateList colors = res.getColorStateList(colorRes); + text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null), + where, len, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } else { - try { - c = XmlUtils.convertValueToInt(f.mColor, -1); - } catch (NumberFormatException nfe) { - // Can't understand the color, so just drop it. + int c = getHtmlColor(f.mColor); + if (c != -1) { + text.setSpan(new ForegroundColorSpan(c | 0xFF000000), + where, len, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } - - if (c != -1) { - text.setSpan(new ForegroundColorSpan(c | 0xFF000000), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } } if (f.mFace != null) { @@ -843,4 +817,47 @@ class HtmlToSpannedConverter implements ContentHandler { mLevel = level; } } + + private static HashMap<String,Integer> COLORS = buildColorMap(); + + private static HashMap<String,Integer> buildColorMap() { + HashMap<String,Integer> map = new HashMap<String,Integer>(); + map.put("aqua", 0x00FFFF); + map.put("black", 0x000000); + map.put("blue", 0x0000FF); + map.put("fuchsia", 0xFF00FF); + map.put("green", 0x008000); + map.put("grey", 0x808080); + map.put("lime", 0x00FF00); + map.put("maroon", 0x800000); + map.put("navy", 0x000080); + map.put("olive", 0x808000); + map.put("purple", 0x800080); + map.put("red", 0xFF0000); + map.put("silver", 0xC0C0C0); + map.put("teal", 0x008080); + map.put("white", 0xFFFFFF); + map.put("yellow", 0xFFFF00); + return map; + } + + /** + * Converts an HTML color (named or numeric) to an integer RGB value. + * + * @param color Non-null color string. + * @return A color value, or {@code -1} if the color string could not be interpreted. + */ + private static int getHtmlColor(String color) { + Integer i = COLORS.get(color.toLowerCase()); + if (i != null) { + return i; + } else { + try { + return XmlUtils.convertValueToInt(color, -1); + } catch (NumberFormatException nfe) { + return -1; + } + } + } + } diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 9071bf029f9e..bfab49d60fa1 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -27,17 +27,31 @@ import android.os.*; */ public class DisplayMetrics { /** + * Standard quantized DPI for low-density screens. + */ + public static final int DENSITY_LOW = 120; + + /** + * Standard quantized DPI for medium-density screens. + */ + public static final int DENSITY_MEDIUM = 160; + + /** + * Standard quantized DPI for high-density screens. + */ + public static final int DENSITY_HIGH = 240; + + /** * The reference density used throughout the system. - * - * @hide Pending API council approval */ - public static final int DEFAULT_DENSITY = 160; + public static final int DENSITY_DEFAULT = DENSITY_MEDIUM; /** * The device's density. - * @hide + * @hide becase eventually this should be able to change while + * running, so shouldn't be a constant. */ - public static final int DEVICE_DENSITY = getDeviceDensity(); + public static final int DENSITY_DEVICE = getDeviceDensity(); /** * The absolute width of the display in pixels. @@ -62,7 +76,7 @@ public class DisplayMetrics { * 320x480 but the screen size remained 1.5"x2" then the density would be * increased (probably to 1.5). * - * @see #DEFAULT_DENSITY + * @see #DENSITY_DEFAULT */ public float density; /** @@ -95,10 +109,10 @@ public class DisplayMetrics { public void setToDefaults() { widthPixels = 0; heightPixels = 0; - density = DEVICE_DENSITY / (float) DEFAULT_DENSITY; + density = DENSITY_DEVICE / (float) DENSITY_DEFAULT; scaledDensity = density; - xdpi = DEVICE_DENSITY; - ydpi = DEVICE_DENSITY; + xdpi = DENSITY_DEVICE; + ydpi = DENSITY_DEVICE; } /** @@ -176,6 +190,6 @@ public class DisplayMetrics { // The reason for this is that ro.sf.lcd_density is write-once and is // set by the init process when it parses build.prop before anything else. return SystemProperties.getInt("qemu.sf.lcd_density", - SystemProperties.getInt("ro.sf.lcd_density", DEFAULT_DENSITY)); + SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT)); } } diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java index d4ba9e21ac13..ed4529851d8f 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -140,12 +140,16 @@ public class TypedValue { /** * If {@link #density} is equal to this value, then the density should be - * treated as the system's default density value: {@link DisplayMetrics#DEFAULT_DENSITY}. - * - * @hide Pending API council approval + * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. */ public static final int DENSITY_DEFAULT = 0; + /** + * If {@link #density} is equal to this value, then there is no density + * associated with the resource and it should not be scaled. + */ + public static final int DENSITY_NONE = 0xffff; + /* ------------------------------------------------------------ */ /** The type held by this value, as defined by the constants here. @@ -171,8 +175,6 @@ public class TypedValue { /** * If the Value came from a resource, this holds the corresponding pixel density. - * - * @hide Pending API council approval * */ public int density; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 09ebeed543e1..5551f64bcde3 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -117,5 +117,32 @@ public class Display private static final Object mStaticInit = new Object(); private static boolean mInitialized = false; + + /** + * Returns a display object which uses the metric's width/height instead. + * @hide + */ + public static Display createMetricsBasedDisplay(int displayId, DisplayMetrics metrics) { + return new CompatibleDisplay(displayId, metrics); + } + + private static class CompatibleDisplay extends Display { + private final DisplayMetrics mMetrics; + + private CompatibleDisplay(int displayId, DisplayMetrics metrics) { + super(displayId); + mMetrics = metrics; + } + + @Override + public int getWidth() { + return mMetrics.widthPixels; + } + + @Override + public int getHeight() { + return mMetrics.heightPixels; + } + } } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 0e37b2665b9b..c41ee324945e 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -396,7 +396,6 @@ public final class MotionEvent implements Parcelable { } /** -<<<<<<< HEAD:core/java/android/view/MotionEvent.java * Returns the time (in ns) when this specific event was generated. * The value is in nanosecond precision but it may not have nanosecond accuracy. * @@ -409,13 +408,6 @@ public final class MotionEvent implements Parcelable { /** * Returns the X coordinate of this event. Whole numbers are pixels; the * value may have a fraction for input devices that are sub-pixel precise. -||||||| - * Returns the X coordinate of this event. Whole numbers are pixels; the - * value may have a fraction for input devices that are sub-pixel precise. -======= - * Returns the X coordinate of this event. Whole numbers are pixels; the - * value may have a fraction for input devices that are sub-pixel precise. ->>>>>>> cafdea61a85c8f5d0646cc9413a09346c637f43f:core/java/android/view/MotionEvent.java */ public final float getX() { return mX; diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 13a6e7aacf19..25bbb6a3bf5d 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -17,7 +17,6 @@ package android.view; import android.content.Context; -import android.content.res.CompatibilityInfo; import android.content.res.CompatibilityInfo.Translator; import android.graphics.Canvas; import android.graphics.PixelFormat; @@ -257,9 +256,9 @@ public class SurfaceView extends View { public boolean dispatchTouchEvent(MotionEvent event) { // SurfaceView uses pre-scaled size unless fixed size is requested. This hook // scales the event back to the pre-scaled coordinates for such surface. - if (mRequestedWidth < 0 && mTranslator != null) { + if (mScaled) { MotionEvent scaledBack = MotionEvent.obtain(event); - scaledBack.scale(mTranslator.applicationScale); + mTranslator.translateEventInScreenToAppWindow(event); try { return super.dispatchTouchEvent(scaledBack); } finally { @@ -291,12 +290,15 @@ public class SurfaceView extends View { public void setWindowType(int type) { mWindowType = type; } + + boolean mScaled = false; private void updateWindow(boolean force) { if (!mHaveFrame) { return; } - mTranslator = ((ViewRoot)getRootView().getParent()).mTranslator; + ViewRoot viewRoot = (ViewRoot) getRootView().getParent(); + mTranslator = viewRoot.mTranslator; float appScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; @@ -310,6 +312,9 @@ public class SurfaceView extends View { if (mRequestedWidth <= 0 && mTranslator != null) { myWidth *= appScale; myHeight *= appScale; + mScaled = true; + } else { + mScaled = false; } getLocationInWindow(mLocation); @@ -353,8 +358,10 @@ public class SurfaceView extends View { | WindowManager.LayoutParams.FLAG_SCALED | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING ; + if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) { + mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + } mLayout.memoryType = mRequestedType; @@ -534,6 +541,7 @@ public class SurfaceView extends View { private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; + private int mSaveCount; public boolean isCreating() { return mIsCreating; @@ -628,6 +636,10 @@ public class SurfaceView extends View { if (localLOGV) Log.i(TAG, "Returned canvas: " + c); if (c != null) { mLastLockTime = SystemClock.uptimeMillis(); + if (mScaled) { + mSaveCount = c.save(); + mTranslator.translateCanvas(c); + } return c; } @@ -650,6 +662,9 @@ public class SurfaceView extends View { } public void unlockCanvasAndPost(Canvas canvas) { + if (mScaled) { + canvas.restoreToCount(mSaveCount); + } mSurface.unlockCanvasAndPost(canvas); mSurfaceLock.unlock(); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 1e3cdb34f436..2acf79081306 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -504,8 +504,12 @@ public final class ViewRoot extends Handler implements ViewParent, void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { synchronized (this) { int oldSoftInputMode = mWindowAttributes.softInputMode; + // preserve compatible window flag if exists. + int compatibleWindowFlag = + mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; mWindowAttributes.copyFrom(attrs); - + mWindowAttributes.flags |= compatibleWindowFlag; + if (newView) { mSoftInputMode = attrs.softInputMode; requestLayout(); @@ -1308,7 +1312,8 @@ public final class ViewRoot extends Handler implements ViewParent, if (DEBUG_DRAW) { Context cxt = mView.getContext(); Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + mView.getContext().getResources().getDisplayMetrics()); + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2c32d8b54af6..02e0515d6f28 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -358,6 +358,8 @@ public abstract class Window { private class LocalWindowManager implements WindowManager { LocalWindowManager(WindowManager wm) { mWindowManager = wm; + mDefaultDisplay = mContext.getResources().getDefaultDisplay( + mWindowManager.getDefaultDisplay()); } public final void addView(View view, ViewGroup.LayoutParams params) { @@ -420,10 +422,12 @@ public abstract class Window { } public Display getDefaultDisplay() { - return mWindowManager.getDefaultDisplay(); + return mDefaultDisplay; } - WindowManager mWindowManager; + private final WindowManager mWindowManager; + + private final Display mDefaultDisplay; } /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e96a15ba05f2..6a26a3144128 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -483,19 +483,12 @@ public interface WindowManager extends ViewManager { * {@hide} */ public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000; - /** Window flag: special flag to let a window ignore the compatibility scaling. - * This is used by SurfaceView to pass this info into ViewRoot, and not used - * by WindowManager. - * - * {@hide} */ - public static final int FLAG_NO_COMPATIBILITY_SCALING = 0x00100000; - /** Window flag: special flag to limit the size of the window to be * original size ([320x480] x density). Used to create window for applications * running under compatibility mode. * * {@hide} */ - public static final int FLAG_COMPATIBLE_WINDOW = 0x00200000; + public static final int FLAG_COMPATIBLE_WINDOW = 0x00100000; /** Window flag: a special option intended for system dialogs. When * this flag is set, the window will demand focus unconditionally when @@ -986,6 +979,9 @@ public interface WindowManager extends ViewManager { sb.append(" or="); sb.append(screenOrientation); } + if ((flags & FLAG_COMPATIBLE_WINDOW) != 0) { + sb.append(" compatible=true"); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index e04ae720a6da..e6ccd70ab74c 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -465,8 +465,6 @@ class BrowserFrame extends Handler { * @param postData If the method is "POST" postData is sent as the request * body. Is null when empty. * @param cacheMode The cache mode to use when loading this resource. - * @param isHighPriority True if this resource needs to be put at the front - * of the network queue. * @param synchronous True if the load is synchronous. * @return A newly created LoadListener object. */ @@ -476,7 +474,6 @@ class BrowserFrame extends Handler { HashMap headers, byte[] postData, int cacheMode, - boolean isHighPriority, boolean synchronous) { PerfChecker checker = new PerfChecker(); @@ -542,8 +539,8 @@ class BrowserFrame extends Handler { if (DebugFlags.BROWSER_FRAME) { Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" - + method + ", postData=" + postData + ", isHighPriority=" - + isHighPriority + ", isMainFramePage=" + isMainFramePage); + + method + ", postData=" + postData + ", isMainFramePage=" + + isMainFramePage); } // Create a LoadListener @@ -553,12 +550,14 @@ class BrowserFrame extends Handler { mCallbackProxy.onLoadResource(url); if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { + // send an error message, so that loadListener can be deleted + // after this is returned. This is important as LoadListener's + // nativeError will remove the request from its DocLoader's request + // list. But the set up is not done until this method is returned. loadListener.error( android.net.http.EventHandler.ERROR, mContext.getString( com.android.internal.R.string.httpErrorTooManyRequests)); - loadListener.notifyError(); - loadListener.tearDown(); - return null; + return loadListener; } // during synchronous load, the WebViewCore thread is blocked, so we @@ -568,8 +567,7 @@ class BrowserFrame extends Handler { CacheManager.endCacheTransaction(); } - FrameLoader loader = new FrameLoader(loadListener, mSettings, - method, isHighPriority); + FrameLoader loader = new FrameLoader(loadListener, mSettings, method); loader.setHeaders(headers); loader.setPostData(postData); // Set the load mode to the mode used for the current page. diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java index f98c5d38dfd1..829872962921 100644 --- a/core/java/android/webkit/FrameLoader.java +++ b/core/java/android/webkit/FrameLoader.java @@ -28,7 +28,6 @@ class FrameLoader { private final LoadListener mListener; private final String mMethod; - private final boolean mIsHighPriority; private final WebSettings mSettings; private Map<String, String> mHeaders; private byte[] mPostData; @@ -52,11 +51,10 @@ class FrameLoader { private static final String LOGTAG = "webkit"; FrameLoader(LoadListener listener, WebSettings settings, - String method, boolean highPriority) { + String method) { mListener = listener; mHeaders = null; mMethod = method; - mIsHighPriority = highPriority; mCacheMode = WebSettings.LOAD_NORMAL; mSettings = settings; } @@ -175,8 +173,7 @@ class FrameLoader { // as response from the cache could be a redirect // and we may need to initiate a network request if the cache // can't satisfy redirect URL - mListener.setRequestData(mMethod, mHeaders, mPostData, - mIsHighPriority); + mListener.setRequestData(mMethod, mHeaders, mPostData); return true; } @@ -190,7 +187,7 @@ class FrameLoader { try { ret = mNetwork.requestURL(mMethod, mHeaders, - mPostData, mListener, mIsHighPriority); + mPostData, mListener); } catch (android.net.ParseException ex) { error = EventHandler.ERROR_BAD_URL; } catch (java.lang.RuntimeException ex) { diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index ea12f3601d4a..4fe4036d3b87 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -38,6 +38,7 @@ import com.android.internal.R; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Vector; import java.util.regex.Pattern; @@ -70,7 +71,12 @@ class LoadListener extends Handler implements EventHandler { private static final int HTTP_NOT_FOUND = 404; private static final int HTTP_PROXY_AUTH = 407; - private static final String CERT_MIMETYPE = "application/x-x509-ca-cert"; + private static HashSet<String> sCertificateMimeTypeMap; + static { + sCertificateMimeTypeMap = new HashSet<String>(); + sCertificateMimeTypeMap.add("application/x-x509-ca-cert"); + sCertificateMimeTypeMap.add("application/x-pkcs12"); + } private static int sNativeLoaderCount; @@ -104,7 +110,6 @@ class LoadListener extends Handler implements EventHandler { private String mMethod; private Map<String, String> mRequestHeaders; private byte[] mPostData; - private boolean mIsHighPriority; // Flag to indicate that this load is synchronous. private boolean mSynchronous; private Vector<Message> mMessageQueue; @@ -312,7 +317,17 @@ class LoadListener extends Handler implements EventHandler { if (mMimeType.equals("text/plain") || mMimeType.equals("application/octet-stream")) { - String newMimeType = guessMimeTypeFromExtension(); + // for attachment, use the filename in the Content-Disposition + // to guess the mimetype + String contentDisposition = headers.getContentDisposition(); + String url = null; + if (contentDisposition != null) { + url = URLUtil.parseContentDisposition(contentDisposition); + } + if (url == null) { + url = mUrl; + } + String newMimeType = guessMimeTypeFromExtension(url); if (newMimeType != null) { mMimeType = newMimeType; } @@ -775,14 +790,12 @@ class LoadListener extends Handler implements EventHandler { * @param method * @param headers * @param postData - * @param isHighPriority */ void setRequestData(String method, Map<String, String> headers, - byte[] postData, boolean isHighPriority) { + byte[] postData) { mMethod = method; mRequestHeaders = headers; mPostData = postData; - mIsHighPriority = isHighPriority; } /** @@ -921,7 +934,7 @@ class LoadListener extends Handler implements EventHandler { // This commits the headers without checking the response status code. private void commitHeaders() { - if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) { + if (mIsMainPageLoader && sCertificateMimeTypeMap.contains(mMimeType)) { // In the case of downloading certificate, we will save it to the // Keystore in commitLoad. Do not call webcore. return; @@ -966,7 +979,7 @@ class LoadListener extends Handler implements EventHandler { private void commitLoad() { if (mCancelled) return; - if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) { + if (mIsMainPageLoader && sCertificateMimeTypeMap.contains(mMimeType)) { // In the case of downloading certificate, we will save it to the // Keystore and stop the current loading so that it will not // generate a new history page @@ -1178,7 +1191,7 @@ class LoadListener extends Handler implements EventHandler { // Network.requestURL. Network network = Network.getInstance(getContext()); if (!network.requestURL(mMethod, mRequestHeaders, - mPostData, this, mIsHighPriority)) { + mPostData, this)) { // Signal a bad url error if we could not load the // redirection. handleError(EventHandler.ERROR_BAD_URL, @@ -1357,7 +1370,8 @@ class LoadListener extends Handler implements EventHandler { */ private boolean ignoreCallbacks() { return (mCancelled || mAuthHeader != null || - (mStatusCode > 300 && mStatusCode < 400)); + // Allow 305 (Use Proxy) to call through. + (mStatusCode > 300 && mStatusCode < 400 && mStatusCode != 305)); } /** @@ -1396,7 +1410,7 @@ class LoadListener extends Handler implements EventHandler { // of frames. If no content-type was specified, it is fine to // default to text/html. mMimeType = "text/html"; - String newMimeType = guessMimeTypeFromExtension(); + String newMimeType = guessMimeTypeFromExtension(mUrl); if (newMimeType != null) { mMimeType = newMimeType; } @@ -1406,14 +1420,14 @@ class LoadListener extends Handler implements EventHandler { /** * guess MIME type based on the file extension. */ - private String guessMimeTypeFromExtension() { + private String guessMimeTypeFromExtension(String url) { // PENDING: need to normalize url if (DebugFlags.LOAD_LISTENER) { - Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl); + Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url); } return MimeTypeMap.getSingleton().getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(mUrl)); + MimeTypeMap.getFileExtensionFromUrl(url)); } /** diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index 096f38add65b..b2aa524ede6d 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -22,7 +22,7 @@ import java.util.regex.Pattern; /** * Two-way map that maps MIME-types to file extensions and vice versa. */ -public /* package */ class MimeTypeMap { +public class MimeTypeMap { /** * Singleton MIME-type map instance: @@ -39,7 +39,6 @@ public /* package */ class MimeTypeMap { */ private HashMap<String, String> mExtensionToMimeTypeMap; - /** * Creates a new MIME-type map. */ @@ -50,7 +49,10 @@ public /* package */ class MimeTypeMap { /** * Returns the file extension or an empty string iff there is no - * extension. + * extension. This method is a convenience method for obtaining the + * extension of a url and has undefined results for other Strings. + * @param url + * @return The file extension of the given url. */ public static String getFileExtensionFromUrl(String url) { if (url != null && url.length() > 0) { @@ -80,8 +82,7 @@ public /* package */ class MimeTypeMap { * Load an entry into the map. This does not check if the item already * exists, it trusts the caller! */ - private void loadEntry(String mimeType, String extension, - boolean textType) { + private void loadEntry(String mimeType, String extension) { // // if we have an existing x --> y mapping, we do not want to // override it with another mapping x --> ? @@ -94,18 +95,12 @@ public /* package */ class MimeTypeMap { mMimeTypeToExtensionMap.put(mimeType, extension); } - // - // here, we don't want to map extensions to text MIME types; - // otherwise, we will start replacing generic text/plain and - // text/html with text MIME types that our platform does not - // understand. - // - if (!textType) { - mExtensionToMimeTypeMap.put(extension, mimeType); - } + mExtensionToMimeTypeMap.put(extension, mimeType); } /** + * Return true if the given MIME type has an entry in the map. + * @param mimeType A MIME type (i.e. text/plain) * @return True iff there is a mimeType entry in the map. */ public boolean hasMimeType(String mimeType) { @@ -117,7 +112,9 @@ public /* package */ class MimeTypeMap { } /** - * @return The extension for the MIME type or null iff there is none. + * Return the MIME type for the given extension. + * @param extension A file extension without the leading '.' + * @return The MIME type for the given extension or null iff there is none. */ public String getMimeTypeFromExtension(String extension) { if (extension != null && extension.length() > 0) { @@ -128,18 +125,23 @@ public /* package */ class MimeTypeMap { } /** + * Return true if the given extension has a registered MIME type. + * @param extension A file extension without the leading '.' * @return True iff there is an extension entry in the map. */ public boolean hasExtension(String extension) { if (extension != null && extension.length() > 0) { return mExtensionToMimeTypeMap.containsKey(extension); } - return false; } /** - * @return The MIME type for the extension or null iff there is none. + * Return the registered extension for the given MIME type. Note that some + * MIME types map to multiple extensions. This call will return the most + * common extension for the given MIME type. + * @param mimeType A MIME type (i.e. text/plain) + * @return The extension for the given MIME type or null iff there is none. */ public String getExtensionFromMimeType(String mimeType) { if (mimeType != null && mimeType.length() > 0) { @@ -150,6 +152,7 @@ public /* package */ class MimeTypeMap { } /** + * Get the singleton instance of MimeTypeMap. * @return The singleton instance of the MIME-type map. */ public static MimeTypeMap getSingleton() { @@ -164,340 +167,311 @@ public /* package */ class MimeTypeMap { // mail.google.com/a/google.com // // and "active" MIME types (due to potential security issues). - // - // Also, notice that not all data from this table is actually - // added (see loadEntry method for more details). - sMimeTypeMap.loadEntry("application/andrew-inset", "ez", false); - sMimeTypeMap.loadEntry("application/dsptype", "tsp", false); - sMimeTypeMap.loadEntry("application/futuresplash", "spl", false); - sMimeTypeMap.loadEntry("application/hta", "hta", false); - sMimeTypeMap.loadEntry("application/mac-binhex40", "hqx", false); - sMimeTypeMap.loadEntry("application/mac-compactpro", "cpt", false); - sMimeTypeMap.loadEntry("application/mathematica", "nb", false); - sMimeTypeMap.loadEntry("application/msaccess", "mdb", false); - sMimeTypeMap.loadEntry("application/oda", "oda", false); - sMimeTypeMap.loadEntry("application/ogg", "ogg", false); - sMimeTypeMap.loadEntry("application/pdf", "pdf", false); - sMimeTypeMap.loadEntry("application/pgp-keys", "key", false); - sMimeTypeMap.loadEntry("application/pgp-signature", "pgp", false); - sMimeTypeMap.loadEntry("application/pics-rules", "prf", false); - sMimeTypeMap.loadEntry("application/rar", "rar", false); - sMimeTypeMap.loadEntry("application/rdf+xml", "rdf", false); - sMimeTypeMap.loadEntry("application/rss+xml", "rss", false); - sMimeTypeMap.loadEntry("application/zip", "zip", false); + sMimeTypeMap.loadEntry("application/andrew-inset", "ez"); + sMimeTypeMap.loadEntry("application/dsptype", "tsp"); + sMimeTypeMap.loadEntry("application/futuresplash", "spl"); + sMimeTypeMap.loadEntry("application/hta", "hta"); + sMimeTypeMap.loadEntry("application/mac-binhex40", "hqx"); + sMimeTypeMap.loadEntry("application/mac-compactpro", "cpt"); + sMimeTypeMap.loadEntry("application/mathematica", "nb"); + sMimeTypeMap.loadEntry("application/msaccess", "mdb"); + sMimeTypeMap.loadEntry("application/oda", "oda"); + sMimeTypeMap.loadEntry("application/ogg", "ogg"); + sMimeTypeMap.loadEntry("application/pdf", "pdf"); + sMimeTypeMap.loadEntry("application/pgp-keys", "key"); + sMimeTypeMap.loadEntry("application/pgp-signature", "pgp"); + sMimeTypeMap.loadEntry("application/pics-rules", "prf"); + sMimeTypeMap.loadEntry("application/rar", "rar"); + sMimeTypeMap.loadEntry("application/rdf+xml", "rdf"); + sMimeTypeMap.loadEntry("application/rss+xml", "rss"); + sMimeTypeMap.loadEntry("application/zip", "zip"); sMimeTypeMap.loadEntry("application/vnd.android.package-archive", - "apk", false); - sMimeTypeMap.loadEntry("application/vnd.cinderella", "cdy", false); - sMimeTypeMap.loadEntry("application/vnd.ms-pki.stl", "stl", false); + "apk"); + sMimeTypeMap.loadEntry("application/vnd.cinderella", "cdy"); + sMimeTypeMap.loadEntry("application/vnd.ms-pki.stl", "stl"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.database", "odb", - false); + "application/vnd.oasis.opendocument.database", "odb"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.formula", "odf", - false); + "application/vnd.oasis.opendocument.formula", "odf"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.graphics", "odg", - false); + "application/vnd.oasis.opendocument.graphics", "odg"); sMimeTypeMap.loadEntry( "application/vnd.oasis.opendocument.graphics-template", - "otg", false); + "otg"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.image", "odi", false); + "application/vnd.oasis.opendocument.image", "odi"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.spreadsheet", "ods", - false); + "application/vnd.oasis.opendocument.spreadsheet", "ods"); sMimeTypeMap.loadEntry( "application/vnd.oasis.opendocument.spreadsheet-template", - "ots", false); - sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text", "odt", false); + "ots"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-master", "odm", - false); + "application/vnd.oasis.opendocument.text", "odt"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-template", "ott", - false); + "application/vnd.oasis.opendocument.text-master", "odm"); sMimeTypeMap.loadEntry( - "application/vnd.oasis.opendocument.text-web", "oth", - false); - sMimeTypeMap.loadEntry("application/vnd.rim.cod", "cod", false); - sMimeTypeMap.loadEntry("application/vnd.smaf", "mmf", false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.calc", "sdc", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.draw", "sda", - false); + "application/vnd.oasis.opendocument.text-template", "ott"); sMimeTypeMap.loadEntry( - "application/vnd.stardivision.impress", "sdd", false); + "application/vnd.oasis.opendocument.text-web", "oth"); + sMimeTypeMap.loadEntry("application/vnd.rim.cod", "cod"); + sMimeTypeMap.loadEntry("application/vnd.smaf", "mmf"); + sMimeTypeMap.loadEntry("application/vnd.stardivision.calc", "sdc"); + sMimeTypeMap.loadEntry("application/vnd.stardivision.draw", "sda"); sMimeTypeMap.loadEntry( - "application/vnd.stardivision.impress", "sdp", false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.math", "smf", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "sdw", - false); - sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", "vor", - false); + "application/vnd.stardivision.impress", "sdd"); sMimeTypeMap.loadEntry( - "application/vnd.stardivision.writer-global", "sgl", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.calc", "sxc", - false); + "application/vnd.stardivision.impress", "sdp"); + sMimeTypeMap.loadEntry("application/vnd.stardivision.math", "smf"); + sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", + "sdw"); + sMimeTypeMap.loadEntry("application/vnd.stardivision.writer", + "vor"); sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.calc.template", "stc", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.draw", "sxd", - false); + "application/vnd.stardivision.writer-global", "sgl"); + sMimeTypeMap.loadEntry("application/vnd.sun.xml.calc", "sxc"); sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.draw.template", "std", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.impress", "sxi", - false); + "application/vnd.sun.xml.calc.template", "stc"); + sMimeTypeMap.loadEntry("application/vnd.sun.xml.draw", "sxd"); sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.impress.template", "sti", false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.math", "sxm", - false); - sMimeTypeMap.loadEntry("application/vnd.sun.xml.writer", "sxw", - false); + "application/vnd.sun.xml.draw.template", "std"); + sMimeTypeMap.loadEntry("application/vnd.sun.xml.impress", "sxi"); sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.writer.global", "sxg", false); + "application/vnd.sun.xml.impress.template", "sti"); + sMimeTypeMap.loadEntry("application/vnd.sun.xml.math", "sxm"); + sMimeTypeMap.loadEntry("application/vnd.sun.xml.writer", "sxw"); sMimeTypeMap.loadEntry( - "application/vnd.sun.xml.writer.template", "stw", false); - sMimeTypeMap.loadEntry("application/vnd.visio", "vsd", false); - sMimeTypeMap.loadEntry("application/x-abiword", "abw", false); - sMimeTypeMap.loadEntry("application/x-apple-diskimage", "dmg", - false); - sMimeTypeMap.loadEntry("application/x-bcpio", "bcpio", false); - sMimeTypeMap.loadEntry("application/x-bittorrent", "torrent", - false); - sMimeTypeMap.loadEntry("application/x-cdf", "cdf", false); - sMimeTypeMap.loadEntry("application/x-cdlink", "vcd", false); - sMimeTypeMap.loadEntry("application/x-chess-pgn", "pgn", false); - sMimeTypeMap.loadEntry("application/x-cpio", "cpio", false); - sMimeTypeMap.loadEntry("application/x-debian-package", "deb", - false); - sMimeTypeMap.loadEntry("application/x-debian-package", "udeb", - false); - sMimeTypeMap.loadEntry("application/x-director", "dcr", false); - sMimeTypeMap.loadEntry("application/x-director", "dir", false); - sMimeTypeMap.loadEntry("application/x-director", "dxr", false); - sMimeTypeMap.loadEntry("application/x-dms", "dms", false); - sMimeTypeMap.loadEntry("application/x-doom", "wad", false); - sMimeTypeMap.loadEntry("application/x-dvi", "dvi", false); - sMimeTypeMap.loadEntry("application/x-flac", "flac", false); - sMimeTypeMap.loadEntry("application/x-font", "pfa", false); - sMimeTypeMap.loadEntry("application/x-font", "pfb", false); - sMimeTypeMap.loadEntry("application/x-font", "gsf", false); - sMimeTypeMap.loadEntry("application/x-font", "pcf", false); - sMimeTypeMap.loadEntry("application/x-font", "pcf.Z", false); - sMimeTypeMap.loadEntry("application/x-freemind", "mm", false); - sMimeTypeMap.loadEntry("application/x-futuresplash", "spl", false); - sMimeTypeMap.loadEntry("application/x-gnumeric", "gnumeric", false); - sMimeTypeMap.loadEntry("application/x-go-sgf", "sgf", false); - sMimeTypeMap.loadEntry("application/x-graphing-calculator", "gcf", - false); - sMimeTypeMap.loadEntry("application/x-gtar", "gtar", false); - sMimeTypeMap.loadEntry("application/x-gtar", "tgz", false); - sMimeTypeMap.loadEntry("application/x-gtar", "taz", false); - sMimeTypeMap.loadEntry("application/x-hdf", "hdf", false); - sMimeTypeMap.loadEntry("application/x-ica", "ica", false); - sMimeTypeMap.loadEntry("application/x-internet-signup", "ins", - false); - sMimeTypeMap.loadEntry("application/x-internet-signup", "isp", - false); - sMimeTypeMap.loadEntry("application/x-iphone", "iii", false); - sMimeTypeMap.loadEntry("application/x-iso9660-image", "iso", false); - sMimeTypeMap.loadEntry("application/x-jmol", "jmz", false); - sMimeTypeMap.loadEntry("application/x-kchart", "chrt", false); - sMimeTypeMap.loadEntry("application/x-killustrator", "kil", false); - sMimeTypeMap.loadEntry("application/x-koan", "skp", false); - sMimeTypeMap.loadEntry("application/x-koan", "skd", false); - sMimeTypeMap.loadEntry("application/x-koan", "skt", false); - sMimeTypeMap.loadEntry("application/x-koan", "skm", false); - sMimeTypeMap.loadEntry("application/x-kpresenter", "kpr", false); - sMimeTypeMap.loadEntry("application/x-kpresenter", "kpt", false); - sMimeTypeMap.loadEntry("application/x-kspread", "ksp", false); - sMimeTypeMap.loadEntry("application/x-kword", "kwd", false); - sMimeTypeMap.loadEntry("application/x-kword", "kwt", false); - sMimeTypeMap.loadEntry("application/x-latex", "latex", false); - sMimeTypeMap.loadEntry("application/x-lha", "lha", false); - sMimeTypeMap.loadEntry("application/x-lzh", "lzh", false); - sMimeTypeMap.loadEntry("application/x-lzx", "lzx", false); - sMimeTypeMap.loadEntry("application/x-maker", "frm", false); - sMimeTypeMap.loadEntry("application/x-maker", "maker", false); - sMimeTypeMap.loadEntry("application/x-maker", "frame", false); - sMimeTypeMap.loadEntry("application/x-maker", "fb", false); - sMimeTypeMap.loadEntry("application/x-maker", "book", false); - sMimeTypeMap.loadEntry("application/x-maker", "fbdoc", false); - sMimeTypeMap.loadEntry("application/x-mif", "mif", false); - sMimeTypeMap.loadEntry("application/x-ms-wmd", "wmd", false); - sMimeTypeMap.loadEntry("application/x-ms-wmz", "wmz", false); - sMimeTypeMap.loadEntry("application/x-msi", "msi", false); - sMimeTypeMap.loadEntry("application/x-ns-proxy-autoconfig", "pac", - false); - sMimeTypeMap.loadEntry("application/x-nwc", "nwc", false); - sMimeTypeMap.loadEntry("application/x-object", "o", false); - sMimeTypeMap.loadEntry("application/x-oz-application", "oza", - false); - sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r", - false); - sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl", false); - sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl", - false); - sMimeTypeMap.loadEntry("application/x-shar", "shar", false); - sMimeTypeMap.loadEntry("application/x-stuffit", "sit", false); - sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio", false); - sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc", false); - sMimeTypeMap.loadEntry("application/x-tar", "tar", false); - sMimeTypeMap.loadEntry("application/x-texinfo", "texinfo", false); - sMimeTypeMap.loadEntry("application/x-texinfo", "texi", false); - sMimeTypeMap.loadEntry("application/x-troff", "t", false); - sMimeTypeMap.loadEntry("application/x-troff", "roff", false); - sMimeTypeMap.loadEntry("application/x-troff-man", "man", false); - sMimeTypeMap.loadEntry("application/x-ustar", "ustar", false); - sMimeTypeMap.loadEntry("application/x-wais-source", "src", false); - sMimeTypeMap.loadEntry("application/x-wingz", "wz", false); + "application/vnd.sun.xml.writer.global", "sxg"); sMimeTypeMap.loadEntry( - "application/x-webarchive", "webarchive", false); // added - sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt", false); - sMimeTypeMap.loadEntry("application/x-xcf", "xcf", false); - sMimeTypeMap.loadEntry("application/x-xfig", "fig", false); - sMimeTypeMap.loadEntry("application/xhtml+xml", "xhtml", false); - sMimeTypeMap.loadEntry("audio/basic", "snd", false); - sMimeTypeMap.loadEntry("audio/midi", "mid", false); - sMimeTypeMap.loadEntry("audio/midi", "midi", false); - sMimeTypeMap.loadEntry("audio/midi", "kar", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mpga", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mpega", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mp2", false); - sMimeTypeMap.loadEntry("audio/mpeg", "mp3", false); - sMimeTypeMap.loadEntry("audio/mpeg", "m4a", false); - sMimeTypeMap.loadEntry("audio/mpegurl", "m3u", false); - sMimeTypeMap.loadEntry("audio/prs.sid", "sid", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aif", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aiff", false); - sMimeTypeMap.loadEntry("audio/x-aiff", "aifc", false); - sMimeTypeMap.loadEntry("audio/x-gsm", "gsm", false); - sMimeTypeMap.loadEntry("audio/x-mpegurl", "m3u", false); - sMimeTypeMap.loadEntry("audio/x-ms-wma", "wma", false); - sMimeTypeMap.loadEntry("audio/x-ms-wax", "wax", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ra", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "rm", false); - sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ram", false); - sMimeTypeMap.loadEntry("audio/x-realaudio", "ra", false); - sMimeTypeMap.loadEntry("audio/x-scpls", "pls", false); - sMimeTypeMap.loadEntry("audio/x-sd2", "sd2", false); - sMimeTypeMap.loadEntry("audio/x-wav", "wav", false); - sMimeTypeMap.loadEntry("image/bmp", "bmp", false); // added - sMimeTypeMap.loadEntry("image/gif", "gif", false); - sMimeTypeMap.loadEntry("image/ico", "cur", false); // added - sMimeTypeMap.loadEntry("image/ico", "ico", false); // added - sMimeTypeMap.loadEntry("image/ief", "ief", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpeg", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpg", false); - sMimeTypeMap.loadEntry("image/jpeg", "jpe", false); - sMimeTypeMap.loadEntry("image/pcx", "pcx", false); - sMimeTypeMap.loadEntry("image/png", "png", false); - sMimeTypeMap.loadEntry("image/svg+xml", "svg", false); - sMimeTypeMap.loadEntry("image/svg+xml", "svgz", false); - sMimeTypeMap.loadEntry("image/tiff", "tiff", false); - sMimeTypeMap.loadEntry("image/tiff", "tif", false); - sMimeTypeMap.loadEntry("image/vnd.djvu", "djvu", false); - sMimeTypeMap.loadEntry("image/vnd.djvu", "djv", false); - sMimeTypeMap.loadEntry("image/vnd.wap.wbmp", "wbmp", false); - sMimeTypeMap.loadEntry("image/x-cmu-raster", "ras", false); - sMimeTypeMap.loadEntry("image/x-coreldraw", "cdr", false); - sMimeTypeMap.loadEntry("image/x-coreldrawpattern", "pat", false); - sMimeTypeMap.loadEntry("image/x-coreldrawtemplate", "cdt", false); - sMimeTypeMap.loadEntry("image/x-corelphotopaint", "cpt", false); - sMimeTypeMap.loadEntry("image/x-icon", "ico", false); - sMimeTypeMap.loadEntry("image/x-jg", "art", false); - sMimeTypeMap.loadEntry("image/x-jng", "jng", false); - sMimeTypeMap.loadEntry("image/x-ms-bmp", "bmp", false); - sMimeTypeMap.loadEntry("image/x-photoshop", "psd", false); - sMimeTypeMap.loadEntry("image/x-portable-anymap", "pnm", false); - sMimeTypeMap.loadEntry("image/x-portable-bitmap", "pbm", false); - sMimeTypeMap.loadEntry("image/x-portable-graymap", "pgm", false); - sMimeTypeMap.loadEntry("image/x-portable-pixmap", "ppm", false); - sMimeTypeMap.loadEntry("image/x-rgb", "rgb", false); - sMimeTypeMap.loadEntry("image/x-xbitmap", "xbm", false); - sMimeTypeMap.loadEntry("image/x-xpixmap", "xpm", false); - sMimeTypeMap.loadEntry("image/x-xwindowdump", "xwd", false); - sMimeTypeMap.loadEntry("model/iges", "igs", false); - sMimeTypeMap.loadEntry("model/iges", "iges", false); - sMimeTypeMap.loadEntry("model/mesh", "msh", false); - sMimeTypeMap.loadEntry("model/mesh", "mesh", false); - sMimeTypeMap.loadEntry("model/mesh", "silo", false); - sMimeTypeMap.loadEntry("text/calendar", "ics", true); - sMimeTypeMap.loadEntry("text/calendar", "icz", true); - sMimeTypeMap.loadEntry("text/comma-separated-values", "csv", true); - sMimeTypeMap.loadEntry("text/css", "css", true); - sMimeTypeMap.loadEntry("text/h323", "323", true); - sMimeTypeMap.loadEntry("text/iuls", "uls", true); - sMimeTypeMap.loadEntry("text/mathml", "mml", true); + "application/vnd.sun.xml.writer.template", "stw"); + sMimeTypeMap.loadEntry("application/vnd.visio", "vsd"); + sMimeTypeMap.loadEntry("application/x-abiword", "abw"); + sMimeTypeMap.loadEntry("application/x-apple-diskimage", "dmg"); + sMimeTypeMap.loadEntry("application/x-bcpio", "bcpio"); + sMimeTypeMap.loadEntry("application/x-bittorrent", "torrent"); + sMimeTypeMap.loadEntry("application/x-cdf", "cdf"); + sMimeTypeMap.loadEntry("application/x-cdlink", "vcd"); + sMimeTypeMap.loadEntry("application/x-chess-pgn", "pgn"); + sMimeTypeMap.loadEntry("application/x-cpio", "cpio"); + sMimeTypeMap.loadEntry("application/x-debian-package", "deb"); + sMimeTypeMap.loadEntry("application/x-debian-package", "udeb"); + sMimeTypeMap.loadEntry("application/x-director", "dcr"); + sMimeTypeMap.loadEntry("application/x-director", "dir"); + sMimeTypeMap.loadEntry("application/x-director", "dxr"); + sMimeTypeMap.loadEntry("application/x-dms", "dms"); + sMimeTypeMap.loadEntry("application/x-doom", "wad"); + sMimeTypeMap.loadEntry("application/x-dvi", "dvi"); + sMimeTypeMap.loadEntry("application/x-flac", "flac"); + sMimeTypeMap.loadEntry("application/x-font", "pfa"); + sMimeTypeMap.loadEntry("application/x-font", "pfb"); + sMimeTypeMap.loadEntry("application/x-font", "gsf"); + sMimeTypeMap.loadEntry("application/x-font", "pcf"); + sMimeTypeMap.loadEntry("application/x-font", "pcf.Z"); + sMimeTypeMap.loadEntry("application/x-freemind", "mm"); + sMimeTypeMap.loadEntry("application/x-futuresplash", "spl"); + sMimeTypeMap.loadEntry("application/x-gnumeric", "gnumeric"); + sMimeTypeMap.loadEntry("application/x-go-sgf", "sgf"); + sMimeTypeMap.loadEntry("application/x-graphing-calculator", "gcf"); + sMimeTypeMap.loadEntry("application/x-gtar", "gtar"); + sMimeTypeMap.loadEntry("application/x-gtar", "tgz"); + sMimeTypeMap.loadEntry("application/x-gtar", "taz"); + sMimeTypeMap.loadEntry("application/x-hdf", "hdf"); + sMimeTypeMap.loadEntry("application/x-ica", "ica"); + sMimeTypeMap.loadEntry("application/x-internet-signup", "ins"); + sMimeTypeMap.loadEntry("application/x-internet-signup", "isp"); + sMimeTypeMap.loadEntry("application/x-iphone", "iii"); + sMimeTypeMap.loadEntry("application/x-iso9660-image", "iso"); + sMimeTypeMap.loadEntry("application/x-jmol", "jmz"); + sMimeTypeMap.loadEntry("application/x-kchart", "chrt"); + sMimeTypeMap.loadEntry("application/x-killustrator", "kil"); + sMimeTypeMap.loadEntry("application/x-koan", "skp"); + sMimeTypeMap.loadEntry("application/x-koan", "skd"); + sMimeTypeMap.loadEntry("application/x-koan", "skt"); + sMimeTypeMap.loadEntry("application/x-koan", "skm"); + sMimeTypeMap.loadEntry("application/x-kpresenter", "kpr"); + sMimeTypeMap.loadEntry("application/x-kpresenter", "kpt"); + sMimeTypeMap.loadEntry("application/x-kspread", "ksp"); + sMimeTypeMap.loadEntry("application/x-kword", "kwd"); + sMimeTypeMap.loadEntry("application/x-kword", "kwt"); + sMimeTypeMap.loadEntry("application/x-latex", "latex"); + sMimeTypeMap.loadEntry("application/x-lha", "lha"); + sMimeTypeMap.loadEntry("application/x-lzh", "lzh"); + sMimeTypeMap.loadEntry("application/x-lzx", "lzx"); + sMimeTypeMap.loadEntry("application/x-maker", "frm"); + sMimeTypeMap.loadEntry("application/x-maker", "maker"); + sMimeTypeMap.loadEntry("application/x-maker", "frame"); + sMimeTypeMap.loadEntry("application/x-maker", "fb"); + sMimeTypeMap.loadEntry("application/x-maker", "book"); + sMimeTypeMap.loadEntry("application/x-maker", "fbdoc"); + sMimeTypeMap.loadEntry("application/x-mif", "mif"); + sMimeTypeMap.loadEntry("application/x-ms-wmd", "wmd"); + sMimeTypeMap.loadEntry("application/x-ms-wmz", "wmz"); + sMimeTypeMap.loadEntry("application/x-msi", "msi"); + sMimeTypeMap.loadEntry("application/x-ns-proxy-autoconfig", "pac"); + sMimeTypeMap.loadEntry("application/x-nwc", "nwc"); + sMimeTypeMap.loadEntry("application/x-object", "o"); + sMimeTypeMap.loadEntry("application/x-oz-application", "oza"); + sMimeTypeMap.loadEntry("application/x-pkcs12", "p12"); + sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r"); + sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl"); + sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl"); + sMimeTypeMap.loadEntry("application/x-shar", "shar"); + sMimeTypeMap.loadEntry("application/x-stuffit", "sit"); + sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio"); + sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc"); + sMimeTypeMap.loadEntry("application/x-tar", "tar"); + sMimeTypeMap.loadEntry("application/x-texinfo", "texinfo"); + sMimeTypeMap.loadEntry("application/x-texinfo", "texi"); + sMimeTypeMap.loadEntry("application/x-troff", "t"); + sMimeTypeMap.loadEntry("application/x-troff", "roff"); + sMimeTypeMap.loadEntry("application/x-troff-man", "man"); + sMimeTypeMap.loadEntry("application/x-ustar", "ustar"); + sMimeTypeMap.loadEntry("application/x-wais-source", "src"); + sMimeTypeMap.loadEntry("application/x-wingz", "wz"); + sMimeTypeMap.loadEntry("application/x-webarchive", "webarchive"); + sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt"); + sMimeTypeMap.loadEntry("application/x-xcf", "xcf"); + sMimeTypeMap.loadEntry("application/x-xfig", "fig"); + sMimeTypeMap.loadEntry("application/xhtml+xml", "xhtml"); + sMimeTypeMap.loadEntry("audio/basic", "snd"); + sMimeTypeMap.loadEntry("audio/midi", "mid"); + sMimeTypeMap.loadEntry("audio/midi", "midi"); + sMimeTypeMap.loadEntry("audio/midi", "kar"); + sMimeTypeMap.loadEntry("audio/mpeg", "mpga"); + sMimeTypeMap.loadEntry("audio/mpeg", "mpega"); + sMimeTypeMap.loadEntry("audio/mpeg", "mp2"); + sMimeTypeMap.loadEntry("audio/mpeg", "mp3"); + sMimeTypeMap.loadEntry("audio/mpeg", "m4a"); + sMimeTypeMap.loadEntry("audio/mpegurl", "m3u"); + sMimeTypeMap.loadEntry("audio/prs.sid", "sid"); + sMimeTypeMap.loadEntry("audio/x-aiff", "aif"); + sMimeTypeMap.loadEntry("audio/x-aiff", "aiff"); + sMimeTypeMap.loadEntry("audio/x-aiff", "aifc"); + sMimeTypeMap.loadEntry("audio/x-gsm", "gsm"); + sMimeTypeMap.loadEntry("audio/x-mpegurl", "m3u"); + sMimeTypeMap.loadEntry("audio/x-ms-wma", "wma"); + sMimeTypeMap.loadEntry("audio/x-ms-wax", "wax"); + sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ra"); + sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "rm"); + sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ram"); + sMimeTypeMap.loadEntry("audio/x-realaudio", "ra"); + sMimeTypeMap.loadEntry("audio/x-scpls", "pls"); + sMimeTypeMap.loadEntry("audio/x-sd2", "sd2"); + sMimeTypeMap.loadEntry("audio/x-wav", "wav"); + sMimeTypeMap.loadEntry("image/bmp", "bmp"); + sMimeTypeMap.loadEntry("image/gif", "gif"); + sMimeTypeMap.loadEntry("image/ico", "cur"); + sMimeTypeMap.loadEntry("image/ico", "ico"); + sMimeTypeMap.loadEntry("image/ief", "ief"); + sMimeTypeMap.loadEntry("image/jpeg", "jpeg"); + sMimeTypeMap.loadEntry("image/jpeg", "jpg"); + sMimeTypeMap.loadEntry("image/jpeg", "jpe"); + sMimeTypeMap.loadEntry("image/pcx", "pcx"); + sMimeTypeMap.loadEntry("image/png", "png"); + sMimeTypeMap.loadEntry("image/svg+xml", "svg"); + sMimeTypeMap.loadEntry("image/svg+xml", "svgz"); + sMimeTypeMap.loadEntry("image/tiff", "tiff"); + sMimeTypeMap.loadEntry("image/tiff", "tif"); + sMimeTypeMap.loadEntry("image/vnd.djvu", "djvu"); + sMimeTypeMap.loadEntry("image/vnd.djvu", "djv"); + sMimeTypeMap.loadEntry("image/vnd.wap.wbmp", "wbmp"); + sMimeTypeMap.loadEntry("image/x-cmu-raster", "ras"); + sMimeTypeMap.loadEntry("image/x-coreldraw", "cdr"); + sMimeTypeMap.loadEntry("image/x-coreldrawpattern", "pat"); + sMimeTypeMap.loadEntry("image/x-coreldrawtemplate", "cdt"); + sMimeTypeMap.loadEntry("image/x-corelphotopaint", "cpt"); + sMimeTypeMap.loadEntry("image/x-icon", "ico"); + sMimeTypeMap.loadEntry("image/x-jg", "art"); + sMimeTypeMap.loadEntry("image/x-jng", "jng"); + sMimeTypeMap.loadEntry("image/x-ms-bmp", "bmp"); + sMimeTypeMap.loadEntry("image/x-photoshop", "psd"); + sMimeTypeMap.loadEntry("image/x-portable-anymap", "pnm"); + sMimeTypeMap.loadEntry("image/x-portable-bitmap", "pbm"); + sMimeTypeMap.loadEntry("image/x-portable-graymap", "pgm"); + sMimeTypeMap.loadEntry("image/x-portable-pixmap", "ppm"); + sMimeTypeMap.loadEntry("image/x-rgb", "rgb"); + sMimeTypeMap.loadEntry("image/x-xbitmap", "xbm"); + sMimeTypeMap.loadEntry("image/x-xpixmap", "xpm"); + sMimeTypeMap.loadEntry("image/x-xwindowdump", "xwd"); + sMimeTypeMap.loadEntry("model/iges", "igs"); + sMimeTypeMap.loadEntry("model/iges", "iges"); + sMimeTypeMap.loadEntry("model/mesh", "msh"); + sMimeTypeMap.loadEntry("model/mesh", "mesh"); + sMimeTypeMap.loadEntry("model/mesh", "silo"); + sMimeTypeMap.loadEntry("text/calendar", "ics"); + sMimeTypeMap.loadEntry("text/calendar", "icz"); + sMimeTypeMap.loadEntry("text/comma-separated-values", "csv"); + sMimeTypeMap.loadEntry("text/css", "css"); + sMimeTypeMap.loadEntry("text/h323", "323"); + sMimeTypeMap.loadEntry("text/iuls", "uls"); + sMimeTypeMap.loadEntry("text/mathml", "mml"); // add it first so it will be the default for ExtensionFromMimeType - sMimeTypeMap.loadEntry("text/plain", "txt", true); - sMimeTypeMap.loadEntry("text/plain", "asc", true); - sMimeTypeMap.loadEntry("text/plain", "text", true); - sMimeTypeMap.loadEntry("text/plain", "diff", true); - sMimeTypeMap.loadEntry("text/plain", "pot", true); - sMimeTypeMap.loadEntry("text/richtext", "rtx", true); - sMimeTypeMap.loadEntry("text/rtf", "rtf", true); - sMimeTypeMap.loadEntry("text/texmacs", "ts", true); - sMimeTypeMap.loadEntry("text/text", "phps", true); - sMimeTypeMap.loadEntry("text/tab-separated-values", "tsv", true); - sMimeTypeMap.loadEntry("text/x-bibtex", "bib", true); - sMimeTypeMap.loadEntry("text/x-boo", "boo", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "h++", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hpp", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hxx", true); - sMimeTypeMap.loadEntry("text/x-c++hdr", "hh", true); - sMimeTypeMap.loadEntry("text/x-c++src", "c++", true); - sMimeTypeMap.loadEntry("text/x-c++src", "cpp", true); - sMimeTypeMap.loadEntry("text/x-c++src", "cxx", true); - sMimeTypeMap.loadEntry("text/x-chdr", "h", true); - sMimeTypeMap.loadEntry("text/x-component", "htc", true); - sMimeTypeMap.loadEntry("text/x-csh", "csh", true); - sMimeTypeMap.loadEntry("text/x-csrc", "c", true); - sMimeTypeMap.loadEntry("text/x-dsrc", "d", true); - sMimeTypeMap.loadEntry("text/x-haskell", "hs", true); - sMimeTypeMap.loadEntry("text/x-java", "java", true); - sMimeTypeMap.loadEntry("text/x-literate-haskell", "lhs", true); - sMimeTypeMap.loadEntry("text/x-moc", "moc", true); - sMimeTypeMap.loadEntry("text/x-pascal", "p", true); - sMimeTypeMap.loadEntry("text/x-pascal", "pas", true); - sMimeTypeMap.loadEntry("text/x-pcs-gcd", "gcd", true); - sMimeTypeMap.loadEntry("text/x-setext", "etx", true); - sMimeTypeMap.loadEntry("text/x-tcl", "tcl", true); - sMimeTypeMap.loadEntry("text/x-tex", "tex", true); - sMimeTypeMap.loadEntry("text/x-tex", "ltx", true); - sMimeTypeMap.loadEntry("text/x-tex", "sty", true); - sMimeTypeMap.loadEntry("text/x-tex", "cls", true); - sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs", true); - sMimeTypeMap.loadEntry("text/x-vcard", "vcf", true); - sMimeTypeMap.loadEntry("video/3gpp", "3gp", false); - sMimeTypeMap.loadEntry("video/3gpp", "3g2", false); - sMimeTypeMap.loadEntry("video/dl", "dl", false); - sMimeTypeMap.loadEntry("video/dv", "dif", false); - sMimeTypeMap.loadEntry("video/dv", "dv", false); - sMimeTypeMap.loadEntry("video/fli", "fli", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpeg", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpg", false); - sMimeTypeMap.loadEntry("video/mpeg", "mpe", false); - sMimeTypeMap.loadEntry("video/mp4", "mp4", false); - sMimeTypeMap.loadEntry("video/mpeg", "VOB", false); - sMimeTypeMap.loadEntry("video/quicktime", "qt", false); - sMimeTypeMap.loadEntry("video/quicktime", "mov", false); - sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu", false); - sMimeTypeMap.loadEntry("video/x-la-asf", "lsf", false); - sMimeTypeMap.loadEntry("video/x-la-asf", "lsx", false); - sMimeTypeMap.loadEntry("video/x-mng", "mng", false); - sMimeTypeMap.loadEntry("video/x-ms-asf", "asf", false); - sMimeTypeMap.loadEntry("video/x-ms-asf", "asx", false); - sMimeTypeMap.loadEntry("video/x-ms-wm", "wm", false); - sMimeTypeMap.loadEntry("video/x-ms-wmv", "wmv", false); - sMimeTypeMap.loadEntry("video/x-ms-wmx", "wmx", false); - sMimeTypeMap.loadEntry("video/x-ms-wvx", "wvx", false); - sMimeTypeMap.loadEntry("video/x-msvideo", "avi", false); - sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie", false); - sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice", false); - sMimeTypeMap.loadEntry("x-epoc/x-sisx-app", "sisx", false); + sMimeTypeMap.loadEntry("text/plain", "txt"); + sMimeTypeMap.loadEntry("text/plain", "asc"); + sMimeTypeMap.loadEntry("text/plain", "text"); + sMimeTypeMap.loadEntry("text/plain", "diff"); + sMimeTypeMap.loadEntry("text/plain", "pot"); + sMimeTypeMap.loadEntry("text/richtext", "rtx"); + sMimeTypeMap.loadEntry("text/rtf", "rtf"); + sMimeTypeMap.loadEntry("text/texmacs", "ts"); + sMimeTypeMap.loadEntry("text/text", "phps"); + sMimeTypeMap.loadEntry("text/tab-separated-values", "tsv"); + sMimeTypeMap.loadEntry("text/x-bibtex", "bib"); + sMimeTypeMap.loadEntry("text/x-boo", "boo"); + sMimeTypeMap.loadEntry("text/x-c++hdr", "h++"); + sMimeTypeMap.loadEntry("text/x-c++hdr", "hpp"); + sMimeTypeMap.loadEntry("text/x-c++hdr", "hxx"); + sMimeTypeMap.loadEntry("text/x-c++hdr", "hh"); + sMimeTypeMap.loadEntry("text/x-c++src", "c++"); + sMimeTypeMap.loadEntry("text/x-c++src", "cpp"); + sMimeTypeMap.loadEntry("text/x-c++src", "cxx"); + sMimeTypeMap.loadEntry("text/x-chdr", "h"); + sMimeTypeMap.loadEntry("text/x-component", "htc"); + sMimeTypeMap.loadEntry("text/x-csh", "csh"); + sMimeTypeMap.loadEntry("text/x-csrc", "c"); + sMimeTypeMap.loadEntry("text/x-dsrc", "d"); + sMimeTypeMap.loadEntry("text/x-haskell", "hs"); + sMimeTypeMap.loadEntry("text/x-java", "java"); + sMimeTypeMap.loadEntry("text/x-literate-haskell", "lhs"); + sMimeTypeMap.loadEntry("text/x-moc", "moc"); + sMimeTypeMap.loadEntry("text/x-pascal", "p"); + sMimeTypeMap.loadEntry("text/x-pascal", "pas"); + sMimeTypeMap.loadEntry("text/x-pcs-gcd", "gcd"); + sMimeTypeMap.loadEntry("text/x-setext", "etx"); + sMimeTypeMap.loadEntry("text/x-tcl", "tcl"); + sMimeTypeMap.loadEntry("text/x-tex", "tex"); + sMimeTypeMap.loadEntry("text/x-tex", "ltx"); + sMimeTypeMap.loadEntry("text/x-tex", "sty"); + sMimeTypeMap.loadEntry("text/x-tex", "cls"); + sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs"); + sMimeTypeMap.loadEntry("text/x-vcard", "vcf"); + sMimeTypeMap.loadEntry("video/3gpp", "3gp"); + sMimeTypeMap.loadEntry("video/3gpp", "3g2"); + sMimeTypeMap.loadEntry("video/dl", "dl"); + sMimeTypeMap.loadEntry("video/dv", "dif"); + sMimeTypeMap.loadEntry("video/dv", "dv"); + sMimeTypeMap.loadEntry("video/fli", "fli"); + sMimeTypeMap.loadEntry("video/mpeg", "mpeg"); + sMimeTypeMap.loadEntry("video/mpeg", "mpg"); + sMimeTypeMap.loadEntry("video/mpeg", "mpe"); + sMimeTypeMap.loadEntry("video/mp4", "mp4"); + sMimeTypeMap.loadEntry("video/mpeg", "VOB"); + sMimeTypeMap.loadEntry("video/quicktime", "qt"); + sMimeTypeMap.loadEntry("video/quicktime", "mov"); + sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu"); + sMimeTypeMap.loadEntry("video/x-la-asf", "lsf"); + sMimeTypeMap.loadEntry("video/x-la-asf", "lsx"); + sMimeTypeMap.loadEntry("video/x-mng", "mng"); + sMimeTypeMap.loadEntry("video/x-ms-asf", "asf"); + sMimeTypeMap.loadEntry("video/x-ms-asf", "asx"); + sMimeTypeMap.loadEntry("video/x-ms-wm", "wm"); + sMimeTypeMap.loadEntry("video/x-ms-wmv", "wmv"); + sMimeTypeMap.loadEntry("video/x-ms-wmx", "wmx"); + sMimeTypeMap.loadEntry("video/x-ms-wvx", "wvx"); + sMimeTypeMap.loadEntry("video/x-msvideo", "avi"); + sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie"); + sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice"); + sMimeTypeMap.loadEntry("x-epoc/x-sisx-app", "sisx"); } return sMimeTypeMap; diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java index 8c2b09b1a7a1..fb601098bd5a 100644 --- a/core/java/android/webkit/Network.java +++ b/core/java/android/webkit/Network.java @@ -149,14 +149,12 @@ class Network { * @param headers The http headers. * @param postData The body of the request. * @param loader A LoadListener for receiving the results of the request. - * @param isHighPriority True if this is high priority request. * @return True if the request was successfully queued. */ public boolean requestURL(String method, Map<String, String> headers, byte [] postData, - LoadListener loader, - boolean isHighPriority) { + LoadListener loader) { String url = loader.url(); @@ -188,7 +186,7 @@ class Network { RequestHandle handle = q.queueRequest( url, loader.getWebAddress(), method, headers, loader, - bodyProvider, bodyLength, isHighPriority); + bodyProvider, bodyLength); loader.attachRequestHandle(handle); if (loader.isSynchronous()) { diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index de70fc2d2b65..1d1828999305 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -348,7 +348,7 @@ public final class URLUtil { * This header provides a filename for content that is going to be * downloaded to the file system. We only support the attachment type. */ - private static String parseContentDisposition(String contentDisposition) { + static String parseContentDisposition(String contentDisposition) { try { Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); if (m.find()) { diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 23c7f7d6651d..da1443c6db3e 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -17,13 +17,22 @@ package android.webkit; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.InputFilter; import android.text.Selection; import android.text.Spannable; +import android.text.TextPaint; import android.text.TextUtils; import android.text.method.MovementMethod; import android.util.Log; +import android.view.Gravity; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -61,6 +70,7 @@ import java.util.ArrayList; // Keep track of the text before the change so we know whether we actually // need to send down the DOM events. private String mPreChange; + private Drawable mBackground; // Array to store the final character added in onTextChanged, so that its // KeyEvents may be determined. private char[] mCharacter = new char[1]; @@ -80,9 +90,6 @@ import java.util.ArrayList; mWebView = webView; mMaxLength = -1; setImeOptions(EditorInfo.IME_ACTION_NONE); - // Allow webkit's drawing to show through - setWillNotDraw(true); - setCursorVisible(false); } @Override @@ -111,7 +118,11 @@ import java.util.ArrayList; if (!isArrowKey && mWebView.nativeFocusNodePointer() != mNodePointer) { mWebView.nativeClearCursor(); - remove(); + // Do not call remove() here, which hides the soft keyboard. If + // the soft keyboard is being displayed, the user will still want + // it there. + mWebView.removeView(this); + mWebView.requestFocus(); return mWebView.dispatchKeyEvent(event); } @@ -456,7 +467,65 @@ import java.util.ArrayList; if (inPassword) { setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo. TYPE_TEXT_VARIATION_PASSWORD); + createBackground(); + } + // For password fields, draw the WebTextView. For others, just show + // webkit's drawing. + setWillNotDraw(!inPassword); + setBackgroundDrawable(inPassword ? mBackground : null); + // For non-password fields, avoid the invals from TextView's blinking + // cursor + setCursorVisible(inPassword); + } + + /** + * Private class used for the background of a password textfield. + */ + private static class OutlineDrawable extends Drawable { + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + Paint paint = new Paint(); + paint.setAntiAlias(true); + // Draw the background. + paint.setColor(Color.WHITE); + canvas.drawRect(bounds, paint); + // Draw the outline. + paint.setStyle(Paint.Style.STROKE); + paint.setColor(Color.BLACK); + canvas.drawRect(bounds, paint); + } + // Always want it to be opaque. + public int getOpacity() { + return PixelFormat.OPAQUE; + } + // These are needed because they are abstract in Drawable. + public void setAlpha(int alpha) { } + public void setColorFilter(ColorFilter cf) { } + } + + /** + * Create a background for the WebTextView and set up the paint for drawing + * the text. This way, we can see the password transformation of the + * system, which (optionally) shows the actual text before changing to dots. + * The background is necessary to hide the webkit-drawn text beneath. + */ + private void createBackground() { + if (mBackground != null) { + return; } + mBackground = new OutlineDrawable(); + + setGravity(Gravity.CENTER_VERTICAL); + // Turn on subpixel text, and turn off kerning, so it better matches + // the text in webkit. + TextPaint paint = getPaint(); + int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG | + Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG; + paint.setFlags(flags); + // Set the text color to black, regardless of the theme. This ensures + // that other applications that use embedded WebViews will properly + // display the text in password textfields. + setTextColor(Color.BLACK); } /* package */ void setMaxLength(int maxLength) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 899d63695ed2..c1ba690cf9d2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1235,6 +1235,9 @@ public class WebView extends AbsoluteLayout * @param url The url of the resource to load. */ public void loadUrl(String url) { + if (url == null) { + return; + } switchOutDrawHistory(); mWebViewCore.sendMessage(EventHub.LOAD_URL, url); clearTextEntry(); @@ -4915,6 +4918,11 @@ public class WebView extends AbsoluteLayout } } setNewZoomScale(scale, false); + // As we are on a new page, remove the WebTextView. This + // is necessary for page loads driven by webkit, and in + // particular when the user was on a password field, so + // the WebTextView was visible. + clearTextEntry(); break; case MOVE_OUT_OF_PLUGIN: if (nativePluginEatsNavKey()) { diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 20f72396a9d1..99ceec276f49 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -32,6 +32,8 @@ import android.os.Process; import android.util.Log; import android.util.SparseBooleanArray; import android.view.KeyEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; import java.util.ArrayList; @@ -1826,6 +1828,76 @@ final class WebViewCore { } } + // This class looks like a SurfaceView to native code. In java, we can + // assume the passed in SurfaceView is this class so we can talk to the + // ViewManager through the ChildView. + private class SurfaceViewProxy extends SurfaceView + implements SurfaceHolder.Callback { + private final ViewManager.ChildView mChildView; + private int mPointer; + SurfaceViewProxy(Context context, ViewManager.ChildView childView, + int pointer) { + super(context); + setWillNotDraw(false); // this prevents the black box artifact + getHolder().addCallback(this); + mChildView = childView; + mChildView.mView = this; + mPointer = pointer; + } + void destroy() { + mPointer = 0; + mChildView.removeView(); + } + void attach(int x, int y, int width, int height) { + mChildView.attachView(x, y, width, height); + } + + // SurfaceHolder.Callback methods + public void surfaceCreated(SurfaceHolder holder) { + if (mPointer != 0) { + nativeSurfaceChanged(mPointer, 0, 0, 0, 0); + } + } + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + if (mPointer != 0) { + nativeSurfaceChanged(mPointer, 1, format, width, height); + } + } + public void surfaceDestroyed(SurfaceHolder holder) { + if (mPointer != 0) { + nativeSurfaceChanged(mPointer, 2, 0, 0, 0); + } + } + } + + // PluginWidget functions for mainting SurfaceViews for the Surface drawing + // model. + private SurfaceView createSurface(int nativePointer) { + if (mWebView == null) { + return null; + } + return new SurfaceViewProxy(mContext, + mWebView.mViewManager.createView(), nativePointer); + } + + private void destroySurface(SurfaceView surface) { + SurfaceViewProxy proxy = (SurfaceViewProxy) surface; + proxy.destroy(); + } + + private void attachSurface(SurfaceView surface, int x, int y, + int width, int height) { + SurfaceViewProxy proxy = (SurfaceViewProxy) surface; + proxy.attach(x, y, width, height); + } + + // Callback for the SurfaceHolder.Callback. Called for all the surface + // callbacks. The state parameter is one of Created(0), Changed(1), + // Destroyed(2). + private native void nativeSurfaceChanged(int pointer, int state, int format, + int width, int height); + private native void nativePause(); private native void nativeResume(); private native void nativeFreeMemory(); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index f9ca8cb7a43b..777beed7ed13 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -720,7 +720,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public void getFocusedRect(Rect r) { View view = getSelectedView(); - if (view != null) { + if (view != null && view.getParent() == this) { // the focused rectangle of the selected view offset into the // coordinate space of this view. view.getFocusedRect(r); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index be3dd1992439..09a547ff5e11 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -31,6 +31,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.EditorInfo; @@ -141,6 +142,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup = new PopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); + mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); TypedArray a = context.obtainStyledAttributes( @@ -208,8 +210,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (mDropDownAlwaysVisible && mPopup.isShowing() && mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED) { - mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); - showDropDown(); + ensureImeVisible(); } } @@ -1070,11 +1071,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * Issues a runnable to show the dropdown as soon as possible. * - * @hide internal used only by Search Dialog + * @hide internal used only by SearchDialog */ public void showDropDownAfterLayout() { post(mShowDropDownRunnable); } + + /** + * Ensures that the drop down is not obscuring the IME. + * + * @hide internal used only here and SearchDialog + */ + public void ensureImeVisible() { + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + showDropDown(); + } /** * <p>Displays the drop down on screen.</p> @@ -1268,11 +1279,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - // Max height available on the screen for a popup. If this AutoCompleteTextView has - // the dropDownAlwaysVisible attribute, and the input method is not currently required, - // we then we ask for the height ignoring any bottom decorations like the input method. - // Otherwise we respect the input method. - boolean ignoreBottomDecorations = mDropDownAlwaysVisible && + // Max height available on the screen for a popup. + boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; final int maxHeight = mPopup.getMaxAvailableHeight( getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 54f27072b4f9..3b9f1de4c66c 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -110,6 +110,8 @@ public class DatePicker extends FrameLayout { * subtract by one to ensure our internal state is always 0-11 */ mMonth = newVal - 1; + // Adjust max day of the month + adjustMaxDay(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay); } @@ -121,9 +123,12 @@ public class DatePicker extends FrameLayout { mYearPicker.setOnChangeListener(new OnChangedListener() { public void onChanged(NumberPicker picker, int oldVal, int newVal) { mYear = newVal; + // Adjust max day for leap years if needed + adjustMaxDay(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay); } + updateDaySpinner(); } }); @@ -318,4 +323,14 @@ public class DatePicker extends FrameLayout { public int getDayOfMonth() { return mDay; } + + private void adjustMaxDay(){ + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, mYear); + cal.set(Calendar.MONTH, mMonth); + int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if (mDay > max) { + mDay = max; + } + } } diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java index 7e55c78a39b3..d90154027327 100644 --- a/core/java/android/widget/Filter.java +++ b/core/java/android/widget/Filter.java @@ -46,6 +46,8 @@ public abstract class Filter { private Handler mThreadHandler; private Handler mResultHandler; + private Delayer mDelayer; + private final Object mLock = new Object(); /** @@ -56,6 +58,20 @@ public abstract class Filter { } /** + * Provide an interface that decides how long to delay the message for a given query. Useful + * for heuristics such as posting a delay for the delete key to avoid doing any work while the + * user holds down the delete key. + * + * @param delayer The delayer. + * @hide + */ + public void setDelayer(Delayer delayer) { + synchronized (mLock) { + mDelayer = delayer; + } + } + + /** * <p>Starts an asynchronous filtering operation. Calling this method * cancels all previous non-executed filtering requests and posts a new * filtering request that will be executed later.</p> @@ -85,10 +101,13 @@ public abstract class Filter { public final void filter(CharSequence constraint, FilterListener listener) { synchronized (mLock) { if (mThreadHandler == null) { - HandlerThread thread = new HandlerThread(THREAD_NAME); + HandlerThread thread = new HandlerThread( + THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } + + final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); @@ -101,7 +120,7 @@ public abstract class Filter { mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); - mThreadHandler.sendMessage(message); + mThreadHandler.sendMessageDelayed(message, delay); } } @@ -288,4 +307,17 @@ public abstract class Filter { */ FilterResults results; } + + /** + * @hide + */ + public interface Delayer { + + /** + * @param constraint The constraint passed to {@link Filter#filter(CharSequence)} + * @return The delay that should be used for + * {@link Handler#sendMessageDelayed(android.os.Message, long)} + */ + long getPostingDelay(CharSequence constraint); + } } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 0c2cd55d2ee3..90fbb77d255e 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -27,7 +27,6 @@ import android.view.WindowManager; import android.view.Gravity; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.WindowManagerImpl; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.View.OnTouchListener; import android.graphics.PixelFormat; @@ -82,6 +81,7 @@ public class PopupWindow { private View mPopupView; private boolean mFocusable; private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; + private int mSoftInputMode; private boolean mTouchable = true; private boolean mOutsideTouchable = false; private boolean mClippingEnabled = true; @@ -446,6 +446,30 @@ public class PopupWindow { public void setInputMethodMode(int mode) { mInputMethodMode = mode; } + + /** + * Sets the operating mode for the soft input area. + * + * @param mode The desired mode, see + * {@link android.view.WindowManager.LayoutParams#softInputMode} + * for the full list + * + * @see android.view.WindowManager.LayoutParams#softInputMode + * @see #getSoftInputMode() + */ + public void setSoftInputMode(int mode) { + mSoftInputMode = mode; + } + + /** + * Returns the current value in {@link #setSoftInputMode(int)}. + * + * @see #setSoftInputMode(int) + * @see android.view.WindowManager.LayoutParams#softInputMode + */ + public int getSoftInputMode() { + return mSoftInputMode; + } /** * <p>Indicates whether the popup window receives touch events.</p> @@ -822,7 +846,7 @@ public class PopupWindow { p.flags = computeFlags(p.flags); p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; p.token = token; - p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p; diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index e62dda58ba42..24c0e2a4a2d9 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -1295,11 +1295,8 @@ public class RelativeLayout extends ViewGroup { if (rule > 0) { // The node this node depends on final Node dependency = keyNodes.get(rule); - if (dependency == node) { - throw new IllegalStateException("A view cannot have a dependency" + - " on itself"); - } - if (dependency == null) { + // Skip unknowns and self dependencies + if (dependency == null || dependency == node) { continue; } // Add the current node as a dependent diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 80d688ef2793..bcddca18009e 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -24,7 +24,6 @@ import android.content.DialogInterface.OnClickListener; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.util.AttributeSet; -import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -40,6 +39,7 @@ import android.view.ViewGroup; public class Spinner extends AbsSpinner implements OnClickListener { private CharSequence mPrompt; + private AlertDialog mPopup; public Spinner(Context context) { this(context, null); @@ -78,6 +78,16 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mPopup != null && mPopup.isShowing()) { + mPopup.dismiss(); + mPopup = null; + } + } + /** * <p>A spinner does not support item click events. Calling this method * will raise an exception.</p> @@ -244,7 +254,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { if (mPrompt != null) { builder.setTitle(mPrompt); } - builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show(); + mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show(); } return handled; @@ -253,6 +263,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void onClick(DialogInterface dialog, int which) { setSelection(which); dialog.dismiss(); + mPopup = null; } /** diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a03802dff012..a449e5f00919 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,6 +16,7 @@ package com.android.internal.os; +import android.bluetooth.BluetoothHeadset; import android.os.BatteryStats; import android.os.NetStat; import android.os.Parcel; @@ -128,7 +129,10 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mBluetoothOn; StopwatchTimer mBluetoothOnTimer; - + + /** Bluetooth headset object */ + BluetoothHeadset mBtHeadset; + /** * These provide time bases that discount the time the device is plugged * in to power. @@ -160,6 +164,9 @@ public final class BatteryStatsImpl extends BatteryStats { private long mRadioDataUptime; private long mRadioDataStart; + private int mBluetoothPingCount; + private int mBluetoothPingStart = -1; + /* * Holds a SamplingTimer associated with each kernel wakelock name being tracked. */ @@ -920,14 +927,18 @@ public final class BatteryStatsImpl extends BatteryStats { dataTransfer[STATS_UNPLUGGED] = currentBytes; } - private long getCurrentRadioDataUptimeMs() { + /** + * Radio uptime in microseconds when transferring data. This value is very approximate. + * @return + */ + private long getCurrentRadioDataUptime() { try { File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms"); if (!awakeTimeFile.exists()) return 0; BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile)); String line = br.readLine(); br.close(); - return Long.parseLong(line); + return Long.parseLong(line) * 1000; } catch (NumberFormatException nfe) { // Nothing } catch (IOException ioe) { @@ -936,14 +947,44 @@ public final class BatteryStatsImpl extends BatteryStats { return 0; } + /** + * @deprecated use getRadioDataUptime + */ public long getRadioDataUptimeMs() { + return getRadioDataUptime() / 1000; + } + + /** + * Returns the duration that the cell radio was up for data transfers. + */ + public long getRadioDataUptime() { if (mRadioDataStart == -1) { return mRadioDataUptime; } else { - return getCurrentRadioDataUptimeMs() - mRadioDataStart; + return getCurrentRadioDataUptime() - mRadioDataStart; } } + private int getCurrentBluetoothPingCount() { + if (mBtHeadset != null) { + return mBtHeadset.getBatteryUsageHint(); + } + return -1; + } + + public int getBluetoothPingCount() { + if (mBluetoothPingStart == -1) { + return mBluetoothPingCount; + } else if (mBtHeadset != null) { + return getCurrentBluetoothPingCount() - mBluetoothPingStart; + } + return -1; + } + + public void setBtHeadset(BluetoothHeadset headset) { + mBtHeadset = headset; + } + public void doUnplug(long batteryUptime, long batteryRealtime) { for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { Uid u = mUidStats.valueAt(iu); @@ -961,8 +1002,11 @@ public final class BatteryStatsImpl extends BatteryStats { doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes()); doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes()); // Track radio awake time - mRadioDataStart = getCurrentRadioDataUptimeMs(); + mRadioDataStart = getCurrentRadioDataUptime(); mRadioDataUptime = 0; + // Track bt headset ping count + mBluetoothPingStart = getCurrentBluetoothPingCount(); + mBluetoothPingCount = 0; } public void doPlug(long batteryUptime, long batteryRealtime) { @@ -985,8 +1029,12 @@ public final class BatteryStatsImpl extends BatteryStats { doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes()); doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes()); // Track radio awake time - mRadioDataUptime = getRadioDataUptimeMs(); + mRadioDataUptime = getRadioDataUptime(); mRadioDataStart = -1; + + // Track bt headset ping count + mBluetoothPingCount = getBluetoothPingCount(); + mBluetoothPingStart = -1; } public void noteStartGps(int uid) { @@ -3335,6 +3383,9 @@ public final class BatteryStatsImpl extends BatteryStats { mRadioDataUptime = in.readLong(); mRadioDataStart = -1; + mBluetoothPingCount = in.readInt(); + mBluetoothPingStart = -1; + mKernelWakelockStats.clear(); int NKW = in.readInt(); for (int ikw = 0; ikw < NKW; ikw++) { @@ -3415,7 +3466,9 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(getTotalTcpBytesSent(STATS_UNPLUGGED)); // Write radio uptime for data - out.writeLong(getRadioDataUptimeMs()); + out.writeLong(getRadioDataUptime()); + + out.writeInt(getBluetoothPingCount()); out.writeInt(mKernelWakelockStats.size()); for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 4a8d8b182f90..94f703add469 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -87,6 +87,11 @@ public class PowerProfile { public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active"; /** + * Power consumption when Bluetooth driver gets an AT command. + */ + public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at"; + + /** * Power consumption when screen is on, not including the backlight power. */ public static final String POWER_SCREEN_ON = "screen.on"; diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java index 2f08c8dd424b..0424ced5c76e 100644 --- a/core/java/com/android/internal/widget/NumberPicker.java +++ b/core/java/com/android/internal/widget/NumberPicker.java @@ -243,9 +243,11 @@ public class NumberPicker extends LinearLayout implements OnClickListener, private void validateCurrentView(CharSequence str) { int val = getSelectedPos(str.toString()); if ((val >= mStart) && (val <= mEnd)) { - mPrevious = mCurrent; - mCurrent = val; - notifyChange(); + if (mCurrent != val) { + mPrevious = mCurrent; + mCurrent = val; + notifyChange(); + } } updateView(); } diff --git a/core/jni/.android_server_BluetoothEventLoop.cpp.swp b/core/jni/.android_server_BluetoothEventLoop.cpp.swp Binary files differnew file mode 100644 index 000000000000..d36e403a79a8 --- /dev/null +++ b/core/jni/.android_server_BluetoothEventLoop.cpp.swp diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index c322b17e5991..63dc9e831360 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -746,6 +746,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) opt.optionString = "-Xincludeselectedmethod"; mOptions.add(opt); } + + /* + * Enable profile collection on JIT'ed code. + */ + property_get("dalvik.vm.jit.profile", propBuf, ""); + if (strlen(propBuf) > 0) { + opt.optionString = "-Xjitprofile"; + mOptions.add(opt); + } #endif if (executionMode == kEMIntPortable) { diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 957b825c3cc1..002d3db3f55e 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -224,7 +224,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, SkBitmap bitmap;
bitmap.setConfig(config, width, height);
- if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL)) {
+ if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {
return NULL;
}
@@ -240,7 +240,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,
SkBitmap::Config dstConfig, jboolean isMutable) {
SkBitmap result;
- JavaPixelAllocator allocator(env);
+ JavaPixelAllocator allocator(env, true);
if (!src->copyTo(&result, dstConfig, &allocator)) {
return NULL;
@@ -356,7 +356,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { }
}
- if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable)) {
+ if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, true)) {
ctable->safeUnref();
delete bitmap;
return NULL;
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 137707fa93bf..0c842650962f 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -23,6 +23,7 @@ static jfieldID gOptions_configFieldID; static jfieldID gOptions_ditherFieldID; static jfieldID gOptions_purgeableFieldID; static jfieldID gOptions_shareableFieldID; +static jfieldID gOptions_nativeAllocFieldID; static jfieldID gOptions_widthFieldID; static jfieldID gOptions_heightFieldID; static jfieldID gOptions_mimeFieldID; @@ -300,6 +301,11 @@ static bool optionsShareable(JNIEnv* env, jobject options) { env->GetBooleanField(options, gOptions_shareableFieldID); } +static bool optionsReportSizeToVM(JNIEnv* env, jobject options) { + return NULL == options || + !env->GetBooleanField(options, gOptions_nativeAllocFieldID); +} + static jobject nullObjectReturn(const char msg[]) { if (msg) { SkDebugf("--- %s\n", msg); @@ -330,6 +336,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, SkBitmap::Config prefConfig = SkBitmap::kNo_Config; bool doDither = true; bool isPurgeable = allowPurgeable && optionsPurgeable(env, options); + bool reportSizeToVM = optionsReportSizeToVM(env, options); if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); @@ -355,7 +362,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, decoder->setDitherImage(doDither); NinePatchPeeker peeker; - JavaPixelAllocator javaAllocator(env); + JavaPixelAllocator javaAllocator(env, reportSizeToVM); SkBitmap* bitmap = new SkBitmap; Res_png_9patch dummy9Patch; @@ -699,6 +706,7 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z"); gOptions_purgeableFieldID = getFieldIDCheck(env, gOptions_class, "inPurgeable", "Z"); gOptions_shareableFieldID = getFieldIDCheck(env, gOptions_class, "inInputShareable", "Z"); + gOptions_nativeAllocFieldID = getFieldIDCheck(env, gOptions_class, "inNativeAlloc", "Z"); gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I"); gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I"); gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;"); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 6eebbdcfb563..6e159a8853d5 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -5,6 +5,7 @@ #include "SkRegion.h" #include <android_runtime/AndroidRuntime.h> +//#define REPORT_SIZE_TO_JVM //#define TRACK_LOCK_COUNT void doThrow(JNIEnv* env, const char* exc, const char* msg) { @@ -444,7 +445,7 @@ private: }; bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, - SkColorTable* ctable) { + SkColorTable* ctable, bool reportSizeToVM) { Sk64 size64 = bitmap->getSize64(); if (size64.isNeg() || !size64.is32()) { doThrow(env, "java/lang/IllegalArgumentException", @@ -453,35 +454,41 @@ bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, } size_t size = size64.get32(); - // SkDebugf("-------------- inform VM we've allocated %d bytes\n", size); jlong jsize = size; // the VM wants longs for the size - bool r = env->CallBooleanMethod(gVMRuntime_singleton, - gVMRuntime_trackExternalAllocationMethodID, - jsize); - if (GraphicsJNI::hasException(env)) { - return false; - } - if (!r) { - LOGE("VM won't let us allocate %zd bytes\n", size); - doThrowOOME(env, "bitmap size exceeds VM budget"); - return false; + if (reportSizeToVM) { + // SkDebugf("-------------- inform VM we've allocated %d bytes\n", size); + bool r = env->CallBooleanMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalAllocationMethodID, + jsize); + if (GraphicsJNI::hasException(env)) { + return false; + } + if (!r) { + LOGE("VM won't let us allocate %zd bytes\n", size); + doThrowOOME(env, "bitmap size exceeds VM budget"); + return false; + } } - // call the version of malloc that returns null on failure void* addr = sk_malloc_flags(size, 0); if (NULL == addr) { - // SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size); - // we didn't actually allocate it, so inform the VM - env->CallVoidMethod(gVMRuntime_singleton, - gVMRuntime_trackExternalFreeMethodID, - jsize); - if (!GraphicsJNI::hasException(env)) { - doThrowOOME(env, "bitmap size too large for malloc"); + if (reportSizeToVM) { + // SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size); + // we didn't actually allocate it, so inform the VM + env->CallVoidMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalFreeMethodID, + jsize); + if (!GraphicsJNI::hasException(env)) { + doThrowOOME(env, "bitmap size too large for malloc"); + } } return false; } - bitmap->setPixelRef(new AndroidPixelRef(env, addr, size, ctable))->unref(); + SkPixelRef* pr = reportSizeToVM ? + new AndroidPixelRef(env, addr, size, ctable) : + new SkMallocPixelRef(addr, size, ctable); + bitmap->setPixelRef(pr)->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels(); @@ -490,12 +497,11 @@ bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, /////////////////////////////////////////////////////////////////////////////// -JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) : fEnv(env) -{ -} +JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM) + : fEnv(env), fReportSizeToVM(reportSizeToVM) {} bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { - return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable); + return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable, fReportSizeToVM); } //////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index e2dc9acf1beb..16925e41ab4f 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -59,7 +59,8 @@ public: Returns true on success. If it returns false, then it failed, and the appropriate exception will have been raised. */ - static bool setJavaPixelRef(JNIEnv*, SkBitmap*, SkColorTable* ctable); + static bool setJavaPixelRef(JNIEnv*, SkBitmap*, SkColorTable* ctable, + bool reportSizeToVM); /** Copy the colors in colors[] to the bitmap, convert to the correct format along the way. @@ -71,12 +72,13 @@ public: class JavaPixelAllocator : public SkBitmap::Allocator { public: - JavaPixelAllocator(JNIEnv* env); + JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM); // overrides virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable); private: JNIEnv* fEnv; + bool fReportSizeToVM; }; class AutoJavaFloatArray { diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 6776dce0de24..8a312d935d84 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -171,7 +171,8 @@ void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr) mCameraJObjectWeak, msgType, 0, 0, NULL); break; default: - LOGV("dataCallback(%d, %p)", msgType, dataPtr.get()); + // TODO: Change to LOGV + LOGD("dataCallback(%d, %p)", msgType, dataPtr.get()); copyAndPost(env, dataPtr, msgType); break; } @@ -222,6 +223,8 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj // finalizer is invoked later. static void android_hardware_Camera_release(JNIEnv *env, jobject thiz) { + // TODO: Change to LOGV + LOGD("release camera"); JNICameraContext* context = NULL; sp<Camera> camera; { diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index e71e3481862e..44a9e8cbcc58 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -212,8 +212,10 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, // failure: native_init_failure: + env->DeleteGlobalRef(lpCallbackData->audioRecord_class); + env->DeleteGlobalRef(lpCallbackData->audioRecord_ref); delete lpCallbackData; - + native_track_failure: delete lpRecorder; @@ -274,6 +276,8 @@ static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) { thiz, javaAudioRecordFields.nativeCallbackCookie); if (lpCookie) { LOGV("deleting lpCookie: %x\n", (int)lpCookie); + env->DeleteGlobalRef(lpCookie->audioRecord_class); + env->DeleteGlobalRef(lpCookie->audioRecord_ref); delete lpCookie; } diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index cf3ba7f502af..bc7f3f5c5daa 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -75,6 +75,9 @@ class AudioTrackJniStorage { int mStreamType; AudioTrackJniStorage() { + mCallbackData.audioTrack_class = 0; + mCallbackData.audioTrack_ref = 0; + mStreamType = AudioSystem::DEFAULT; } ~AudioTrackJniStorage() { @@ -318,6 +321,8 @@ native_init_failure: env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0); native_track_failure: + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_class); + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_ref); delete lpJniStorage; env->SetIntField(thiz, javaAudioTrackFields.jniData, 0); return AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED; @@ -415,6 +420,9 @@ static void android_media_AudioTrack_native_finalize(JNIEnv *env, jobject thiz) AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetIntField( thiz, javaAudioTrackFields.jniData); if (pJniStorage) { + // delete global refs created in native_setup + env->DeleteGlobalRef(pJniStorage->mCallbackData.audioTrack_class); + env->DeleteGlobalRef(pJniStorage->mCallbackData.audioTrack_ref); //LOGV("deleting pJniStorage: %x\n", (int)pJniStorage); delete pJniStorage; } diff --git a/core/jni/android_server_BluetoothDeviceService.cpp b/core/jni/android_server_BluetoothDeviceService.cpp index b02a19b135a4..444e6287a4d6 100644 --- a/core/jni/android_server_BluetoothDeviceService.cpp +++ b/core/jni/android_server_BluetoothDeviceService.cpp @@ -437,6 +437,65 @@ static jint isEnabledNative(JNIEnv *env, jobject object) { return -1; } +static jboolean setPairingConfirmationNative(JNIEnv *env, jobject object, + jstring address, bool confirm, + int nativeData) { +#ifdef HAVE_BLUETOOTH + LOGV(__FUNCTION__); + native_data_t *nat = get_native_data(env, object); + if (nat) { + DBusMessage *msg = (DBusMessage *)nativeData; + DBusMessage *reply; + if (confirm) { + reply = dbus_message_new_method_return(msg); + } else { + reply = dbus_message_new_error(msg, + "org.bluez.Error.Rejected", "User rejected confirmation"); + } + + if (!reply) { + LOGE("%s: Cannot create message reply to RequestConfirmation to " + "D-Bus\n", __FUNCTION__); + dbus_message_unref(msg); + return JNI_FALSE; + } + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(msg); + dbus_message_unref(reply); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + +static jboolean setPasskeyNative(JNIEnv *env, jobject object, jstring address, + int passkey, int nativeData) { +#ifdef HAVE_BLUETOOTH + LOGV(__FUNCTION__); + native_data_t *nat = get_native_data(env, object); + if (nat) { + DBusMessage *msg = (DBusMessage *)nativeData; + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) { + LOGE("%s: Cannot create message reply to return Passkey code to " + "D-Bus\n", __FUNCTION__); + dbus_message_unref(msg); + return JNI_FALSE; + } + + dbus_message_append_args(reply, DBUS_TYPE_UINT32, (uint32_t *)&passkey, + DBUS_TYPE_INVALID); + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(msg); + dbus_message_unref(reply); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + static jboolean setPinNative(JNIEnv *env, jobject object, jstring address, jstring pin, int nativeData) { #ifdef HAVE_BLUETOOTH @@ -467,17 +526,17 @@ static jboolean setPinNative(JNIEnv *env, jobject object, jstring address, return JNI_FALSE; } -static jboolean cancelPinNative(JNIEnv *env, jobject object, jstring address, - int nativeData) { +static jboolean cancelPairingUserInputNative(JNIEnv *env, jobject object, + jstring address, int nativeData) { #ifdef HAVE_BLUETOOTH LOGV(__FUNCTION__); native_data_t *nat = get_native_data(env, object); if (nat) { DBusMessage *msg = (DBusMessage *)nativeData; DBusMessage *reply = dbus_message_new_error(msg, - "org.bluez.Error.Canceled", "PIN Entry was canceled"); + "org.bluez.Error.Canceled", "Pairing User Input was canceled"); if (!reply) { - LOGE("%s: Cannot create message reply to return PIN cancel to " + LOGE("%s: Cannot create message reply to return cancelUserInput to" "D-BUS\n", __FUNCTION__); dbus_message_unref(msg); return JNI_FALSE; @@ -665,8 +724,12 @@ static JNINativeMethod sMethods[] = { {"getDeviceServiceChannelNative", "(Ljava/lang/String;Ljava/lang/String;I)I", (void *)getDeviceServiceChannelNative}, + {"setPairingConfirmationNative", "(Ljava/lang/String;ZI)Z", + (void *)setPairingConfirmationNative}, + {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative}, {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative}, - {"cancelPinNative", "(Ljava/lang/String;I)Z", (void *)cancelPinNative}, + {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z", + (void *)cancelPairingUserInputNative}, }; int register_android_server_BluetoothDeviceService(JNIEnv *env) { diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index 0857cb3a5225..4a13e8004ea8 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -50,6 +50,8 @@ static jmethodID method_onCreatePairedDeviceResult; static jmethodID method_onGetDeviceServiceChannelResult; static jmethodID method_onRequestPinCode; +static jmethodID method_onRequestPasskey; +static jmethodID method_onRequestConfirmation; static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; @@ -89,6 +91,10 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V"); method_onRequestPinCode = env->GetMethodID(clazz, "onRequestPinCode", "(Ljava/lang/String;I)V"); + method_onRequestPasskey = env->GetMethodID(clazz, "onRequestPasskey", + "(Ljava/lang/String;I)V"); + method_onRequestConfirmation = env->GetMethodID(clazz, "onRequestConfirmation", + "(Ljava/lang/String;II)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); #endif @@ -872,6 +878,38 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, int(msg)); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "RequestPasskey")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestPasskey() method", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onRequestPasskey, + env->NewStringUTF(object_path), + int(msg)); + } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "RequestConfirmation")) { + char *object_path; + uint32_t passkey; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestConfirmation() method", __FUNCTION__); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onRequestConfirmation, + env->NewStringUTF(object_path), + passkey, + int(msg)); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "Release")) { // reply DBusMessage *reply = dbus_message_new_method_return(msg); diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 770c75525225..09a0d70bcf40 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -368,7 +368,7 @@ static int pid_compare(const void* v1, const void* v2) return *((const jint*)v1) - *((const jint*)v2); } -jint android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) +static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) { int fd = open("/proc/meminfo", O_RDONLY); @@ -388,7 +388,7 @@ jint android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) buffer[len] = 0; int numFound = 0; - int mem = 0; + jlong mem = 0; static const char* const sums[] = { "MemFree:", "Cached:", NULL }; static const int sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), NULL }; @@ -407,7 +407,7 @@ jint android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) p++; if (*p == 0) p--; } - mem += atoi(num) * 1024; + mem += atoll(num) * 1024; numFound++; break; } @@ -857,7 +857,7 @@ static const JNINativeMethod methods[] = { {"setGid", "(I)I", (void*)android_os_Process_setGid}, {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal}, {"supportsProcesses", "()Z", (void*)android_os_Process_supportsProcesses}, - {"getFreeMemory", "()I", (void*)android_os_Process_getFreeMemory}, + {"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory}, {"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V", (void*)android_os_Process_readProcLines}, {"getPids", "(Ljava/lang/String;[I)[I", (void*)android_os_Process_getPids}, {"readProcFile", "(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z", (void*)android_os_Process_readProcFile}, diff --git a/core/res/res/drawable/light_header.9.png b/core/res/res/drawable/light_header.9.png Binary files differnew file mode 100644 index 000000000000..ad5dce1e241e --- /dev/null +++ b/core/res/res/drawable/light_header.9.png diff --git a/core/res/res/layout/search_dropdown_app_selector.xml b/core/res/res/layout/search_dropdown_app_selector.xml deleted file mode 100644 index f86645fa829d..000000000000 --- a/core/res/res/layout/search_dropdown_app_selector.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/layout/search_dropdown_app_selector.xml -** -** Copyright 2008, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:orientation="horizontal" - android:gravity="center_vertical" - android:baselineAligned="false" - > - - <ImageView android:id="@+id/search_app_icon1" - android:layout_width="32dip" - android:layout_height="32dip" - android:layout_gravity="center_vertical" - android:scaleType="fitCenter" - android:src="@android:drawable/ic_search_category_default" /> - - <TextView android:id="@+id/search_app_text1" - style="?android:attr/dropDownItemStyle" - android:singleLine="true" - android:layout_height="wrap_content" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_gravity="center_vertical" /> - -</LinearLayout> diff --git a/core/res/res/layout/search_dropdown_item_2line.xml b/core/res/res/layout/search_dropdown_item_2line.xml deleted file mode 100644 index 5546b6636bb9..000000000000 --- a/core/res/res/layout/search_dropdown_item_2line.xml +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml -** -** Copyright 2008, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="?android:attr/searchResultListItemHeight" - android:orientation="horizontal" - android:gravity="center_vertical" - android:baselineAligned="false" - > - - <TwoLineListItem - android:paddingTop="1dip" - android:paddingBottom="1dip" - android:gravity="center_vertical" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="wrap_content" - android:mode="twoLine" > - - <TextView - android:id="@android:id/text1" - style="?android:attr/dropDownItemStyle" - android:textAppearance="?android:attr/textAppearanceSearchResultTitle" - android:singleLine="true" - android:layout_width="fill_parent" - android:layout_height="wrap_content" /> - - <TextView - android:id="@android:id/text2" - style="?android:attr/dropDownItemStyle" - android:textAppearance="?android:attr/textAppearanceSearchResultSubtitle" - android:textColor="?android:attr/textColorSecondaryInverse" - android:singleLine="true" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@android:id/text1" - android:layout_alignLeft="@android:id/text1" /> - - </TwoLineListItem> - -</LinearLayout> diff --git a/core/res/res/layout/search_dropdown_item_icons_1line.xml b/core/res/res/layout/search_dropdown_item_icons_1line.xml deleted file mode 100644 index 4f65d746b4eb..000000000000 --- a/core/res/res/layout/search_dropdown_item_icons_1line.xml +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml -** -** Copyright 2008, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> - - <!-- NOTE: The appearance of the inner text element must match the appearance --> - <!-- of the text element in apps/common/res/layout/simple_dropdown_item_1line.xml --> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:paddingLeft="4dip" - android:paddingRight="2dip" - android:layout_width="fill_parent" - android:layout_height="?android:attr/searchResultListItemHeight" - android:orientation="horizontal" - android:gravity="center_vertical" - android:baselineAligned="false" - > - - <ImageView android:id="@android:id/icon1" - android:layout_width="48dip" - android:layout_height="48dip" - android:layout_gravity="center_vertical" - android:scaleType="centerInside" /> - - <TextView android:id="@android:id/text1" - style="?android:attr/dropDownItemStyle" - android:textAppearance="?android:attr/textAppearanceSearchResultTitle" - android:singleLine="true" - android:layout_height="wrap_content" - android:layout_width="0dip" - android:layout_weight="1" /> - - <ImageView android:id="@android:id/icon2" - android:layout_width="48dip" - android:layout_height="48dip" - android:layout_gravity="center_vertical" - android:scaleType="centerInside" /> - -</LinearLayout> diff --git a/core/res/res/values-he-rIL/donottranslate-cldr.xml b/core/res/res/values-he-rIL/donottranslate-cldr.xml index e3feb1e33146..3378ed7227ad 100644 --- a/core/res/res/values-he-rIL/donottranslate-cldr.xml +++ b/core/res/res/values-he-rIL/donottranslate-cldr.xml @@ -61,20 +61,20 @@ <string name="day_of_week_long_friday">יום שישי</string> <string name="day_of_week_long_saturday">יום שבת</string> - <string name="day_of_week_medium_sunday">יום א'</string> - <string name="day_of_week_medium_monday">יום ב'</string> - <string name="day_of_week_medium_tuesday">יום ג'</string> - <string name="day_of_week_medium_wednesday">יום ד'</string> - <string name="day_of_week_medium_thursday">יום ה'</string> - <string name="day_of_week_medium_friday">יום ו'</string> + <string name="day_of_week_medium_sunday">יום א\'</string> + <string name="day_of_week_medium_monday">יום ב\'</string> + <string name="day_of_week_medium_tuesday">יום ג\'</string> + <string name="day_of_week_medium_wednesday">יום ד\'</string> + <string name="day_of_week_medium_thursday">יום ה\'</string> + <string name="day_of_week_medium_friday">יום ו\'</string> <string name="day_of_week_medium_saturday">שבת</string> - <string name="day_of_week_short_sunday">יום א'</string> - <string name="day_of_week_short_monday">יום ב'</string> - <string name="day_of_week_short_tuesday">יום ג'</string> - <string name="day_of_week_short_wednesday">יום ד'</string> - <string name="day_of_week_short_thursday">יום ה'</string> - <string name="day_of_week_short_friday">יום ו'</string> + <string name="day_of_week_short_sunday">יום א\'</string> + <string name="day_of_week_short_monday">יום ב\'</string> + <string name="day_of_week_short_tuesday">יום ג\'</string> + <string name="day_of_week_short_wednesday">יום ד\'</string> + <string name="day_of_week_short_thursday">יום ה\'</string> + <string name="day_of_week_short_friday">יום ו\'</string> <string name="day_of_week_short_saturday">שבת</string> <string name="day_of_week_shortest_sunday">א</string> diff --git a/core/res/res/values-pt-rBR/donottranslate-cldr.xml b/core/res/res/values-pt-rBR/donottranslate-cldr.xml index 47290552dd2e..1111658ffb90 100644 --- a/core/res/res/values-pt-rBR/donottranslate-cldr.xml +++ b/core/res/res/values-pt-rBR/donottranslate-cldr.xml @@ -95,7 +95,7 @@ <string name="hour_minute_ampm">%-l:%M %p</string> <string name="hour_minute_cap_ampm">%-l:%M %^p</string> <string name="twelve_hour_time_format">h:mm a</string> - <string name="twenty_four_hour_time_format">H'h'mm</string> + <string name="twenty_four_hour_time_format">H\'h\'mm</string> <string name="numeric_date">%d/%m/%Y</string> <string name="numeric_date_format">dd/MM/yyyy</string> <string name="numeric_date_template">"%s/%s/%s"</string> diff --git a/core/res/res/values-pt-rPT/donottranslate-cldr.xml b/core/res/res/values-pt-rPT/donottranslate-cldr.xml index f38a2d0499d7..197cb6ed16fd 100644 --- a/core/res/res/values-pt-rPT/donottranslate-cldr.xml +++ b/core/res/res/values-pt-rPT/donottranslate-cldr.xml @@ -95,7 +95,7 @@ <string name="hour_minute_ampm">%-l:%M %p</string> <string name="hour_minute_cap_ampm">%-l:%M %^p</string> <string name="twelve_hour_time_format">h:mm a</string> - <string name="twenty_four_hour_time_format">H'h'mm</string> + <string name="twenty_four_hour_time_format">H\'h\'mm</string> <string name="numeric_date">%d/%m/%Y</string> <string name="numeric_date_format">dd/MM/yyyy</string> <string name="numeric_date_template">"%s/%s/%s"</string> diff --git a/core/res/res/values-pt/donottranslate-cldr.xml b/core/res/res/values-pt/donottranslate-cldr.xml index 47290552dd2e..1111658ffb90 100644 --- a/core/res/res/values-pt/donottranslate-cldr.xml +++ b/core/res/res/values-pt/donottranslate-cldr.xml @@ -95,7 +95,7 @@ <string name="hour_minute_ampm">%-l:%M %p</string> <string name="hour_minute_cap_ampm">%-l:%M %^p</string> <string name="twelve_hour_time_format">h:mm a</string> - <string name="twenty_four_hour_time_format">H'h'mm</string> + <string name="twenty_four_hour_time_format">H\'h\'mm</string> <string name="numeric_date">%d/%m/%Y</string> <string name="numeric_date_format">dd/MM/yyyy</string> <string name="numeric_date_template">"%s/%s/%s"</string> diff --git a/core/res/res/values-vi-rVN/donottranslate-cldr.xml b/core/res/res/values-vi-rVN/donottranslate-cldr.xml index 72ff8b68999c..6f2d3422e9fb 100644 --- a/core/res/res/values-vi-rVN/donottranslate-cldr.xml +++ b/core/res/res/values-vi-rVN/donottranslate-cldr.xml @@ -133,8 +133,8 @@ <string name="same_month_md1_time1_md2_time2">%3$s %2$s %5$s - %8$s %7$s %10$s</string> <string name="same_year_wday1_md1_time1_wday2_md2_time2">%1$s %3$s %2$s %5$s - %6$s %8$s %7$s %10$s</string> <string name="same_month_wday1_md1_time1_wday2_md2_time2">%1$s %3$s %2$s %5$s - %6$s %8$s %7$s %10$s</string> - <string name="same_year_mdy1_time1_mdy2_time2">Ngày %3$s tháng %2$s năm %4$s %5$s - 'Ngày %8$s tháng %7$s năm %9$s %10$s</string> - <string name="same_month_mdy1_time1_mdy2_time2">Ngày %3$s tháng %2$s năm %4$s %5$s - 'Ngày %8$s tháng %7$s năm %9$s %10$s</string> + <string name="same_year_mdy1_time1_mdy2_time2">Ngày %3$s tháng %2$s năm %4$s %5$s - \'Ngày %8$s tháng %7$s năm %9$s %10$s</string> + <string name="same_month_mdy1_time1_mdy2_time2">Ngày %3$s tháng %2$s năm %4$s %5$s - \'Ngày %8$s tháng %7$s năm %9$s %10$s</string> <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%1$s, %3$s %2$s %4$s %5$s - %6$s, %8$s %7$s %9$s %10$s</string> <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%1$s, %3$s %2$s %4$s %5$s - %6$s, %8$s %7$s %9$s %10$s</string> <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s %4$s - %6$s, %8$s %7$s %9$s</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 7d235ec8d4ef..8eda12e1f131 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -348,7 +348,8 @@ </style> <style name="Widget.TextView.ListSeparator.White"> - <item name="android:textColor">?textColorSecondaryInverse</item> + <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:background">@android:drawable/light_header</item> </style> <style name="Widget.EditText"> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index be836ebec4e5..e3fffb74f11a 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -325,6 +325,32 @@ <item name="android:windowContentOverlay">@null</item> <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item> + + <item name="textAppearance">@android:style/TextAppearance</item> + <item name="textAppearanceInverse">@android:style/TextAppearance.Inverse</item> + + <item name="textColorPrimary">@android:color/primary_text_dark</item> + <item name="textColorSecondary">@android:color/secondary_text_dark</item> + <item name="textColorTertiary">@android:color/tertiary_text_dark</item> + <item name="textColorPrimaryInverse">@android:color/primary_text_light</item> + <item name="textColorSecondaryInverse">@android:color/secondary_text_light</item> + <item name="textColorTertiaryInverse">@android:color/tertiary_text_light</item> + <item name="textColorPrimaryDisableOnly">@android:color/primary_text_dark_disable_only</item> + <item name="textColorPrimaryInverseDisableOnly">@android:color/primary_text_light_disable_only</item> + <item name="textColorPrimaryNoDisable">@android:color/primary_text_dark_nodisable</item> + <item name="textColorSecondaryNoDisable">@android:color/secondary_text_dark_nodisable</item> + <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_light_nodisable</item> + <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_light_nodisable</item> + <item name="textColorHint">@android:color/hint_foreground_dark</item> + <item name="textColorHintInverse">@android:color/hint_foreground_light</item> + <item name="textColorSearchUrl">@android:color/search_url_text</item> + + <item name="textAppearanceLarge">@android:style/TextAppearance.Large</item> + <item name="textAppearanceMedium">@android:style/TextAppearance.Medium</item> + <item name="textAppearanceSmall">@android:style/TextAppearance.Small</item> + <item name="textAppearanceLargeInverse">@android:style/TextAppearance.Large.Inverse</item> + <item name="textAppearanceMediumInverse">@android:style/TextAppearance.Medium.Inverse</item> + <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Small.Inverse</item> </style> <!-- Default theme for alert dialog windows, which is used by the diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 0069fe1765da..20aa6e787640 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -148,5 +148,7 @@ file="/system/framework/android.test.runner.jar" /> <library name="com.android.im.plugin" file="/system/framework/com.android.im.plugin.jar"/> + <library name="javax.obex" + file="/system/framework/javax.obex.jar"/> </permissions> diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk index aea3f0b3007b..649787efc511 100644 --- a/data/sounds/AudioPackage2.mk +++ b/data/sounds/AudioPackage2.mk @@ -23,7 +23,7 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \ $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \ $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \ - $(LOCAL_PATH)/Silence.ogg:system/media/audio/ringtones/Silence.ogg \ + $(LOCAL_PATH)/Silence.ogg:system/media/audio/ringtones/notifications/Silence.ogg \ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \ diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk index 8c8fc329c430..fc1e92109cac 100644 --- a/data/sounds/OriginalAudio.mk +++ b/data/sounds/OriginalAudio.mk @@ -22,7 +22,7 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \ $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \ $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \ - $(LOCAL_PATH)/Silence.ogg:system/media/audio/ringtones/Silence.ogg \ + $(LOCAL_PATH)/Silence.ogg:system/media/audio/ringtones/notifications/Silence.ogg \ $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \ $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \ $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \ diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index e2e93eb87e67..df659ef224de 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -18,6 +18,7 @@ package android.graphics; import android.os.Parcel; import android.os.Parcelable; +import android.util.DisplayMetrics; import java.io.OutputStream; import java.nio.Buffer; @@ -31,8 +32,6 @@ public final class Bitmap implements Parcelable { * * @see Bitmap#getDensityScale() * @see Bitmap#setDensityScale(float) - * - * @hide pending API council approval */ public static final float DENSITY_SCALE_UNKNOWN = -1.0f; @@ -84,11 +83,9 @@ public final class Bitmap implements Parcelable { * @see #setDensityScale(float) * @see #isAutoScalingEnabled() * @see #setAutoScalingEnabled(boolean) - * @see android.util.DisplayMetrics#DEFAULT_DENSITY + * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density * @see #DENSITY_SCALE_UNKNOWN - * - * @hide pending API council approval */ public float getDensityScale() { return mDensityScale; @@ -106,11 +103,9 @@ public final class Bitmap implements Parcelable { * @see #getDensityScale() * @see #isAutoScalingEnabled() * @see #setAutoScalingEnabled(boolean) - * @see android.util.DisplayMetrics#DEFAULT_DENSITY + * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density * @see #DENSITY_SCALE_UNKNOWN - * - * @hide pending API council approval */ public void setDensityScale(float densityScale) { mDensityScale = densityScale; @@ -132,8 +127,6 @@ public final class Bitmap implements Parcelable { * @see #setAutoScalingEnabled(boolean) * @see #getDensityScale() * @see #setDensityScale(float) - * - * @hide pending API council approval */ public boolean isAutoScalingEnabled() { return mAutoScaling; @@ -150,8 +143,6 @@ public final class Bitmap implements Parcelable { * the bitmap will never be automatically scaled at drawing time.</p> * * @param autoScalingEnabled True to scale the bitmap at drawing time, false otherwise. - * - * @hide pending API council approval */ public void setAutoScalingEnabled(boolean autoScalingEnabled) { mAutoScaling = autoScalingEnabled; @@ -465,8 +456,8 @@ public final class Bitmap implements Parcelable { // The new bitmap was created from a known bitmap source so assume that // they use the same density scale - bitmap.setDensityScale(source.getDensityScale()); - bitmap.setAutoScalingEnabled(source.isAutoScalingEnabled()); + bitmap.mDensityScale = source.mDensityScale; + bitmap.mAutoScaling = source.mAutoScaling; return bitmap; } @@ -615,26 +606,60 @@ public final class Bitmap implements Parcelable { * Convenience method that returns the width of this bitmap divided * by the density scale factor. * + * @param canvas The Canvas the bitmap will be drawn to. * @return The scaled width of this bitmap, according to the density scale factor. - * - * @hide pending API council approval */ - public int getScaledWidth() { - final float scale = getDensityScale(); - return scale == DENSITY_SCALE_UNKNOWN ? getWidth() : (int) (getWidth() / scale); + public int getScaledWidth(Canvas canvas) { + final float scale = mDensityScale; + if (!mAutoScaling || scale < 0) { + return getWidth(); + } + return (int)(getWidth() * canvas.getDensityScale() / scale); } /** * Convenience method that returns the height of this bitmap divided * by the density scale factor. * + * @param canvas The Canvas the bitmap will be drawn to. * @return The scaled height of this bitmap, according to the density scale factor. + */ + public int getScaledHeight(Canvas canvas) { + final float scale = mDensityScale; + if (!mAutoScaling || scale < 0) { + return getHeight(); + } + return (int)(getHeight() * canvas.getDensityScale() / scale); + } + + /** + * Convenience method that returns the width of this bitmap divided + * by the density scale factor. + * + * @param metrics The target display metrics. + * @return The scaled width of this bitmap, according to the density scale factor. + */ + public int getScaledWidth(DisplayMetrics metrics) { + final float scale = mDensityScale; + if (!mAutoScaling || scale < 0) { + return getWidth(); + } + return (int)(getWidth() * metrics.density / scale); + } + + /** + * Convenience method that returns the height of this bitmap divided + * by the density scale factor. * - * @hide pending API council approval + * @param metrics The target display metrics. + * @return The scaled height of this bitmap, according to the density scale factor. */ - public int getScaledHeight() { - final float scale = getDensityScale(); - return scale == DENSITY_SCALE_UNKNOWN ? getWidth() : (int) (getHeight() / scale); + public int getScaledHeight(DisplayMetrics metrics) { + final float scale = mDensityScale; + if (!mAutoScaling || scale < 0) { + return getHeight(); + } + return (int)(getHeight() * metrics.density / scale); } /** diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 2a3998776e2a..975bc1abb90f 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -82,10 +82,8 @@ public class BitmapFactory { /** * The desired pixel density of the bitmap. * - * @see android.util.DisplayMetrics#DEFAULT_DENSITY + * @see android.util.DisplayMetrics#DENSITY_DEFAULT * @see android.util.DisplayMetrics#density - * - * @hide pending API council approval */ public int inDensity; @@ -98,8 +96,6 @@ public class BitmapFactory { * a non-scaled version of the bitmap. In this case, * {@link android.graphics.Bitmap#setAutoScalingEnabled(boolean)} can be used * to properly scale the bitmap at drawing time.</p> - * - * @hide pending API council approval */ public boolean inScaled; @@ -130,6 +126,19 @@ public class BitmapFactory { public boolean inInputShareable; /** + * Normally bitmap allocations count against the dalvik heap, which + * means they help trigger GCs when a lot have been allocated. However, + * in rare cases, the caller may want to allocate the bitmap outside of + * that heap. To request that, set inNativeAlloc to true. In these + * rare instances, it is solely up to the caller to ensure that OOM is + * managed explicitly by calling bitmap.recycle() as soon as such a + * bitmap is no longer needed. + * + * @hide pending API council approval + */ + public boolean inNativeAlloc; + + /** * The resulting width of the bitmap, set independent of the state of * inJustDecodeBounds. However, if there is an error trying to decode, * outWidth will be set to -1. @@ -226,8 +235,6 @@ public class BitmapFactory { /** * Decode a new Bitmap from an InputStream. This InputStream was obtained from * resources, which we pass to be able to scale the bitmap accordingly. - * - * @hide */ public static Bitmap decodeStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { @@ -239,15 +246,19 @@ public class BitmapFactory { Bitmap bm = decodeStream(is, pad, opts); if (bm != null && res != null && value != null) { + final int density = value.density; + if (density == TypedValue.DENSITY_NONE) { + return bm; + } + byte[] np = bm.getNinePatchChunk(); final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); - final int density = value.density; if (opts.inDensity == 0) { opts.inDensity = density == TypedValue.DENSITY_DEFAULT ? - DisplayMetrics.DEFAULT_DENSITY : density; + DisplayMetrics.DENSITY_DEFAULT : density; } - float scale = opts.inDensity / (float) DisplayMetrics.DEFAULT_DENSITY; + float scale = opts.inDensity / (float) DisplayMetrics.DENSITY_DEFAULT; if (opts.inScaled || isNinePatch) { bm.setDensityScale(1.0f); diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 4498e1a2e117..da7359772ee3 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -184,8 +184,6 @@ public class Canvas { * * @see #setDensityScale(float) * @see Bitmap#getDensityScale() - * - * @hide pending API council approval */ public float getDensityScale() { if (mBitmap != null) { @@ -205,8 +203,6 @@ public class Canvas { * * @see #getDensityScale() * @see Bitmap#setDensityScale(float) - * - * @hide pending API council approval */ public void setDensityScale(float densityScale) { if (mBitmap != null) { diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h index f6faf144dc5e..39b5e57b1fe3 100644 --- a/include/media/IMediaPlayerService.h +++ b/include/media/IMediaPlayerService.h @@ -29,6 +29,7 @@ namespace android { class IMediaRecorder; +class IOMX; class IMediaPlayerService: public IInterface { @@ -41,6 +42,7 @@ public: virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0; virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; + virtual sp<IOMX> createOMX() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/IOMX.h b/include/media/IOMX.h new file mode 100644 index 000000000000..5c61c50ee22b --- /dev/null +++ b/include/media/IOMX.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_IOMX_H_ + +#define ANDROID_IOMX_H_ + +#include <binder/IInterface.h> +#include <utils/List.h> +#include <utils/String8.h> + +#include <OMX_Core.h> + +#define IOMX_USES_SOCKETS 0 + +namespace android { + +class IMemory; +class IOMXObserver; + +class IOMX : public IInterface { +public: + DECLARE_META_INTERFACE(OMX); + + typedef void *buffer_id; + typedef void *node_id; + +#if IOMX_USES_SOCKETS + // If successful, returns a socket descriptor used for further + // communication. Caller assumes ownership of "*sd". + virtual status_t connect(int *sd) = 0; +#endif + + virtual status_t list_nodes(List<String8> *list) = 0; + + virtual status_t allocate_node(const char *name, node_id *node) = 0; + virtual status_t free_node(node_id node) = 0; + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) = 0; + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) = 0; + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) = 0; + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) = 0; + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) = 0; + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) = 0; + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer) = 0; + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer) = 0; + + virtual void fill_buffer(node_id node, buffer_id buffer) = 0; + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) = 0; +#endif +}; + +struct omx_message { + enum { + EVENT, + EMPTY_BUFFER_DONE, + FILL_BUFFER_DONE, + +#if IOMX_USES_SOCKETS + EMPTY_BUFFER, + FILL_BUFFER, + SEND_COMMAND, + DISCONNECT, + DISCONNECTED, +#endif + + // reserved for OMXDecoder use. + START, + INITIAL_FILL_BUFFER, + + // reserved for OMXObserver use. + QUIT_OBSERVER, + } type; + + union { + // if type == EVENT + struct { + IOMX::node_id node; + OMX_EVENTTYPE event; + OMX_U32 data1; + OMX_U32 data2; + } event_data; + + // if type == EMPTY_BUFFER_DONE || type == FILL_BUFFER + // || type == INITIAL_FILL_BUFFER + struct { + IOMX::node_id node; + IOMX::buffer_id buffer; + } buffer_data; + + // if type == EMPTY_BUFFER || type == FILL_BUFFER_DONE + struct { + IOMX::node_id node; + IOMX::buffer_id buffer; + OMX_U32 range_offset; + OMX_U32 range_length; + OMX_U32 flags; + OMX_TICKS timestamp; + OMX_PTR platform_private; // ignored if type == EMPTY_BUFFER + } extended_buffer_data; + + // if type == SEND_COMMAND + struct { + IOMX::node_id node; + OMX_COMMANDTYPE cmd; + OMX_S32 param; + } send_command_data; + + } u; +}; + +class IOMXObserver : public IInterface { +public: + DECLARE_META_INTERFACE(OMXObserver); + + virtual void on_message(const omx_message &msg) = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class BnOMX : public BnInterface<IOMX> { +public: + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +class BnOMXObserver : public BnInterface<IOMXObserver> { +public: + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +} // namespace android + +#endif // ANDROID_IOMX_H_ diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 21600b2de9aa..97d55aa0537a 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -19,20 +19,30 @@ #ifdef __cplusplus +#include <sys/types.h> #include <ui/ISurface.h> #include <utils/RefBase.h> +#include <utils/Errors.h> #include <media/mediaplayer.h> #include <media/AudioSystem.h> namespace android { +typedef int32_t MetadataType; + class Parcel; +template<typename T> class SortedVector; enum player_type { PV_PLAYER = 1, SONIVOX_PLAYER = 2, - VORBIS_PLAYER = 3 + VORBIS_PLAYER = 3, + STAGEFRIGHT_PLAYER = 4, + // Test players are available only in the 'test' and 'eng' builds. + // The shared library with the test player is passed passed as an + // argument to the 'test:' url in the setDataSource call. + TEST_PLAYER = 5, }; @@ -51,6 +61,9 @@ public: // AudioSink: abstraction layer for audio output class AudioSink : public RefBase { public: + typedef void (*AudioCallback)( + AudioSink *audioSink, void *buffer, size_t size, void *cookie); + virtual ~AudioSink() {} virtual bool ready() const = 0; // audio output is open and ready virtual bool realtime() const = 0; // audio output is real-time output @@ -60,7 +73,17 @@ public: virtual ssize_t frameSize() const = 0; virtual uint32_t latency() const = 0; virtual float msecsPerFrame() const = 0; - virtual status_t open(uint32_t sampleRate, int channelCount, int format=AudioSystem::PCM_16_BIT, int bufferCount=DEFAULT_AUDIOSINK_BUFFERCOUNT) = 0; + + // If no callback is specified, use the "write" API below to submit + // audio data. Otherwise return a full buffer of audio data on each + // callback. + virtual status_t open( + uint32_t sampleRate, int channelCount, + int format=AudioSystem::PCM_16_BIT, + int bufferCount=DEFAULT_AUDIOSINK_BUFFERCOUNT, + AudioCallback cb = NULL, + void *cookie = NULL) = 0; + virtual void start() = 0; virtual ssize_t write(const void* buffer, size_t size) = 0; virtual void stop() = 0; @@ -92,12 +115,23 @@ public: mCookie = cookie; mNotify = notifyFunc; } // Invoke a generic method on the player by using opaque parcels // for the request and reply. + // // @param request Parcel that is positioned at the start of the // data sent by the java layer. // @param[out] reply Parcel to hold the reply data. Cannot be null. - // @return OK if the invocation was made successfully. A player - // not supporting the direct API should return INVALID_OPERATION. + // @return OK if the call was successful. virtual status_t invoke(const Parcel& request, Parcel *reply) = 0; + + // The Client in the MetadataPlayerService calls this method on + // the native player to retrieve all or a subset of metadata. + // + // @param ids SortedList of metadata ID to be fetch. If empty, all + // the known metadata should be returned. + // @param[inout] records Parcel where the player appends its metadata. + // @return OK if the call was successful. + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records) = 0; + protected: virtual void sendEvent(int msg, int ext1=0, int ext2=0) { if (mNotify) mNotify(mCookie, msg, ext1, ext2); } diff --git a/include/media/PVPlayer.h b/include/media/PVPlayer.h index eb4595ba156a..40ccc14b2ecb 100644 --- a/include/media/PVPlayer.h +++ b/include/media/PVPlayer.h @@ -53,6 +53,8 @@ public: virtual status_t setLooping(int loop); virtual player_type playerType() { return PV_PLAYER; } virtual status_t invoke(const Parcel& request, Parcel *reply); + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records); // make available to PlayerDriver void sendEvent(int msg, int ext1=0, int ext2=0) { MediaPlayerBase::sendEvent(msg, ext1, ext2); } @@ -63,6 +65,7 @@ private: static void run_set_video_surface(status_t s, void *cookie, bool cancelled); static void run_set_audio_output(status_t s, void *cookie, bool cancelled); static void run_prepare(status_t s, void *cookie, bool cancelled); + static void check_for_live_streaming(status_t s, void *cookie, bool cancelled); PlayerDriver* mPlayerDriver; char * mDataSourcePath; diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h new file mode 100644 index 000000000000..0f2e52884485 --- /dev/null +++ b/include/media/stagefright/AudioPlayer.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AUDIO_PLAYER_H_ + +#define AUDIO_PLAYER_H_ + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/TimeSource.h> +#include <utils/threads.h> + +namespace android { + +class MediaSource; +class AudioTrack; + +class AudioPlayer : public TimeSource { +public: + AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink); + ~AudioPlayer(); + + // Caller retains ownership of "source". + void setSource(MediaSource *source); + + // Return time in us. + virtual int64_t getRealTimeUs(); + + void start(); + + void pause(); + void resume(); + + void stop(); + + // Returns the timestamp of the last buffer played (in us). + int64_t getMediaTimeUs(); + + // Returns true iff a mapping is established, i.e. the AudioPlayer + // has played at least one frame of audio. + bool getMediaTimeMapping(int64_t *realtime_us, int64_t *mediatime_us); + + status_t seekTo(int64_t time_us); + +private: + MediaSource *mSource; + AudioTrack *mAudioTrack; + + MediaBuffer *mInputBuffer; + + int mSampleRate; + int64_t mLatencyUs; + size_t mFrameSize; + + Mutex mLock; + int64_t mNumFramesPlayed; + + int64_t mPositionTimeMediaUs; + int64_t mPositionTimeRealUs; + + bool mSeeking; + int64_t mSeekTimeUs; + + bool mStarted; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + + static void AudioCallback(int event, void *user, void *info); + void AudioCallback(int event, void *info); + + static void AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *data, size_t size, void *me); + + void fillBuffer(void *data, size_t size); + + int64_t getRealTimeUsLocked() const; + + AudioPlayer(const AudioPlayer &); + AudioPlayer &operator=(const AudioPlayer &); +}; + +} // namespace android + +#endif // AUDIO_PLAYER_H_ diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h new file mode 100644 index 000000000000..e12995802ff7 --- /dev/null +++ b/include/media/stagefright/AudioSource.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AUDIO_SOURCE_H_ + +#define AUDIO_SOURCE_H_ + +#include <media/stagefright/MediaSource.h> + +namespace android { + +class AudioRecord; + +class AudioSource { +public: + AudioSource(int inputSource); + virtual ~AudioSource(); + + status_t initCheck() const; + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + AudioRecord *mRecord; + status_t mInitCheck; + + AudioSource(const AudioSource &); + AudioSource &operator=(const AudioSource &); +}; + +} // namespace android + +#endif // AUDIO_SOURCE_H_ diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h new file mode 100644 index 000000000000..e275cb42672c --- /dev/null +++ b/include/media/stagefright/CachingDataSource.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CACHING_DATASOURCE_H_ + +#define CACHING_DATASOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class CachingDataSource : public DataSource { +public: + // Assumes ownership of "source". + CachingDataSource(DataSource *source, size_t pageSize, int numPages); + virtual ~CachingDataSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + struct Page { + Page *mPrev, *mNext; + off_t mOffset; + size_t mLength; + void *mData; + }; + + DataSource *mSource; + void *mData; + size_t mPageSize; + Page *mFirst, *mLast; + + Page *allocate_page(); + + Mutex mLock; + + CachingDataSource(const CachingDataSource &); + CachingDataSource &operator=(const CachingDataSource &); +}; + +} // namespace android + +#endif // CACHING_DATASOURCE_H_ diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h new file mode 100644 index 000000000000..7042e1ad4960 --- /dev/null +++ b/include/media/stagefright/CameraSource.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMERA_SOURCE_H_ + +#define CAMERA_SOURCE_H_ + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class ICamera; +class ICameraClient; +class IMemory; + +class CameraSource : public MediaSource, + public MediaBufferObserver { +public: + static CameraSource *Create(); + + virtual ~CameraSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void dataCallback(int32_t msgType, const sp<IMemory>& data); + + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + CameraSource(const sp<ICamera> &camera, const sp<ICameraClient> &client); + + sp<ICamera> mCamera; + sp<ICameraClient> mCameraClient; + + Mutex mLock; + Condition mFrameAvailableCondition; + List<sp<IMemory> > mFrames; + + int mNumFrames; + bool mStarted; + + CameraSource(const CameraSource &); + CameraSource &operator=(const CameraSource &); +}; + +} // namespace android + +#endif // CAMERA_SOURCE_H_ diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h new file mode 100644 index 000000000000..31eea27429ac --- /dev/null +++ b/include/media/stagefright/DataSource.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DATA_SOURCE_H_ + +#define DATA_SOURCE_H_ + +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class String8; + +class DataSource { +public: + DataSource() {} + virtual ~DataSource() {} + + virtual ssize_t read_at(off_t offset, void *data, size_t size) = 0; + + // May return ERROR_UNSUPPORTED. + virtual status_t getSize(off_t *size); + + //////////////////////////////////////////////////////////////////////////// + + bool sniff(String8 *mimeType, float *confidence); + + typedef bool (*SnifferFunc)( + DataSource *source, String8 *mimeType, float *confidence); + + static void RegisterSniffer(SnifferFunc func); + static void RegisterDefaultSniffers(); + +private: + static Mutex gSnifferMutex; + static List<SnifferFunc> gSniffers; + + DataSource(const DataSource &); + DataSource &operator=(const DataSource &); +}; + +} // namespace android + +#endif // DATA_SOURCE_H_ diff --git a/include/media/stagefright/ESDS.h b/include/media/stagefright/ESDS.h new file mode 100644 index 000000000000..01bcd1831806 --- /dev/null +++ b/include/media/stagefright/ESDS.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ESDS_H_ + +#define ESDS_H_ + +#include <stdint.h> + +#include <media/stagefright/MediaErrors.h> + +namespace android { + +class ESDS { +public: + ESDS(const void *data, size_t size); + ~ESDS(); + + status_t InitCheck() const; + + status_t getCodecSpecificInfo(const void **data, size_t *size) const; + +private: + enum { + kTag_ESDescriptor = 0x03, + kTag_DecoderConfigDescriptor = 0x04, + kTag_DecoderSpecificInfo = 0x05 + }; + + uint8_t *mData; + size_t mSize; + + status_t mInitCheck; + + size_t mDecoderSpecificOffset; + size_t mDecoderSpecificLength; + + status_t skipDescriptorHeader( + size_t offset, size_t size, + uint8_t *tag, size_t *data_offset, size_t *data_size) const; + + status_t parse(); + status_t parseESDescriptor(size_t offset, size_t size); + status_t parseDecoderConfigDescriptor(size_t offset, size_t size); + + ESDS(const ESDS &); + ESDS &operator=(const ESDS &); +}; + +} // namespace android +#endif // ESDS_H_ diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h new file mode 100644 index 000000000000..ccbe0efa10bd --- /dev/null +++ b/include/media/stagefright/FileSource.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILE_SOURCE_H_ + +#define FILE_SOURCE_H_ + +#include <stdio.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class FileSource : public DataSource { +public: + FileSource(const char *filename); + virtual ~FileSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + FILE *mFile; + Mutex mLock; + + FileSource(const FileSource &); + FileSource &operator=(const FileSource &); +}; + +} // namespace android + +#endif // FILE_SOURCE_H_ + diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h new file mode 100644 index 000000000000..0587c7cf1d50 --- /dev/null +++ b/include/media/stagefright/HTTPDataSource.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_DATASOURCE_H_ + +#define HTTP_DATASOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/HTTPStream.h> + +namespace android { + +class HTTPDataSource : public DataSource { +public: + HTTPDataSource(const char *host, int port, const char *path); + HTTPDataSource(const char *uri); + + virtual ~HTTPDataSource(); + + // XXXandih + status_t InitCheck() const { return OK; } + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + enum { + kBufferSize = 64 * 1024 + }; + + HTTPStream mHttp; + char *mHost; + int mPort; + char *mPath; + + void *mBuffer; + size_t mBufferLength; + off_t mBufferOffset; + + HTTPDataSource(const HTTPDataSource &); + HTTPDataSource &operator=(const HTTPDataSource &); +}; + +} // namespace android + +#endif // HTTP_DATASOURCE_H_ + diff --git a/include/media/stagefright/HTTPStream.h b/include/media/stagefright/HTTPStream.h new file mode 100644 index 000000000000..3d0d67a26a0d --- /dev/null +++ b/include/media/stagefright/HTTPStream.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_STREAM_H_ + +#define HTTP_STREAM_H_ + +#include <sys/types.h> + +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/string.h> +#include <utils/KeyedVector.h> + +namespace android { + +class HTTPStream { +public: + HTTPStream(); + ~HTTPStream(); + + status_t connect(const char *server, int port = 80); + status_t disconnect(); + + status_t send(const char *data, size_t size); + + // Assumes data is a '\0' terminated string. + status_t send(const char *data); + + // Receive up to "size" bytes of data. + ssize_t receive(void *data, size_t size); + + status_t receive_header(int *http_status); + + // The header key used to retrieve the status line. + static const char *kStatusKey; + + bool find_header_value( + const string &key, string *value) const; + +private: + enum State { + READY, + CONNECTED + }; + + State mState; + int mSocket; + + KeyedVector<string, string> mHeaders; + + // Receive a line of data terminated by CRLF, line will be '\0' terminated + // _excluding_ the termianting CRLF. + status_t receive_line(char *line, size_t size); + + HTTPStream(const HTTPStream &); + HTTPStream &operator=(const HTTPStream &); +}; + +} // namespace android + +#endif // HTTP_STREAM_H_ diff --git a/include/media/stagefright/MP3Extractor.h b/include/media/stagefright/MP3Extractor.h new file mode 100644 index 000000000000..09cfb70aab8c --- /dev/null +++ b/include/media/stagefright/MP3Extractor.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MP3_EXTRACTOR_H_ + +#define MP3_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class DataSource; +class String8; + +class MP3Extractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + MP3Extractor(DataSource *source); + + ~MP3Extractor(); + + status_t countTracks(int *num_tracks); + status_t getTrack(int index, MediaSource **source); + sp<MetaData> getTrackMetaData(int index); + +private: + DataSource *mDataSource; + off_t mFirstFramePos; + sp<MetaData> mMeta; + uint32_t mFixedHeader; + + MP3Extractor(const MP3Extractor &); + MP3Extractor &operator=(const MP3Extractor &); +}; + +bool SniffMP3(DataSource *source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MP3_EXTRACTOR_H_ diff --git a/include/media/stagefright/MPEG4Extractor.h b/include/media/stagefright/MPEG4Extractor.h new file mode 100644 index 000000000000..51a7e829281e --- /dev/null +++ b/include/media/stagefright/MPEG4Extractor.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MPEG4_EXTRACTOR_H_ + +#define MPEG4_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class DataSource; +class SampleTable; +class String8; + +class MPEG4Extractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + MPEG4Extractor(DataSource *source); + ~MPEG4Extractor(); + + status_t countTracks(int *num_tracks); + status_t getTrack(int index, MediaSource **source); + sp<MetaData> getTrackMetaData(int index); + +private: + struct Track { + Track *next; + sp<MetaData> meta; + uint32_t timescale; + SampleTable *sampleTable; + }; + + DataSource *mDataSource; + bool mHaveMetadata; + + Track *mFirstTrack, *mLastTrack; + + uint32_t mHandlerType; + + status_t readMetaData(); + status_t parseChunk(off_t *offset, int depth); + + MPEG4Extractor(const MPEG4Extractor &); + MPEG4Extractor &operator=(const MPEG4Extractor &); +}; + +bool SniffMPEG4(DataSource *source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MPEG4_EXTRACTOR_H_ diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h new file mode 100644 index 000000000000..40d612769375 --- /dev/null +++ b/include/media/stagefright/MPEG4Writer.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MPEG4_WRITER_H_ + +#define MPEG4_WRITER_H_ + +#include <stdio.h> + +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class MediaBuffer; +class MediaSource; +class MetaData; + +class MPEG4Writer { +public: + MPEG4Writer(const char *filename); + ~MPEG4Writer(); + + // Caller retains ownership of both meta and source. + void addSource(const sp<MetaData> &meta, MediaSource *source); + void start(); + void stop(); + + void beginBox(const char *fourcc); + void writeInt8(int8_t x); + void writeInt16(int16_t x); + void writeInt32(int32_t x); + void writeInt64(int64_t x); + void writeCString(const char *s); + void writeFourcc(const char *fourcc); + void write(const void *data, size_t size); + void endBox(); + +private: + class Track; + + FILE *mFile; + off_t mOffset; + off_t mMdatOffset; + Mutex mLock; + + List<Track *> mTracks; + + List<off_t> mBoxes; + + off_t addSample(MediaBuffer *buffer); + + MPEG4Writer(const MPEG4Writer &); + MPEG4Writer &operator=(const MPEG4Writer &); +}; + +} // namespace android + +#endif // MPEG4_WRITER_H_ diff --git a/include/media/stagefright/MediaBuffer.h b/include/media/stagefright/MediaBuffer.h new file mode 100644 index 000000000000..c72ed66d46bd --- /dev/null +++ b/include/media/stagefright/MediaBuffer.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_BUFFER_H_ + +#define MEDIA_BUFFER_H_ + +#include <pthread.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +class MediaBuffer; +class MediaBufferObserver; +class MetaData; + +class MediaBufferObserver { +public: + MediaBufferObserver() {} + virtual ~MediaBufferObserver() {} + + virtual void signalBufferReturned(MediaBuffer *buffer) = 0; + +private: + MediaBufferObserver(const MediaBufferObserver &); + MediaBufferObserver &operator=(const MediaBufferObserver &); +}; + +class MediaBuffer { +public: + // The underlying data remains the responsibility of the caller! + MediaBuffer(void *data, size_t size); + + MediaBuffer(size_t size); + + // Decrements the reference count and returns the buffer to its + // associated MediaBufferGroup if the reference count drops to 0. + void release(); + + // Increments the reference count. + void add_ref(); + + void *data() const; + size_t size() const; + + size_t range_offset() const; + size_t range_length() const; + + void set_range(size_t offset, size_t length); + + sp<MetaData> meta_data(); + + // Clears meta data and resets the range to the full extent. + void reset(); + + void setObserver(MediaBufferObserver *group); + + // Returns a clone of this MediaBuffer increasing its reference count. + // The clone references the same data but has its own range and + // MetaData. + MediaBuffer *clone(); + +protected: + virtual ~MediaBuffer(); + +private: + friend class MediaBufferGroup; + friend class OMXDecoder; + + // For use by OMXDecoder, reference count must be 1, drop reference + // count to 0 without signalling the observer. + void claim(); + + MediaBufferObserver *mObserver; + MediaBuffer *mNextBuffer; + int mRefCount; + + void *mData; + size_t mSize, mRangeOffset, mRangeLength; + + bool mOwnsData; + + sp<MetaData> mMetaData; + + MediaBuffer *mOriginal; + + void setNextBuffer(MediaBuffer *buffer); + MediaBuffer *nextBuffer(); + + int refcount() const; + + MediaBuffer(const MediaBuffer &); + MediaBuffer &operator=(const MediaBuffer &); +}; + +} // namespace android + +#endif // MEDIA_BUFFER_H_ diff --git a/include/media/stagefright/MediaBufferGroup.h b/include/media/stagefright/MediaBufferGroup.h new file mode 100644 index 000000000000..e95a9c27897c --- /dev/null +++ b/include/media/stagefright/MediaBufferGroup.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_BUFFER_GROUP_H_ + +#define MEDIA_BUFFER_GROUP_H_ + +#include <utils/Errors.h> +#include <utils/threads.h> + +namespace android { + +class MediaBuffer; +class MetaData; + +class MediaBufferGroup : public MediaBufferObserver { +public: + MediaBufferGroup(); + ~MediaBufferGroup(); + + void add_buffer(MediaBuffer *buffer); + + // Blocks until a buffer is available and returns it to the caller, + // the returned buffer will have a reference count of 1. + status_t acquire_buffer(MediaBuffer **buffer); + +protected: + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + friend class MediaBuffer; + + Mutex mLock; + Condition mCondition; + + MediaBuffer *mFirstBuffer, *mLastBuffer; + + MediaBufferGroup(const MediaBufferGroup &); + MediaBufferGroup &operator=(const MediaBufferGroup &); +}; + +} // namespace android + +#endif // MEDIA_BUFFER_GROUP_H_ diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h new file mode 100644 index 000000000000..2bb0ed6ce0b9 --- /dev/null +++ b/include/media/stagefright/MediaErrors.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_ERRORS_H_ + +#define MEDIA_ERRORS_H_ + +#include <utils/Errors.h> + +namespace android { + +enum { + MEDIA_ERROR_BASE = -1000, + + ERROR_ALREADY_CONNECTED = MEDIA_ERROR_BASE, + ERROR_NOT_CONNECTED = MEDIA_ERROR_BASE - 1, + ERROR_UNKNOWN_HOST = MEDIA_ERROR_BASE - 2, + ERROR_CANNOT_CONNECT = MEDIA_ERROR_BASE - 3, + ERROR_IO = MEDIA_ERROR_BASE - 4, + ERROR_CONNECTION_LOST = MEDIA_ERROR_BASE - 5, + ERROR_MALFORMED = MEDIA_ERROR_BASE - 7, + ERROR_OUT_OF_RANGE = MEDIA_ERROR_BASE - 8, + ERROR_BUFFER_TOO_SMALL = MEDIA_ERROR_BASE - 9, + ERROR_UNSUPPORTED = MEDIA_ERROR_BASE - 10, + ERROR_END_OF_STREAM = MEDIA_ERROR_BASE - 11, +}; + +} // namespace android + +#endif // MEDIA_ERRORS_H_ diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h new file mode 100644 index 000000000000..38f8e5bc3118 --- /dev/null +++ b/include/media/stagefright/MediaExtractor.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_EXTRACTOR_H_ + +#define MEDIA_EXTRACTOR_H_ + +#include <utils/RefBase.h> + +namespace android { + +class DataSource; +class MediaSource; +class MetaData; + +class MediaExtractor { +public: + static MediaExtractor *Create(DataSource *source, const char *mime = NULL); + + virtual ~MediaExtractor() {} + + virtual status_t countTracks(int *num_tracks) = 0; + virtual status_t getTrack(int index, MediaSource **source) = 0; + virtual sp<MetaData> getTrackMetaData(int index) = 0; + +protected: + MediaExtractor() {} + +private: + MediaExtractor(const MediaExtractor &); + MediaExtractor &operator=(const MediaExtractor &); +}; + +} // namespace android + +#endif // MEDIA_EXTRACTOR_H_ diff --git a/include/media/stagefright/MediaPlayerImpl.h b/include/media/stagefright/MediaPlayerImpl.h new file mode 100644 index 000000000000..c48400c530e9 --- /dev/null +++ b/include/media/stagefright/MediaPlayerImpl.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_PLAYER_IMPL_H_ + +#define MEDIA_PLAYER_IMPL_H_ + +#include <pthread.h> + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/OMXClient.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class AudioPlayer; +class ISurface; +class MediaExtractor; +class MediaBuffer; +class MediaSource; +class MemoryHeapPmem; +class MetaData; +class OMXDecoder; +class Surface; +class TimeSource; +class VideoRenderer; + +class MediaPlayerImpl { +public: + MediaPlayerImpl(const char *uri); + + status_t initCheck() const; + + // Assumes ownership of "fd". + MediaPlayerImpl(int fd, int64_t offset, int64_t length); + + ~MediaPlayerImpl(); + + void play(); + void pause(); + bool isPlaying() const; + + void setSurface(const sp<Surface> &surface); + void setISurface(const sp<ISurface> &isurface); + + void setAudioSink(const sp<MediaPlayerBase::AudioSink> &audioSink); + + int32_t getWidth() const { return mVideoWidth; } + int32_t getHeight() const { return mVideoHeight; } + + int64_t getDuration(); + int64_t getPosition(); + status_t seekTo(int64_t time); + +private: + status_t mInitCheck; + + OMXClient mClient; + + MediaExtractor *mExtractor; + + TimeSource *mTimeSource; + + MediaSource *mAudioSource; + OMXDecoder *mAudioDecoder; + AudioPlayer *mAudioPlayer; + + MediaSource *mVideoSource; + MediaSource *mVideoDecoder; + int32_t mVideoWidth, mVideoHeight; + int64_t mVideoPosition; + + int64_t mDuration; + + bool mPlaying; + bool mPaused; + + int64_t mTimeSourceDeltaUs; + + sp<Surface> mSurface; + sp<ISurface> mISurface; + VideoRenderer *mRenderer; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + + Mutex mLock; + pthread_t mVideoThread; + + bool mSeeking; + int64_t mSeekTimeUs; + + size_t mFrameSize; + bool mUseSoftwareColorConversion; + + void init(); + + static void *VideoWrapper(void *me); + void videoEntry(); + + void setAudioSource(MediaSource *source); + void setVideoSource(MediaSource *source); + + MediaSource *makeShoutcastSource(const char *path); + + void displayOrDiscardFrame(MediaBuffer *buffer, int64_t pts_us); + void populateISurface(); + void depopulateISurface(); + void sendFrameToISurface(MediaBuffer *buffer); + + void stop(); + + MediaPlayerImpl(const MediaPlayerImpl &); + MediaPlayerImpl &operator=(const MediaPlayerImpl &); +}; + +} // namespace android + +#endif // MEDIA_PLAYER_IMPL_H_ diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h new file mode 100644 index 000000000000..eb07f6857b39 --- /dev/null +++ b/include/media/stagefright/MediaSource.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEDIA_SOURCE_H_ + +#define MEDIA_SOURCE_H_ + +#include <sys/types.h> + +#include <utils/RefBase.h> + +namespace android { + +class MediaBuffer; +class MetaData; + +struct MediaSource { + MediaSource(); + virtual ~MediaSource(); + + // To be called before any other methods on this object, except + // getFormat(). + virtual status_t start(MetaData *params = NULL) = 0; + + // Any blocking read call returns immediately with a result of NO_INIT. + // It is an error to call any methods other than start after this call + // returns. Any buffers the object may be holding onto at the time of + // the stop() call are released. + // Also, it is imperative that any buffers output by this object and + // held onto by callers be released before a call to stop() !!! + virtual status_t stop() = 0; + + // Returns the format of the data output by this media source. + virtual sp<MetaData> getFormat() = 0; + + struct ReadOptions; + + // Returns a new buffer of data. Call blocks until a + // buffer is available, an error is encountered of the end of the stream + // is reached. + // End of stream is signalled by a result of ERROR_END_OF_STREAM. + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL) = 0; + + // Options that modify read() behaviour. The default is to + // a) not request a seek + // b) not be late, i.e. lateness_us = 0 + struct ReadOptions { + ReadOptions(); + + // Reset everything back to defaults. + void reset(); + + void setSeekTo(int64_t time_us); + void clearSeekTo(); + bool getSeekTo(int64_t *time_us) const; + + void setLateBy(int64_t lateness_us); + int64_t getLateBy() const; + + private: + enum Options { + kSeekTo_Option = 1, + }; + + uint32_t mOptions; + int64_t mSeekTimeUs; + int64_t mLatenessUs; + }; + +private: + MediaSource(const MediaSource &); + MediaSource &operator=(const MediaSource &); +}; + +} // namespace android + +#endif // MEDIA_SOURCE_H_ diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h new file mode 100644 index 000000000000..04805dab5c9d --- /dev/null +++ b/include/media/stagefright/MetaData.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef META_DATA_H_ + +#define META_DATA_H_ + +#include <sys/types.h> + +#include <stdint.h> + +#include <utils/RefBase.h> +#include <utils/KeyedVector.h> + +namespace android { + +enum { + kKeyMIMEType = 'mime', + kKeyWidth = 'widt', + kKeyHeight = 'heig', + kKeyChannelCount = '#chn', + kKeySampleRate = 'srte', + kKeyBitRate = 'brte', + kKeyESDS = 'esds', + kKeyAVCC = 'avcc', + kKeyTimeUnits = '#tim', + kKeyTimeScale = 'scal', + kKeyNeedsNALFraming = 'NALf', + kKeyIsSyncFrame = 'sync', + kKeyDuration = 'dura', + kKeyColorFormat = 'colf', + kKeyPlatformPrivate = 'priv', + kKeyDecoderComponent = 'decC', +}; + +enum { + kTypeESDS = 'esds', + kTypeAVCC = 'avcc', +}; + +class MetaData : public RefBase { +public: + MetaData(); + MetaData(const MetaData &from); + + enum Type { + TYPE_NONE = 'none', + TYPE_C_STRING = 'cstr', + TYPE_INT32 = 'in32', + TYPE_FLOAT = 'floa', + TYPE_POINTER = 'ptr ', + }; + + void clear(); + bool remove(uint32_t key); + + bool setCString(uint32_t key, const char *value); + bool setInt32(uint32_t key, int32_t value); + bool setFloat(uint32_t key, float value); + bool setPointer(uint32_t key, void *value); + + bool findCString(uint32_t key, const char **value); + bool findInt32(uint32_t key, int32_t *value); + bool findFloat(uint32_t key, float *value); + bool findPointer(uint32_t key, void **value); + + bool setData(uint32_t key, uint32_t type, const void *data, size_t size); + + bool findData(uint32_t key, uint32_t *type, + const void **data, size_t *size) const; + +protected: + virtual ~MetaData(); + +private: + struct typed_data { + typed_data(); + ~typed_data(); + + typed_data(const MetaData::typed_data &); + typed_data &operator=(const MetaData::typed_data &); + + void clear(); + void setData(uint32_t type, const void *data, size_t size); + void getData(uint32_t *type, const void **data, size_t *size) const; + + private: + uint32_t mType; + size_t mSize; + + union { + void *ext_data; + float reservoir; + } u; + + bool usesReservoir() const { + return mSize <= sizeof(u.reservoir); + } + + void allocateStorage(size_t size); + void freeStorage(); + + void *storage() { + return usesReservoir() ? &u.reservoir : u.ext_data; + } + + const void *storage() const { + return usesReservoir() ? &u.reservoir : u.ext_data; + } + }; + + KeyedVector<uint32_t, typed_data> mItems; + + // MetaData &operator=(const MetaData &); +}; + +} // namespace android + +#endif // META_DATA_H_ diff --git a/include/media/stagefright/MmapSource.h b/include/media/stagefright/MmapSource.h new file mode 100644 index 000000000000..a8bd57f0d815 --- /dev/null +++ b/include/media/stagefright/MmapSource.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MMAP_SOURCE_H_ + +#define MMAP_SOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +class MmapSource : public DataSource { +public: + MmapSource(const char *filename); + + // Assumes ownership of "fd". + MmapSource(int fd, int64_t offset, int64_t length); + + virtual ~MmapSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + virtual status_t getSize(off_t *size); + +private: + int mFd; + void *mBase; + size_t mSize; + + MmapSource(const MmapSource &); + MmapSource &operator=(const MmapSource &); +}; + +} // namespace android + +#endif // MMAP_SOURCE_H_ + diff --git a/include/media/stagefright/OMXClient.h b/include/media/stagefright/OMXClient.h new file mode 100644 index 000000000000..454c38be5e70 --- /dev/null +++ b/include/media/stagefright/OMXClient.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OMX_CLIENT_H_ + +#define OMX_CLIENT_H_ + +#include <media/IOMX.h> + +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class OMXObserver { +public: + OMXObserver(); + virtual ~OMXObserver(); + + void postMessage(const omx_message &msg); + +protected: + virtual void onOMXMessage(const omx_message &msg) = 0; + +private: + friend class OMXClient; + + pthread_t mThread; + Mutex mLock; + Condition mQueueNotEmpty; + List<omx_message> mQueue; + + void start(); + void stop(); + + static void *ThreadWrapper(void *me); + void threadEntry(); + + OMXObserver(const OMXObserver &); + OMXObserver &operator=(const OMXObserver &); +}; + +class OMXClient; + +class OMXClientReflector : public BnOMXObserver { +public: + OMXClientReflector(OMXClient *client); + + virtual void on_message(const omx_message &msg); + void reset(); + +private: + OMXClient *mClient; + + OMXClientReflector(const OMXClientReflector &); + OMXClientReflector &operator=(const OMXClientReflector &); +}; + +class OMXClient { +public: + friend class OMXClientReflector; + + OMXClient(); + ~OMXClient(); + + status_t connect(); + void disconnect(); + + sp<IOMX> interface() { + return mOMX; + } + + status_t registerObserver(IOMX::node_id node, OMXObserver *observer); + void unregisterObserver(IOMX::node_id node); + + status_t fillBuffer(IOMX::node_id node, IOMX::buffer_id buffer); + + status_t emptyBuffer( + IOMX::node_id node, IOMX::buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp); + + status_t send_command( + IOMX::node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); + +private: + sp<IOMX> mOMX; + + int mSock; + Mutex mLock; + pthread_t mThread; + + KeyedVector<IOMX::node_id, OMXObserver *> mObservers; + + sp<OMXClientReflector> mReflector; + +#if IOMX_USES_SOCKETS + static void *ThreadWrapper(void *me); + void threadEntry(); +#endif + + bool onOMXMessage(const omx_message &msg); + + OMXClient(const OMXClient &); + OMXClient &operator=(const OMXClient &); +}; + +} // namespace android + +#endif // OMX_CLIENT_H_ diff --git a/include/media/stagefright/OMXDecoder.h b/include/media/stagefright/OMXDecoder.h new file mode 100644 index 000000000000..085945710d8f --- /dev/null +++ b/include/media/stagefright/OMXDecoder.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OMX_DECODER_H_ + +#define OMX_DECODER_H_ + +#include <binder/MemoryDealer.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/OMXClient.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class OMXMediaBuffer; + +class OMXDecoder : public MediaSource, + public OMXObserver, + public MediaBufferObserver { +public: + static OMXDecoder *Create( + OMXClient *client, const sp<MetaData> &data); + + static OMXDecoder *CreateEncoder( + OMXClient *client, const sp<MetaData> &data); + + virtual ~OMXDecoder(); + + // Caller retains ownership of "source". + void setSource(MediaSource *source); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + void addCodecSpecificData(const void *data, size_t size); + + // from OMXObserver + virtual void onOMXMessage(const omx_message &msg); + + // from MediaBufferObserver + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + enum { + kPortIndexInput = 0, + kPortIndexOutput = 1 + }; + + enum PortStatus { + kPortStatusActive = 0, + kPortStatusDisabled = 1, + kPortStatusShutdown = 2, + kPortStatusFlushing = 3 + }; + + OMXClient *mClient; + sp<IOMX> mOMX; + IOMX::node_id mNode; + char *mComponentName; + bool mIsMP3; + + MediaSource *mSource; + sp<MetaData> mOutputFormat; + + Mutex mLock; + Condition mOutputBufferAvailable; + + List<MediaBuffer *> mOutputBuffers; + + struct CodecSpecificData { + void *data; + size_t size; + }; + + List<CodecSpecificData> mCodecSpecificData; + List<CodecSpecificData>::iterator mCodecSpecificDataIterator; + + volatile OMX_STATETYPE mState; + OMX_U32 mPortStatusMask; + bool mShutdownInitiated; + + typedef List<IOMX::buffer_id> BufferList; + Vector<BufferList> mBuffers; + + KeyedVector<IOMX::buffer_id, sp<IMemory> > mBufferMap; + KeyedVector<IOMX::buffer_id, OMXMediaBuffer *> mMediaBufferMap; + + sp<MemoryDealer> mDealer; + + bool mSeeking; + int64_t mSeekTimeUs; + + bool mStarted; + status_t mErrorCondition; + bool mReachedEndOfInput; + + OMXDecoder(OMXClient *client, IOMX::node_id node, + const char *mime, const char *codec); + + void setPortStatus(OMX_U32 port_index, PortStatus status); + PortStatus getPortStatus(OMX_U32 port_index) const; + + void allocateBuffers(OMX_U32 port_index); + + void setAMRFormat(); + void setAACFormat(); + void setVideoOutputFormat(OMX_U32 width, OMX_U32 height); + void setup(); + void dumpPortDefinition(OMX_U32 port_index); + + void onStart(); + void onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + void onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data); + void onEventPortSettingsChanged(OMX_U32 port_index); + void onStateChanged(OMX_STATETYPE to); + void onEmptyBufferDone(IOMX::buffer_id buffer); + void onFillBufferDone(const omx_message &msg); + + void onRealEmptyBufferDone(IOMX::buffer_id buffer); + void onRealFillBufferDone(const omx_message &msg); + + void initiateShutdown(); + + void freeInputBuffer(IOMX::buffer_id buffer); + void freeOutputBuffer(IOMX::buffer_id buffer); + + void postStart(); + void postEmptyBufferDone(IOMX::buffer_id buffer); + void postInitialFillBuffer(IOMX::buffer_id buffer); + + OMXDecoder(const OMXDecoder &); + OMXDecoder &operator=(const OMXDecoder &); +}; + +} // namespace android + +#endif // OMX_DECODER_H_ diff --git a/include/media/stagefright/QComHardwareRenderer.h b/include/media/stagefright/QComHardwareRenderer.h new file mode 100644 index 000000000000..8292dd560077 --- /dev/null +++ b/include/media/stagefright/QComHardwareRenderer.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QCOM_HARDWARE_RENDERER_H_ + +#define QCOM_HARDWARE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class ISurface; +class MemoryHeapPmem; + +class QComHardwareRenderer : public VideoRenderer { +public: + QComHardwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~QComHardwareRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<ISurface> mISurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + size_t mFrameSize; + sp<MemoryHeapPmem> mMemoryHeap; + + bool getOffset(void *platformPrivate, size_t *offset); + void publishBuffers(uint32_t pmem_fd); + + QComHardwareRenderer(const QComHardwareRenderer &); + QComHardwareRenderer &operator=(const QComHardwareRenderer &); +}; + +} // namespace android + +#endif // QCOM_HARDWARE_RENDERER_H_ diff --git a/include/media/stagefright/SampleTable.h b/include/media/stagefright/SampleTable.h new file mode 100644 index 000000000000..712da1061232 --- /dev/null +++ b/include/media/stagefright/SampleTable.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SAMPLE_TABLE_H_ + +#define SAMPLE_TABLE_H_ + +#include <sys/types.h> +#include <stdint.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class DataSource; + +class SampleTable { +public: + // Caller retains ownership of "source". + SampleTable(DataSource *source); + ~SampleTable(); + + // type can be 'stco' or 'co64'. + status_t setChunkOffsetParams( + uint32_t type, off_t data_offset, off_t data_size); + + status_t setSampleToChunkParams(off_t data_offset, off_t data_size); + + // type can be 'stsz' or 'stz2'. + status_t setSampleSizeParams( + uint32_t type, off_t data_offset, off_t data_size); + + status_t setTimeToSampleParams(off_t data_offset, off_t data_size); + + status_t setSyncSampleParams(off_t data_offset, off_t data_size); + + //////////////////////////////////////////////////////////////////////////// + + uint32_t countChunkOffsets() const; + status_t getChunkOffset(uint32_t chunk_index, off_t *offset); + + status_t getChunkForSample( + uint32_t sample_index, uint32_t *chunk_index, + uint32_t *chunk_relative_sample_index, uint32_t *desc_index); + + uint32_t countSamples() const; + status_t getSampleSize(uint32_t sample_index, size_t *sample_size); + + status_t getSampleOffsetAndSize( + uint32_t sample_index, off_t *offset, size_t *size); + + status_t getMaxSampleSize(size_t *size); + + status_t getDecodingTime(uint32_t sample_index, uint32_t *time); + + enum { + kSyncSample_Flag = 1 + }; + status_t findClosestSample( + uint32_t req_time, uint32_t *sample_index, uint32_t flags); + + status_t findClosestSyncSample( + uint32_t start_sample_index, uint32_t *sample_index); + +private: + DataSource *mDataSource; + Mutex mLock; + + off_t mChunkOffsetOffset; + uint32_t mChunkOffsetType; + uint32_t mNumChunkOffsets; + + off_t mSampleToChunkOffset; + uint32_t mNumSampleToChunkOffsets; + + off_t mSampleSizeOffset; + uint32_t mSampleSizeFieldSize; + uint32_t mDefaultSampleSize; + uint32_t mNumSampleSizes; + + uint32_t mTimeToSampleCount; + uint32_t *mTimeToSample; + + off_t mSyncSampleOffset; + uint32_t mNumSyncSamples; + + SampleTable(const SampleTable &); + SampleTable &operator=(const SampleTable &); +}; + +} // namespace android + +#endif // SAMPLE_TABLE_H_ diff --git a/include/media/stagefright/ShoutcastSource.h b/include/media/stagefright/ShoutcastSource.h new file mode 100644 index 000000000000..352857acedf6 --- /dev/null +++ b/include/media/stagefright/ShoutcastSource.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SHOUTCAST_SOURCE_H_ + +#define SHOUTCAST_SOURCE_H_ + +#include <sys/types.h> + +#include <media/stagefright/MediaSource.h> + +namespace android { + +class HTTPStream; +class MediaBufferGroup; + +class ShoutcastSource : public MediaSource { +public: + // Assumes ownership of "http". + ShoutcastSource(HTTPStream *http); + virtual ~ShoutcastSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + HTTPStream *mHttp; + size_t mMetaDataOffset; + size_t mBytesUntilMetaData; + + MediaBufferGroup *mGroup; + bool mStarted; + + ShoutcastSource(const ShoutcastSource &); + ShoutcastSource &operator= (const ShoutcastSource &); +}; + +} // namespace android + +#endif // SHOUTCAST_SOURCE_H_ + diff --git a/include/media/stagefright/SoftwareRenderer.h b/include/media/stagefright/SoftwareRenderer.h new file mode 100644 index 000000000000..705b91478d6b --- /dev/null +++ b/include/media/stagefright/SoftwareRenderer.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SOFTWARE_RENDERER_H_ + +#define SOFTWARE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class ISurface; +class MemoryHeapBase; + +class SoftwareRenderer : public VideoRenderer { +public: + SoftwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~SoftwareRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<ISurface> mISurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + size_t mFrameSize; + sp<MemoryHeapBase> mMemoryHeap; + int mIndex; + + SoftwareRenderer(const SoftwareRenderer &); + SoftwareRenderer &operator=(const SoftwareRenderer &); +}; + +} // namespace android + +#endif // SOFTWARE_RENDERER_H_ diff --git a/include/media/stagefright/SurfaceRenderer.h b/include/media/stagefright/SurfaceRenderer.h new file mode 100644 index 000000000000..298ab50a87d4 --- /dev/null +++ b/include/media/stagefright/SurfaceRenderer.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SURFACE_RENDERER_H_ + +#define SURFACE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class Surface; + +class SurfaceRenderer : public VideoRenderer { +public: + SurfaceRenderer( + const sp<Surface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~SurfaceRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<Surface> mSurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + + SurfaceRenderer(const SurfaceRenderer &); + SurfaceRenderer &operator=(const SurfaceRenderer &); +}; + +} // namespace android + +#endif // SURFACE_RENDERER_H_ diff --git a/include/media/stagefright/TimeSource.h b/include/media/stagefright/TimeSource.h new file mode 100644 index 000000000000..443673de4692 --- /dev/null +++ b/include/media/stagefright/TimeSource.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIME_SOURCE_H_ + +#define TIME_SOURCE_H_ + +#include <stdint.h> + +namespace android { + +class TimeSource { +public: + TimeSource() {} + virtual ~TimeSource() {} + + virtual int64_t getRealTimeUs() = 0; + +private: + TimeSource(const TimeSource &); + TimeSource &operator=(const TimeSource &); +}; + +class SystemTimeSource : public TimeSource { +public: + SystemTimeSource(); + + virtual int64_t getRealTimeUs(); + +private: + static int64_t GetSystemTimeUs(); + + int64_t mStartTimeUs; +}; + +} // namespace android + +#endif // TIME_SOURCE_H_ diff --git a/include/media/stagefright/TimedEventQueue.h b/include/media/stagefright/TimedEventQueue.h new file mode 100644 index 000000000000..a2644219cec5 --- /dev/null +++ b/include/media/stagefright/TimedEventQueue.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIMED_EVENT_QUEUE_H_ + +#define TIMED_EVENT_QUEUE_H_ + +#include <pthread.h> + +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +struct TimedEventQueue { + + struct Event : public RefBase { + Event() {} + virtual ~Event() {} + + protected: + virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0; + + private: + friend class TimedEventQueue; + + Event(const Event &); + Event &operator=(const Event &); + }; + + TimedEventQueue(); + ~TimedEventQueue(); + + // Start executing the event loop. + void start(); + + // Stop executing the event loop, if flush is false, any pending + // events are discarded, otherwise the queue will stop (and this call + // return) once all pending events have been handled. + void stop(bool flush = false); + + // Posts an event to the front of the queue (after all events that + // have previously been posted to the front but before timed events). + void postEvent(const sp<Event> &event); + + void postEventToBack(const sp<Event> &event); + + // It is an error to post an event with a negative delay. + void postEventWithDelay(const sp<Event> &event, int64_t delay_us); + + // If the event is to be posted at a time that has already passed, + // it will fire as soon as possible. + void postTimedEvent(const sp<Event> &event, int64_t realtime_us); + + // Returns true iff event is currently in the queue and has been + // successfully cancelled. In this case the event will have been + // removed from the queue and won't fire. + bool cancelEvent(const sp<Event> &event); + + static int64_t getRealTimeUs(); + +private: + struct QueueItem { + sp<Event> event; + int64_t realtime_us; + }; + + struct StopEvent : public TimedEventQueue::Event { + virtual void fire(TimedEventQueue *queue, int64_t now_us) { + queue->mStopped = true; + } + }; + + pthread_t mThread; + List<QueueItem> mQueue; + Mutex mLock; + Condition mQueueNotEmptyCondition; + Condition mQueueHeadChangedCondition; + + bool mRunning; + bool mStopped; + + static void *ThreadWrapper(void *me); + void threadEntry(); + + TimedEventQueue(const TimedEventQueue &); + TimedEventQueue &operator=(const TimedEventQueue &); +}; + +} // namespace android + +#endif // TIMED_EVENT_QUEUE_H_ diff --git a/include/media/stagefright/Utils.h b/include/media/stagefright/Utils.h new file mode 100644 index 000000000000..30c7f1158138 --- /dev/null +++ b/include/media/stagefright/Utils.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTILS_H_ + +#define UTILS_H_ + +#include <stdint.h> + +namespace android { + +#define FOURCC(c1, c2, c3, c4) \ + (c1 << 24 | c2 << 16 | c3 << 8 | c4) + +uint16_t U16_AT(const uint8_t *ptr); +uint32_t U32_AT(const uint8_t *ptr); +uint64_t U64_AT(const uint8_t *ptr); + +uint64_t ntoh64(uint64_t x); +uint64_t hton64(uint64_t x); + +} // namespace android + +#endif // UTILS_H_ diff --git a/include/media/stagefright/VideoRenderer.h b/include/media/stagefright/VideoRenderer.h new file mode 100644 index 000000000000..f80b277b47fd --- /dev/null +++ b/include/media/stagefright/VideoRenderer.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef VIDEO_RENDERER_H_ + +#define VIDEO_RENDERER_H_ + +#include <sys/types.h> + +namespace android { + +class VideoRenderer { +public: + virtual ~VideoRenderer() {} + + virtual void render( + const void *data, size_t size, void *platformPrivate) = 0; + +protected: + VideoRenderer() {} + + VideoRenderer(const VideoRenderer &); + VideoRenderer &operator=(const VideoRenderer &); +}; + +} // namespace android + +#endif // VIDEO_RENDERER_H_ diff --git a/include/media/stagefright/string.h b/include/media/stagefright/string.h new file mode 100644 index 000000000000..5dc711653f04 --- /dev/null +++ b/include/media/stagefright/string.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STRING_H_ + +#define STRING_H_ + +#include <utils/String8.h> + +namespace android { + +class string { +public: + typedef size_t size_type; + static size_type npos; + + string(); + string(const char *s); + string(const char *s, size_t length); + string(const string &from, size_type start, size_type length = npos); + + const char *c_str() const; + size_type size() const; + + void clear(); + void erase(size_type from, size_type length); + + size_type find(char c) const; + + bool operator<(const string &other) const; + bool operator==(const string &other) const; + + string &operator+=(char c); + +private: + String8 mString; +}; + +} // namespace android + +#endif // STRING_H_ diff --git a/include/private/ui/SharedState.h b/include/private/ui/SharedState.h index 3bc7979ca810..c9f6b5edb9ab 100644 --- a/include/private/ui/SharedState.h +++ b/include/private/ui/SharedState.h @@ -98,6 +98,8 @@ struct layer_cblk_t // (128 bytes) struct per_client_cblk_t // 4KB max { + per_client_cblk_t() : lock(Mutex::SHARED) { } + Mutex lock; Condition cv; layer_cblk_t layers[NUM_LAYERS_MAX] __attribute__((aligned(32))); diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h index e12c4f13ea0d..d62fd7d1af6a 100644 --- a/include/ui/EventHub.h +++ b/include/ui/EventHub.h @@ -73,6 +73,13 @@ public: int getKeycodeState(int key) const; int getKeycodeState(int32_t deviceId, int key) const; + status_t scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const; + + // exclude a particular device from opening + // this can be used to ignore input devices for sensors + void addExcludedDevice(const char* deviceName); + // special type codes when devices are added/removed. enum { DEVICE_ADDED = 0x10000000, @@ -85,10 +92,9 @@ public: virtual bool getEvent(int32_t* outDeviceId, int32_t* outType, int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, int32_t* outValue, nsecs_t* outWhen); - + protected: virtual ~EventHub(); - virtual void onFirstRef(); private: bool openPlatformInput(void); @@ -121,7 +127,7 @@ private: mutable Mutex mLock; bool mHaveFirstKeyboard; - int32_t mFirstKeyboardId; // the API is that the build in keyboard is id 0, so map it + int32_t mFirstKeyboardId; // the API is that the built-in keyboard is id 0, so map it struct device_ent { device_t* device; @@ -136,7 +142,10 @@ private: device_t **mDevices; struct pollfd *mFDs; int mFDCount; - + + bool mOpened; + List<String8> mExcludedDevices; + // device ids that report particular switches. #ifdef EV_SW int32_t mSwitches[SW_MAX+1]; diff --git a/include/ui/Overlay.h b/include/ui/Overlay.h index 9ba2f7b6fd1c..acc9bea4e4b5 100644 --- a/include/ui/Overlay.h +++ b/include/ui/Overlay.h @@ -82,6 +82,10 @@ public: /* release the overlay buffer and post it */ status_t queueBuffer(overlay_buffer_t buffer); + status_t setCrop(uint32_t x, uint32_t y, uint32_t w, uint32_t h) ; + + status_t getCrop(uint32_t* x, uint32_t* y, uint32_t* w, uint32_t* h) ; + /* returns the address of a given buffer if supported, NULL otherwise. */ void* getBufferAddress(overlay_buffer_t buffer); diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 93bca4aefc08..381933556d2f 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -825,7 +825,8 @@ struct ResTable_config }; enum { - DENSITY_ANY = 0 + DENSITY_DEFAULT = 0, + DENSITY_NONE = 0xffff }; union { diff --git a/include/utils/threads.h b/include/utils/threads.h index 5c0396596074..e9b0788895e2 100644 --- a/include/utils/threads.h +++ b/include/utils/threads.h @@ -190,8 +190,14 @@ inline thread_id_t getThreadId() { */ class Mutex { public: + enum { + NORMAL = 0, + SHARED = 1 + }; + Mutex(); Mutex(const char* name); + Mutex(int type, const char* name = NULL); ~Mutex(); // lock or unlock the mutex @@ -235,6 +241,17 @@ inline Mutex::Mutex() { inline Mutex::Mutex(const char* name) { pthread_mutex_init(&mMutex, NULL); } +inline Mutex::Mutex(int type, const char* name) { + if (type == SHARED) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&mMutex, &attr); + pthread_mutexattr_destroy(&attr); + } else { + pthread_mutex_init(&mMutex, NULL); + } +} inline Mutex::~Mutex() { pthread_mutex_destroy(&mMutex); } diff --git a/keystore/java/android/security/CertTool.java b/keystore/java/android/security/CertTool.java index 26d22aec57fb..c96cd4f4d907 100644 --- a/keystore/java/android/security/CertTool.java +++ b/keystore/java/android/security/CertTool.java @@ -16,11 +16,19 @@ package android.security; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + import android.content.Context; import android.content.Intent; import android.security.Keystore; import android.text.TextUtils; - +import android.util.Log; /** * The CertTool class provides the functions to list the certs/keys, @@ -41,12 +49,12 @@ public class CertTool { public static final String KEY_NAMESPACE = "namespace"; public static final String KEY_DESCRIPTION = "description"; - private static final String TAG = "CertTool"; + public static final String TITLE_CA_CERT = "CA Certificate"; + public static final String TITLE_USER_CERT = "User Certificate"; + public static final String TITLE_PKCS12_KEYSTORE = "PKCS12 Keystore"; + public static final String TITLE_PRIVATE_KEY = "Private Key"; - private static final String TITLE_CA_CERT = "CA Certificate"; - private static final String TITLE_USER_CERT = "User Certificate"; - private static final String TITLE_PKCS12_KEYSTORE = "PKCS12 Keystore"; - private static final String TITLE_PRIVATE_KEY = "Private Key"; + private static final String TAG = "CertTool"; private static final String UNKNOWN = "Unknown"; private static final String ISSUER_NAME = "Issuer Name:"; private static final String DISTINCT_NAME = "Distinct Name:"; @@ -58,6 +66,11 @@ public class CertTool { private static final String KEYNAME_DELIMITER = "_"; private static final Keystore sKeystore = Keystore.getInstance(); + private native int getPkcs12Handle(byte[] data, String password); + private native String getPkcs12Certificate(int handle); + private native String getPkcs12PrivateKey(int handle); + private native String popPkcs12CertificateStack(int handle); + private native void freePkcs12Handle(int handle); private native String generateCertificateRequest(int bits, String subject); private native boolean isPkcs12Keystore(byte[] data); private native int generateX509Certificate(byte[] data); @@ -130,10 +143,35 @@ public class CertTool { intent.putExtra(KEY_NAMESPACE + "1", namespace); } + public int addPkcs12Keystore(byte[] p12Data, String password, + String keyname) { + int handle, i = 0; + String pemData; + Log.i("CertTool", "addPkcs12Keystore()"); + + if ((handle = getPkcs12Handle(p12Data, password)) == 0) return -1; + if ((pemData = getPkcs12Certificate(handle)) != null) { + sKeystore.put(USER_CERTIFICATE, keyname, pemData); + } + if ((pemData = getPkcs12PrivateKey(handle)) != null) { + sKeystore.put(USER_KEY, keyname, pemData); + } + while ((pemData = this.popPkcs12CertificateStack(handle)) != null) { + if (i++ > 0) { + sKeystore.put(CA_CERTIFICATE, keyname + i, pemData); + } else { + sKeystore.put(CA_CERTIFICATE, keyname, pemData); + } + } + freePkcs12Handle(handle); + return 0; + } + public synchronized void addCertificate(byte[] data, Context context) { int handle; Intent intent = null; + Log.i("CertTool", "addCertificate()"); if (isPkcs12Keystore(data)) { intent = prepareIntent(TITLE_PKCS12_KEYSTORE, data, USER_KEY, UNKNOWN, UNKNOWN); diff --git a/keystore/jni/cert.c b/keystore/jni/cert.c index cc36b84e99a3..0db28fd0e822 100644 --- a/keystore/jni/cert.c +++ b/keystore/jni/cert.c @@ -136,30 +136,126 @@ err: return ret_code; } -int is_pkcs12(const char *buf, int bufLen) +PKCS12 *get_p12_handle(const char *buf, int bufLen) { - int ret = 0; BIO *bp = NULL; PKCS12 *p12 = NULL; - if (!buf || bufLen < 1) goto err; + if (!buf || (bufLen < 1) || (buf[0] != 48)) goto err; bp = BIO_new(BIO_s_mem()); if (!bp) goto err; - if (buf[0] != 48) goto err; // it is not DER. - if (!BIO_write(bp, buf, bufLen)) goto err; - if ((p12 = d2i_PKCS12_bio(bp, NULL)) != NULL) { - PKCS12_free(p12); - ret = 1; - } + p12 = d2i_PKCS12_bio(bp, NULL); + err: if (bp) BIO_free(bp); + return p12; +} + +PKCS12_KEYSTORE *get_pkcs12_keystore_handle(const char *buf, int bufLen, + const char *passwd) +{ + PKCS12_KEYSTORE *p12store = NULL; + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + STACK_OF(X509) *certs = NULL; + PKCS12 *p12 = get_p12_handle(buf, bufLen); + + if (p12 == NULL) return NULL; + if (!PKCS12_parse(p12, passwd, &pkey, &cert, &certs)) { + LOGE("Can not parse PKCS12 content"); + PKCS12_free(p12); + return NULL; + } + if ((p12store = malloc(sizeof(PKCS12_KEYSTORE))) == NULL) { + if (cert) X509_free(cert); + if (pkey) EVP_PKEY_free(pkey); + if (certs) sk_X509_free(certs); + } + p12store->p12 = p12; + p12store->pkey = pkey; + p12store->cert = cert; + p12store->certs = certs; + return p12store; +} + +void free_pkcs12_keystore(PKCS12_KEYSTORE *p12store) +{ + if (p12store != NULL) { + if (p12store->cert) X509_free(p12store->cert); + if (p12store->pkey) EVP_PKEY_free(p12store->pkey); + if (p12store->certs) sk_X509_free(p12store->certs); + free(p12store); + } +} + +int is_pkcs12(const char *buf, int bufLen) +{ + int ret = 0; + PKCS12 *p12 = get_p12_handle(buf, bufLen); + if (p12 != NULL) ret = 1; + PKCS12_free(p12); return ret; } +static int convert_to_pem(void *data, int is_cert, char *buf, int size) +{ + int len = 0; + BIO *bio = NULL; + + if (data == NULL) return -1; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) goto err; + if (is_cert) { + if ((len = PEM_write_bio_X509(bio, (X509*)data)) == 0) { + goto err; + } + } else { + if ((len = PEM_write_bio_PrivateKey(bio, (EVP_PKEY *)data, NULL, + NULL, 0, NULL, NULL)) == 0) { + goto err; + } + } + if (len < size && (len = BIO_read(bio, buf, size - 1)) > 0) { + buf[len] = 0; + } +err: + if (bio) BIO_free(bio); + return (len == 0) ? -1 : 0; +} + +int get_pkcs12_certificate(PKCS12_KEYSTORE *p12store, char *buf, int size) +{ + if ((p12store != NULL) && (p12store->cert != NULL)) { + return convert_to_pem((void*)p12store->cert, 1, buf, size); + } + return -1; +} + +int get_pkcs12_private_key(PKCS12_KEYSTORE *p12store, char *buf, int size) +{ + if ((p12store != NULL) && (p12store->pkey != NULL)) { + return convert_to_pem((void*)p12store->pkey, 0, buf, size); + } + return -1; +} + +int pop_pkcs12_certs_stack(PKCS12_KEYSTORE *p12store, char *buf, int size) +{ + X509 *cert = NULL; + + if ((p12store != NULL) && (p12store->certs != NULL) && + ((cert = sk_X509_pop(p12store->certs)) != NULL)) { + int ret = convert_to_pem((void*)cert, 1, buf, size); + X509_free(cert); + return ret; + } + return -1; +} + X509* parse_cert(const char *buf, int bufLen) { X509 *cert = NULL; diff --git a/keystore/jni/cert.h b/keystore/jni/cert.h index a9807b1b20f0..aaa7602d8126 100644 --- a/keystore/jni/cert.h +++ b/keystore/jni/cert.h @@ -41,6 +41,13 @@ typedef struct { int key_len; } PKEY_STORE; +typedef struct { + PKCS12 *p12; + EVP_PKEY *pkey; + X509 *cert; + STACK_OF(X509) *certs; +} PKCS12_KEYSTORE; + #define PKEY_STORE_free(x) { \ if(x.pkey) EVP_PKEY_free(x.pkey); \ if(x.public_key) free(x.public_key); \ @@ -49,8 +56,14 @@ typedef struct { #define nelem(x) (sizeof (x) / sizeof *(x)) int gen_csr(int bits, const char *organizations, char reply[REPLY_MAX]); +PKCS12_KEYSTORE *get_pkcs12_keystore_handle(const char *buf, int bufLen, + const char *passwd); +int get_pkcs12_certificate(PKCS12_KEYSTORE *p12store, char *buf, int size); +int get_pkcs12_private_key(PKCS12_KEYSTORE *p12store, char *buf, int size); +int pop_pkcs12_certs_stack(PKCS12_KEYSTORE *p12store, char *buf, int size); +void free_pkcs12_keystore(PKCS12_KEYSTORE *p12store); int is_pkcs12(const char *buf, int bufLen); -X509* parse_cert(const char *buf, int bufLen); +X509 *parse_cert(const char *buf, int bufLen); int get_cert_name(X509 *cert, char *buf, int size); int get_issuer_name(X509 *cert, char *buf, int size); int is_ca_cert(X509 *cert); diff --git a/keystore/jni/certtool.c b/keystore/jni/certtool.c index fabf5cdf3fb7..1ae8dab4281f 100644 --- a/keystore/jni/certtool.c +++ b/keystore/jni/certtool.c @@ -19,10 +19,13 @@ #include <string.h> #include <jni.h> #include <cutils/log.h> +#include <openssl/pkcs12.h> #include <openssl/x509v3.h> #include "cert.h" +typedef int PKCS12_KEYSTORE_FUNC(PKCS12_KEYSTORE *store, char *buf, int size); + jstring android_security_CertTool_generateCertificateRequest(JNIEnv* env, jobject thiz, @@ -42,12 +45,88 @@ android_security_CertTool_isPkcs12Keystore(JNIEnv* env, jobject thiz, jbyteArray data) { - char buf[REPLY_MAX]; int len = (*env)->GetArrayLength(env, data); - if (len > REPLY_MAX) return 0; - (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf); - return (jboolean) is_pkcs12(buf, len); + if (len > 0) { + PKCS12 *handle = NULL; + char buf[len]; + + (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf); + return (jboolean)is_pkcs12(buf, len); + } else { + return 0; + } +} + +jint +android_security_CertTool_getPkcs12Handle(JNIEnv* env, + jobject thiz, + jbyteArray data, + jstring jPassword) +{ + jboolean bIsCopy; + int len = (*env)->GetArrayLength(env, data); + const char* passwd = (*env)->GetStringUTFChars(env, jPassword , &bIsCopy); + + if (len > 0) { + PKCS12_KEYSTORE *handle = NULL; + char buf[len]; + + (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf); + handle = get_pkcs12_keystore_handle(buf, len, passwd); + (*env)->ReleaseStringUTFChars(env, jPassword, passwd); + return (jint)handle; + } else { + return 0; + } +} + +jstring call_pkcs12_ks_func(PKCS12_KEYSTORE_FUNC *func, + JNIEnv* env, + jobject thiz, + jint phandle) +{ + char buf[REPLY_MAX]; + + if (phandle == 0) return NULL; + if (func((PKCS12_KEYSTORE*)phandle, buf, sizeof(buf)) == 0) { + return (*env)->NewStringUTF(env, buf); + } + return NULL; +} + +jstring +android_security_CertTool_getPkcs12Certificate(JNIEnv* env, + jobject thiz, + jint phandle) +{ + return call_pkcs12_ks_func((PKCS12_KEYSTORE_FUNC *)get_pkcs12_certificate, + env, thiz, phandle); +} + +jstring +android_security_CertTool_getPkcs12PrivateKey(JNIEnv* env, + jobject thiz, + jint phandle) +{ + return call_pkcs12_ks_func((PKCS12_KEYSTORE_FUNC *)get_pkcs12_private_key, + env, thiz, phandle); +} + +jstring +android_security_CertTool_popPkcs12CertificateStack(JNIEnv* env, + jobject thiz, + jint phandle) +{ + return call_pkcs12_ks_func((PKCS12_KEYSTORE_FUNC *)pop_pkcs12_certs_stack, + env, thiz, phandle); +} + +void android_security_CertTool_freePkcs12Handle(JNIEnv* env, + jobject thiz, + jint handle) +{ + if (handle != 0) free_pkcs12_keystore((PKCS12_KEYSTORE*)handle); } jint @@ -117,6 +196,16 @@ static JNINativeMethod gCertToolMethods[] = { (void*)android_security_CertTool_generateCertificateRequest}, {"isPkcs12Keystore", "([B)Z", (void*)android_security_CertTool_isPkcs12Keystore}, + {"getPkcs12Handle", "([BLjava/lang/String;)I", + (void*)android_security_CertTool_getPkcs12Handle}, + {"getPkcs12Certificate", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_getPkcs12Certificate}, + {"getPkcs12PrivateKey", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_getPkcs12PrivateKey}, + {"popPkcs12CertificateStack", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_popPkcs12CertificateStack}, + {"freePkcs12Handle", "(I)V", + (void*)android_security_CertTool_freePkcs12Handle}, {"generateX509Certificate", "([B)I", (void*)android_security_CertTool_generateX509Certificate}, {"isCaCertificate", "(I)Z", diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 598a35682cfc..ffc02782c7d5 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -212,6 +212,7 @@ void AudioFlinger::setA2dpEnabled_l(bool enable) } else { mA2dpMixerThread->getTracks_l(tracks, activeTracks); mHardwareMixerThread->putTracks_l(tracks, activeTracks); + mA2dpMixerThread->mOutput->standby(); } mA2dpEnabled = enable; mNotifyA2dpChange = true; diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index c3889e9855cf..c371a23b85f4 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -366,13 +366,8 @@ void IPCThreadState::restoreCallingIdentity(int64_t token) void IPCThreadState::clearCaller() { - if (mProcess->supportsProcesses()) { - mCallingPid = getpid(); - mCallingUid = getuid(); - } else { - mCallingPid = -1; - mCallingUid = -1; - } + mCallingPid = getpid(); + mCallingUid = getuid(); } void IPCThreadState::flushCommands() diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk index f5297f8d07ba..0091e322d08f 100644 --- a/libs/rs/Android.mk +++ b/libs/rs/Android.mk @@ -85,10 +85,12 @@ LOCAL_SRC_FILES:= \ rsContext.cpp \ rsDevice.cpp \ rsElement.cpp \ + rsFileA3D.cpp \ rsLight.cpp \ rsLocklessFifo.cpp \ rsObjectBase.cpp \ rsMatrix.cpp \ + rsMesh.cpp \ rsProgram.cpp \ rsProgramFragment.cpp \ rsProgramFragmentStore.cpp \ @@ -96,6 +98,7 @@ LOCAL_SRC_FILES:= \ rsSampler.cpp \ rsScript.cpp \ rsScriptC.cpp \ + rsScriptC_Lib.cpp \ rsThreadIO.cpp \ rsType.cpp \ rsTriangleMesh.cpp diff --git a/libs/rs/RenderScriptEnv.h b/libs/rs/RenderScriptEnv.h index 53de1f1f1ec4..07893014ac70 100644 --- a/libs/rs/RenderScriptEnv.h +++ b/libs/rs/RenderScriptEnv.h @@ -30,65 +30,7 @@ typedef struct { #define RS_PROGRAM_VERTEX_PROJECTION_OFFSET 16 #define RS_PROGRAM_VERTEX_TEXTURE_OFFSET 32 -typedef struct { - const void * (*loadEnvVp)(uint32_t bank, uint32_t offset); - - float (*loadEnvF)(uint32_t bank, uint32_t offset); - int32_t (*loadEnvI32)(uint32_t bank, uint32_t offset); - uint32_t (*loadEnvU32)(uint32_t bank, uint32_t offset); - void (*loadEnvVec4)(uint32_t bank, uint32_t offset, rsc_Vector4 *); - void (*loadEnvMatrix)(uint32_t bank, uint32_t offset, rsc_Matrix *); - - void (*storeEnvF)(uint32_t bank, uint32_t offset, float); - void (*storeEnvI32)(uint32_t bank, uint32_t offset, int32_t); - void (*storeEnvU32)(uint32_t bank, uint32_t offset, uint32_t); - void (*storeEnvVec4)(uint32_t bank, uint32_t offset, const rsc_Vector4 *); - void (*storeEnvMatrix)(uint32_t bank, uint32_t offset, const rsc_Matrix *); - - void (*matrixLoadIdentity)(rsc_Matrix *); - void (*matrixLoadFloat)(rsc_Matrix *, const float *); - void (*matrixLoadMat)(rsc_Matrix *, const rsc_Matrix *); - void (*matrixLoadRotate)(rsc_Matrix *, float rot, float x, float y, float z); - void (*matrixLoadScale)(rsc_Matrix *, float x, float y, float z); - void (*matrixLoadTranslate)(rsc_Matrix *, float x, float y, float z); - void (*matrixLoadMultiply)(rsc_Matrix *, const rsc_Matrix *lhs, const rsc_Matrix *rhs); - void (*matrixMultiply)(rsc_Matrix *, const rsc_Matrix *rhs); - void (*matrixRotate)(rsc_Matrix *, float rot, float x, float y, float z); - void (*matrixScale)(rsc_Matrix *, float x, float y, float z); - void (*matrixTranslate)(rsc_Matrix *, float x, float y, float z); - - void (*color)(float r, float g, float b, float a); - - void (*programFragmentBindTexture)(RsProgramFragment, uint32_t slot, RsAllocation); - void (*programFragmentBindSampler)(RsProgramFragment, uint32_t slot, RsAllocation); - - void (*materialDiffuse)(float r, float g, float b, float a); - void (*materialSpecular)(float r, float g, float b, float a); - void (*lightPosition)(float x, float y, float z, float w); - void (*materialShininess)(float s); - - void (*uploadToTexture)(RsAllocation va, uint32_t baseMipLevel); - - void (*enable)(uint32_t); - void (*disable)(uint32_t); - - uint32_t (*rand)(uint32_t max); - - void (*contextBindProgramFragment)(RsProgramFragment pf); - void (*contextBindProgramFragmentStore)(RsProgramFragmentStore pfs); - - - // Drawing funcs - void (*renderTriangleMesh)(RsTriangleMesh); - void (*renderTriangleMeshRange)(RsTriangleMesh, uint32_t start, uint32_t count); - - // Assumes (GL_FIXED) x,y,z (GL_UNSIGNED_BYTE)r,g,b,a - void (*drawTriangleArray)(RsAllocation alloc, uint32_t count); - - void (*drawRect)(int32_t x1, int32_t x2, int32_t y1, int32_t y2); -} rsc_FunctionTable; - -typedef int (*rsc_RunScript)(uint32_t launchIndex, const rsc_FunctionTable *); +//typedef int (*rsc_RunScript)(uint32_t launchIndex, const rsc_FunctionTable *); /* EnableCap */ diff --git a/libs/rs/java/Film/res/raw/filmstrip.c b/libs/rs/java/Film/res/raw/filmstrip.c index 1687a3121068..6885251d9250 100644 --- a/libs/rs/java/Film/res/raw/filmstrip.c +++ b/libs/rs/java/Film/res/raw/filmstrip.c @@ -1,7 +1,7 @@ // Fountain test script #pragma version(1) -#pragma stateVertex(PV) +#pragma stateVertex(PVBackground) #pragma stateFragment(PFBackground) #pragma stateFragmentStore(PFSBackground) @@ -23,47 +23,35 @@ int main(int index) { int f1,f2,f3,f4, f5,f6,f7,f8, f9,f10,f11,f12, f13,f14,f15,f16; int g1,g2,g3,g4, g5,g6,g7,g8, g9,g10,g11,g12, g13,g14,g15,g16; - int float_1; - int float_0; - int float_2; - int float_90; - int float_0_5; - int trans; // float - int rot; // float + float trans; + float rot; int x; float focusPos; // float int focusID; int lastFocusID; int imgCount; - float_2 = intToFloat(2); - float_1 = intToFloat(1); - float_0 = intToFloat(0); - float_90= intToFloat(90); - float_0_5 = fixedtoFloat(0x8000); - trans = loadF(1, 0); rot = loadF(1, 1); - matrixLoadScale(&f16, float_2, float_2, float_2); - matrixTranslate(&f16, 0, 0, trans); - matrixRotate(&f16, float_90, 0, 0, float_1); - matrixRotate(&f16, rot, float_1, 0, 0); - storeEnvMatrix(3, 0, &f16); + matrixLoadScale(&f16, 2.f, 2.f, 2.f); + matrixTranslate(&f16, 0.f, 0.f, trans); + matrixRotate(&f16, 90.f, 0.f, 0.f, 1.f); + matrixRotate(&f16, rot, 1.f, 0.f, 0.f); + storeMatrix(3, 0, &f16); //materialDiffuse(con, 0.0f, 0.0f, 0.0f, 1.0f); //materialSpecular(con, 0.5f, 0.5f, 0.5f, 0.5f); //materialShininess(intToFloat(20)); - //lightPosition(con, 0.2f, -0.2f, -2.0f, 0.0f); - //enable(con, GL_LIGHTING); - renderTriangleMesh(NAMED_mesh); + drawTriangleMesh(NAMED_mesh); //int imgId = 0; - contextBindProgramFragmentStore(NAMED_PFImages); - contextBindProgramFragment(NAMED_PFSImages); + bindProgramFragmentStore(NAMED_PFImages); + bindProgramFragment(NAMED_PFSImages); + bindProgramVertex(NAMED_PVImages); //focusPos = loadF(1, 2); //focusID = 0; diff --git a/libs/rs/java/Film/src/com/android/film/FilmRS.java b/libs/rs/java/Film/src/com/android/film/FilmRS.java index 2711bf0cc4aa..fca0818983d1 100644 --- a/libs/rs/java/Film/src/com/android/film/FilmRS.java +++ b/libs/rs/java/Film/src/com/android/film/FilmRS.java @@ -45,7 +45,6 @@ public class FilmRS { public void init(RenderScript rs, Resources res, int width, int height) { mRS = rs; mRes = res; - initNamed(); initRS(); } @@ -77,7 +76,8 @@ public class FilmRS { private RenderScript.ProgramFragmentStore mPFSImages; private RenderScript.ProgramFragment mPFBackground; private RenderScript.ProgramFragment mPFImages; - private RenderScript.ProgramVertex mPV; + private RenderScript.ProgramVertex mPVBackground; + private RenderScript.ProgramVertex mPVImages; private ProgramVertexAlloc mPVA; private RenderScript.Allocation mAllocEnv; @@ -90,20 +90,7 @@ public class FilmRS { private float[] mBufferPos; private float[] mBufferPV; - private void initNamed() { - mElementVertex = mRS.elementGetPredefined( - RenderScript.ElementPredefined.NORM_ST_XYZ_F32); - mElementIndex = mRS.elementGetPredefined( - RenderScript.ElementPredefined.INDEX_16); - - mRS.triangleMeshBegin(mElementVertex, mElementIndex); - FilmStripMesh fsm = new FilmStripMesh(); - fsm.init(mRS); - mMesh = mRS.triangleMeshCreate(); - mMesh.setName("mesh"); - Log.e("rs", "Done loading strips"); - - + private void initSamplers() { mRS.samplerBegin(); mRS.samplerSet(RenderScript.SamplerParam.FILTER_MIN, RenderScript.SamplerValue.LINEAR_MIP_LINEAR); @@ -112,19 +99,9 @@ public class FilmRS { mRS.samplerSet(RenderScript.SamplerParam.WRAP_MODE_T, RenderScript.SamplerValue.CLAMP); mSampler = mRS.samplerCreate(); + } - mRS.programFragmentBegin(null, null); - mPFBackground = mRS.programFragmentCreate(); - mPFBackground.setName("PFBackground"); - - mRS.programFragmentBegin(null, null); - mRS.programFragmentSetTexEnable(0, true); - //mRS.programFragmentSetEnvMode(0, RS_TEX_ENV_MODE_REPLACE); - //rsProgramFragmentSetType(0, gEnv.tex[0]->getType()); - mPFImages = mRS.programFragmentCreate(); - mPFImages.setName("PFImages"); - mPFImages.bindSampler(mSampler, 0); - + private void initPFS() { mRS.programFragmentStoreBegin(null, null); mRS.programFragmentStoreDepthFunc(RenderScript.DepthFunc.LESS); mRS.programFragmentStoreDitherEnable(true); @@ -139,26 +116,59 @@ public class FilmRS { RenderScript.BlendDstFunc.ONE); mPFSImages = mRS.programFragmentStoreCreate(); mPFSImages.setName("PFSImages"); + } - mRS.programVertexBegin(null, null); - mRS.programVertexSetTextureMatrixEnable(true); - mPV = mRS.programVertexCreate(); - mPV.setName("PV"); + private void initPF() { + mRS.programFragmentBegin(null, null); + mPFBackground = mRS.programFragmentCreate(); + mPFBackground.setName("PFBackground"); + + mRS.programFragmentBegin(null, null); + mRS.programFragmentSetTexEnable(0, true); + //mRS.programFragmentSetEnvMode(0, RS_TEX_ENV_MODE_REPLACE); + //rsProgramFragmentSetType(0, gEnv.tex[0]->getType()); + mPFImages = mRS.programFragmentCreate(); + mPFImages.setName("PFImages"); + } + private void initPV() { mRS.lightBegin(); mLight = mRS.lightCreate(); mLight.setPosition(0, -0.5f, -1.0f); - Log.e("rs", "Done loading named"); - } + mRS.programVertexBegin(null, null); + mRS.programVertexSetTextureMatrixEnable(true); + mRS.programVertexAddLight(mLight); + mPVBackground = mRS.programVertexCreate(); + mPVBackground.setName("PVBackground"); + mRS.programVertexBegin(null, null); + mPVImages = mRS.programVertexCreate(); + mPVImages.setName("PVImages"); + } - private Bitmap mBackground; int mParams[] = new int[10]; private void initRS() { - int partCount = 1024; + mElementVertex = mRS.elementGetPredefined( + RenderScript.ElementPredefined.NORM_ST_XYZ_F32); + mElementIndex = mRS.elementGetPredefined( + RenderScript.ElementPredefined.INDEX_16); + + mRS.triangleMeshBegin(mElementVertex, mElementIndex); + FilmStripMesh fsm = new FilmStripMesh(); + fsm.init(mRS); + mMesh = mRS.triangleMeshCreate(); + mMesh.setName("mesh"); + + initPFS(); + initSamplers(); + initPF(); + initPV(); + mPFImages.bindSampler(mSampler, 0); + + Log.e("rs", "Done loading named"); mRS.scriptCBegin(); mRS.scriptCSetClearColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -172,7 +182,8 @@ public class FilmRS { mBufferPos.length); mPVA = new ProgramVertexAlloc(mRS); - mPV.bindAllocation(0, mPVA.mAlloc); + mPVBackground.bindAllocation(0, mPVA.mAlloc); + mPVImages.bindAllocation(0, mPVA.mAlloc); mPVA.setupProjectionNormalized(320, 480); @@ -181,10 +192,6 @@ public class FilmRS { mScriptStrip.bindAllocation(mPVA.mAlloc, 3); - //mIntAlloc = mRS.allocationCreatePredefSized(RenderScript.ElementPredefined.USER_I32, 10); - //mPartAlloc = mRS.allocationCreatePredefSized(RenderScript.ElementPredefined.USER_I32, partCount * 3 * 3); - //mPartAlloc.setName("PartBuffer"); - //mVertAlloc = mRS.allocationCreatePredefSized(RenderScript.ElementPredefined.USER_I32, partCount * 5 + 1); /* { Resources res = getResources(); @@ -203,25 +210,6 @@ public class FilmRS { mPFS = mRS.programFragmentStoreCreate(); mPFS.setName("MyBlend"); mRS.contextBindProgramFragmentStore(mPFS); - - mRS.samplerBegin(); - mRS.samplerSet(RenderScript.SamplerParam.FILTER_MAG, RenderScript.SamplerValue.LINEAR); - mRS.samplerSet(RenderScript.SamplerParam.FILTER_MIN, RenderScript.SamplerValue.LINEAR); - mSampler = mRS.samplerCreate(); - - - mParams[0] = 0; - mParams[1] = partCount; - mParams[2] = 0; - mParams[3] = 0; - mParams[4] = 0; - mIntAlloc.data(mParams); - - int t2[] = new int[partCount * 4*3]; - for (int ct=0; ct < t2.length; ct++) { - t2[ct] = 0; - } - mPartAlloc.data(t2); */ setFilmStripPosition(0, 0); diff --git a/libs/rs/java/Fountain/AndroidManifest.xml b/libs/rs/java/Fountain/AndroidManifest.xml index a10938bae863..dd0e42876f53 100644 --- a/libs/rs/java/Fountain/AndroidManifest.xml +++ b/libs/rs/java/Fountain/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.android.fountain"> <application android:label="Fountain"> <activity android:name="Fountain" - android:theme="@android:style/Theme.Black.NoTitleBar"> + android:theme="@android:style/Theme.Translucent"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/libs/rs/java/Fountain/res/raw/fountain.c b/libs/rs/java/Fountain/res/raw/fountain.c index 69195654732a..99551fcdf915 100644 --- a/libs/rs/java/Fountain/res/raw/fountain.c +++ b/libs/rs/java/Fountain/res/raw/fountain.c @@ -2,8 +2,8 @@ #pragma version(1) #pragma stateVertex(default) -#pragma stateFragment(PgmFragBackground) -#pragma stateFragmentStore(PFSReplace) +#pragma stateFragment(PgmFragParts) +#pragma stateFragmentStore(PFSBlend) int main(int launchID) { @@ -34,15 +34,11 @@ int main(int launchID) { } } - drawRect(0, 256, 0, 512); - contextBindProgramFragment(NAMED_PgmFragParts); - contextBindProgramFragmentStore(NAMED_PFSBlend); - if (touch) { newPart = loadI32(2, 0); for (ct2=0; ct2<rate; ct2++) { - dx = scriptRand(0x10000) - 0x8000; - dy = scriptRand(0x10000) - 0x8000; + dx = (int)((randf(1.f) - 0.5f) * 0x10000); + dy = (int)((randf(1.f) - 0.5f) * 0x10000); idx = newPart * 5 + 1; storeI32(2, idx, dx); @@ -74,15 +70,15 @@ int main(int launchID) { dstIdx = drawCount * 9; c = 0xffafcf | ((life >> lifeShift) << 24); - storeU32(1, dstIdx, c); + storeI32(1, dstIdx, c); storeI32(1, dstIdx + 1, posx); storeI32(1, dstIdx + 2, posy); - storeU32(1, dstIdx + 3, c); + storeI32(1, dstIdx + 3, c); storeI32(1, dstIdx + 4, posx + 0x10000); storeI32(1, dstIdx + 5, posy + dy * 4); - storeU32(1, dstIdx + 6, c); + storeI32(1, dstIdx + 6, c); storeI32(1, dstIdx + 7, posx - 0x10000); storeI32(1, dstIdx + 8, posy + dy * 4); drawCount ++; diff --git a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java index 745635fb1269..c8e9a1ed9a7f 100644 --- a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java +++ b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java @@ -56,11 +56,7 @@ public class FountainRS { private RenderScript.Allocation mVertAlloc; private RenderScript.Script mScript; private RenderScript.ProgramFragmentStore mPFS; - private RenderScript.ProgramFragmentStore mPFS2; private RenderScript.ProgramFragment mPF; - private RenderScript.ProgramFragment mPF2; - private RenderScript.Allocation mTexture; - private RenderScript.Sampler mSampler; private Bitmap mBackground; @@ -74,16 +70,6 @@ public class FountainRS { mPartAlloc.setName("PartBuffer"); mVertAlloc = mRS.allocationCreatePredefSized(RenderScript.ElementPredefined.USER_I32, partCount * 5 + 1); - { - Drawable d = mRes.getDrawable(R.drawable.gadgets_clock_mp3); - BitmapDrawable bd = (BitmapDrawable)d; - Bitmap b = bd.getBitmap(); - mTexture = mRS.allocationCreateFromBitmap(b, - RenderScript.ElementPredefined.RGB_565, - false); - mTexture.uploadToTexture(0); - } - mRS.programFragmentStoreBegin(null, null); mRS.programFragmentStoreBlendFunc(RenderScript.BlendSrcFunc.SRC_ALPHA, RenderScript.BlendDstFunc.ONE); mRS.programFragmentStoreDepthFunc(RenderScript.DepthFunc.ALWAYS); @@ -92,33 +78,10 @@ public class FountainRS { mPFS = mRS.programFragmentStoreCreate(); mPFS.setName("PFSBlend"); - mRS.programFragmentStoreBegin(null, null); - mRS.programFragmentStoreDepthFunc(RenderScript.DepthFunc.ALWAYS); - mRS.programFragmentStoreDepthMask(false); - mRS.programFragmentStoreDitherEnable(false); - mPFS2 = mRS.programFragmentStoreCreate(); - mPFS2.setName("PFSReplace"); - mRS.contextBindProgramFragmentStore(mPFS2); - - mRS.samplerBegin(); - mRS.samplerSet(RenderScript.SamplerParam.FILTER_MAG, RenderScript.SamplerValue.NEAREST); - mRS.samplerSet(RenderScript.SamplerParam.FILTER_MIN, RenderScript.SamplerValue.NEAREST); - mSampler = mRS.samplerCreate(); - - mRS.programFragmentBegin(null, null); mPF = mRS.programFragmentCreate(); mPF.setName("PgmFragParts"); - mRS.programFragmentBegin(null, null); - mRS.programFragmentSetTexEnable(0, true); - mPF2 = mRS.programFragmentCreate(); - mRS.contextBindProgramFragment(mPF2); - mPF2.bindTexture(mTexture, 0); - mPF2.bindSampler(mSampler, 0); - mPF2.setName("PgmFragBackground"); - - mParams[0] = 0; mParams[1] = partCount; mParams[2] = 0; diff --git a/libs/rs/java/RenderScript/android/renderscript/RenderScript.java b/libs/rs/java/RenderScript/android/renderscript/RenderScript.java index 09d18369f39c..e3556351a952 100644 --- a/libs/rs/java/RenderScript/android/renderscript/RenderScript.java +++ b/libs/rs/java/RenderScript/android/renderscript/RenderScript.java @@ -156,6 +156,7 @@ public class RenderScript { native private void nProgramVertexBegin(int inID, int outID); native private void nProgramVertexSetType(int slot, int mID); native private void nProgramVertexSetTextureMatrixEnable(boolean enable); + native private void nProgramVertexAddLight(int id); native private int nProgramVertexCreate(); native private void nLightBegin(); @@ -720,7 +721,6 @@ public class RenderScript { public void bindAllocation(int slot, Allocation va) { nProgramVertexBindAllocation(mID, slot, va.mID); } - } public void programVertexBegin(Element in, Element out) { @@ -743,6 +743,10 @@ public class RenderScript { nProgramVertexSetTextureMatrixEnable(enable); } + public void programVertexAddLight(Light l) { + nProgramVertexAddLight(l.mID); + } + public ProgramVertex programVertexCreate() { int id = nProgramVertexCreate(); return new ProgramVertex(id); @@ -846,6 +850,10 @@ public class RenderScript { nProgramFragmentSetTexEnable(slot, enable); } + public void programFragmentSetTexEnvMode(int slot, EnvMode env) { + nProgramFragmentSetEnvMode(slot, env.mID); + } + public ProgramFragment programFragmentCreate() { int id = nProgramFragmentCreate(); return new ProgramFragment(id); diff --git a/libs/rs/java/Rollo/AndroidManifest.xml b/libs/rs/java/Rollo/AndroidManifest.xml index da160a37a343..127a140d2397 100644 --- a/libs/rs/java/Rollo/AndroidManifest.xml +++ b/libs/rs/java/Rollo/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.android.rollo"> <application android:label="Rollo"> <activity android:name="Rollo" - android:theme="@android:style/Theme.Black.NoTitleBar"> + android:theme="@android:style/Theme.Translucent"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> diff --git a/libs/rs/java/Rollo/res/drawable/browser.png b/libs/rs/java/Rollo/res/raw/browser.png Binary files differindex 513f0be49693..513f0be49693 100644 --- a/libs/rs/java/Rollo/res/drawable/browser.png +++ b/libs/rs/java/Rollo/res/raw/browser.png diff --git a/libs/rs/java/Rollo/res/drawable/market.png b/libs/rs/java/Rollo/res/raw/market.png Binary files differindex 83b6910fcdec..83b6910fcdec 100644 --- a/libs/rs/java/Rollo/res/drawable/market.png +++ b/libs/rs/java/Rollo/res/raw/market.png diff --git a/libs/rs/java/Rollo/res/drawable/photos.png b/libs/rs/java/Rollo/res/raw/photos.png Binary files differindex 1ed8f1e811ad..1ed8f1e811ad 100644 --- a/libs/rs/java/Rollo/res/drawable/photos.png +++ b/libs/rs/java/Rollo/res/raw/photos.png diff --git a/libs/rs/java/Rollo/res/raw/rollo.c b/libs/rs/java/Rollo/res/raw/rollo.c index f181e4972f72..d338d0df1b0c 100644 --- a/libs/rs/java/Rollo/res/raw/rollo.c +++ b/libs/rs/java/Rollo/res/raw/rollo.c @@ -3,54 +3,98 @@ #pragma stateFragment(PF) #pragma stateFragmentStore(PFS) +// Scratch buffer layout +#define SCRATCH_FADE 0 +#define SCRATCH_ZOOM 1 +#define SCRATCH_ROT 2 + +//#define STATE_POS_X 0 +#define STATE_DONE 1 +//#define STATE_PRESSURE 2 +#define STATE_ZOOM 3 +//#define STATE_WARP 4 +#define STATE_ORIENTATION 5 +#define STATE_SELECTION 6 +#define STATE_FIRST_VISIBLE 7 +#define STATE_COUNT 8 +#define STATE_TOUCH 9 + +float filter(float val, float target, float str) +{ + float delta = (target - val); + return val + delta * str; +} + int main(void* con, int ft, int launchID) { int rowCount; - int x; - int y; int row; int col; int imageID; - int tx1; - int ty1; - int tz1; - int tx2; - int ty2; - int tz2; - int rot; - int rotStep; - int tmpSin; - int tmpCos; - int iconCount; - int pressure; - - - rotStep = 20 * 0x10000; - pressure = loadI32(0, 2); + int done = loadI32(0, STATE_DONE); + int selectedID = loadI32(0, STATE_SELECTION); - rowCount = 4; + float f = loadF(2, 0); - iconCount = loadI32(0, 1); - rot = (-20 + loadI32(0, 0)) * 0x10000; - while (iconCount) { - tmpSin = sinx(rot); - tmpCos = cosx(rot); + pfClearColor(0.0f, 0.0f, 0.0f, f); + if (done) { + if (f > 0.02f) { + //f = f - 0.02f; + //storeF(2, 0, f); + } + } else { + if (f < 0.8f) { + f = f + 0.02f; + storeF(2, 0, f); + } + } - tx1 = tmpSin * 8 - tmpCos; - tx2 = tx1 + tmpCos * 2; + float touchCut = 1.f; + if (loadI32(0, STATE_TOUCH)) { + touchCut = 5.f; + } + + + float targetZoom = ((float)loadI32(0, STATE_ZOOM)) / 1000.f; + float zoom = filter(loadF(2, SCRATCH_ZOOM), targetZoom, 0.15 * touchCut); + storeF(2, SCRATCH_ZOOM, zoom); - tz1 = tmpCos * 8 + tmpSin + pressure; - tz2 = tz1 - tmpSin * 2; + float targetRot = loadI32(0, STATE_FIRST_VISIBLE) / 180.0f * 3.14f; + float rot = filter(loadF(2, SCRATCH_ROT), targetRot, 0.1f * touchCut); + storeF(2, SCRATCH_ROT, rot); - for (y = 0; (y < rowCount) && iconCount; y++) { - ty1 = (y * 0x30000) - 0x48000; - ty2 = ty1 + 0x20000; - pfBindTexture(NAMED_PF, 0, loadI32(1, y)); + float diam = 8.f;// + curve * 2.f; + float scale = 1.0f / zoom; + + rot = rot * scale; + float rotStep = 20.0f / 180.0f * 3.14f * scale; + rowCount = 4; + int index = 0; + int iconCount = loadI32(0, STATE_COUNT); + while (iconCount) { + float tmpSin = sinf(rot); + float tmpCos = cosf(rot); + + float tx1 = tmpSin * diam - (tmpCos * scale); + float tx2 = tx1 + (tmpCos * scale * 2.f); + float tz1 = tmpCos * diam + (tmpSin * scale); + float tz2 = tz1 - (tmpSin * scale * 2.f); + + int y; + for (y = rowCount -1; (y >= 0) && iconCount; y--) { + float ty1 = ((y * 3.0f) - 4.5f) * scale; + float ty2 = ty1 + scale * 2.f; + bindTexture(NAMED_PF, 0, loadI32(1, y)); + color(1.0f, 1.0f, 1.0f, 1.0f); + if (done && (index != selectedID)) { + color(0.4f, 0.4f, 0.4f, 1.0f); + } drawQuad(tx1, ty1, tz1, tx2, ty1, tz2, tx2, ty2, tz2, tx1, ty2, tz1); iconCount--; + index++; } rot = rot + rotStep; } @@ -58,3 +102,4 @@ int main(void* con, int ft, int launchID) return 0; } + diff --git a/libs/rs/java/Rollo/res/raw/rollo2.c b/libs/rs/java/Rollo/res/raw/rollo2.c new file mode 100644 index 000000000000..b04ea73e53f6 --- /dev/null +++ b/libs/rs/java/Rollo/res/raw/rollo2.c @@ -0,0 +1,67 @@ +#pragma version(1) +#pragma stateVertex(PV) +#pragma stateFragment(PF) +#pragma stateFragmentStore(PFS) + +void drawLoop(int x, int y, int z, int rot) +{ + int ct; + int tx; + int ty; + int tmpSin; + int tmpCos; + int sz; + + for (ct = 0; ct < 10; ct ++) { + tmpSin = sinx((ct * 36 + rot) * 0x10000); + tmpCos = cosx((ct * 36 + rot) * 0x10000); + + ty = y + tmpCos * 4; + tx = x + tmpSin * 4; + pfBindTexture(NAMED_PF, 0, loadI32(1, ct & 3)); + + sz = 0xc000; + drawQuad(tx - sz, ty - sz, z, + tx + sz, ty - sz, z, + tx + sz, ty + sz, z, + tx - sz, ty + sz, z); + } +} + +int main(void* con, int ft, int launchID) +{ + int rowCount; + int x; + int y; + int row; + int col; + int imageID; + int tx1; + int ty1; + int tz1; + int tx2; + int ty2; + int tz2; + int tmpSin; + int tmpCos; + int iconCount; + int pressure; + + int ringCount; + + + + rotStep = 16 * 0x10000; + pressure = loadI32(0, 2); + rowCount = 4; + + iconCount = loadI32(0, 1); + rot = (-20 + loadI32(0, 0)) * 0x10000; + + for (ringCount = 0; ringCount < 5; ringCount++) { + drawLoop(0, 0, 0x90000 + (ringCount * 0x80000)); + } + + return 0; +} + diff --git a/libs/rs/java/Rollo/res/drawable/settings.png b/libs/rs/java/Rollo/res/raw/settings.png Binary files differindex dd2cd9570486..dd2cd9570486 100644 --- a/libs/rs/java/Rollo/res/drawable/settings.png +++ b/libs/rs/java/Rollo/res/raw/settings.png diff --git a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java index 91f25c2725bd..8f4833519a62 100644 --- a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java +++ b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java @@ -24,6 +24,7 @@ import android.renderscript.ProgramVertexAlloc; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -37,6 +38,17 @@ import android.view.KeyEvent; import android.view.MotionEvent; public class RolloRS { + //public static final int STATE_SELECTED_ID = 0; + public static final int STATE_DONE = 1; + //public static final int STATE_PRESSURE = 2; + public static final int STATE_ZOOM = 3; + //public static final int STATE_WARP = 4; + public static final int STATE_ORIENTATION = 5; + public static final int STATE_SELECTION = 6; + public static final int STATE_FIRST_VISIBLE = 7; + public static final int STATE_COUNT = 8; + public static final int STATE_TOUCH = 9; + public RolloRS() { } @@ -48,22 +60,37 @@ public class RolloRS { initRS(); } - public void setPosition(float dx, float pressure) { - mAllocStateBuf[0] += (int)(dx); - mAllocStateBuf[2] = (int)(pressure * 0x40000); + public void setPosition(float column) { + mAllocStateBuf[STATE_FIRST_VISIBLE] = (int)(column * (-20)); mAllocState.data(mAllocStateBuf); } + public void setTouch(boolean touch) { + mAllocStateBuf[STATE_TOUCH] = touch ? 1 : 0; + mAllocState.data(mAllocStateBuf); + } - private Resources mRes; - private RenderScript mRS; + public void setZoom(float z) { + //Log.e("rs", "zoom " + Float.toString(z)); + mAllocStateBuf[STATE_ZOOM] = (int)(z * 1000.f); + mAllocState.data(mAllocStateBuf); + } - private RenderScript.Script mScript; + public void setSelected(int index) { + Log.e("rs", "setSelected " + Integer.toString(index)); + + mAllocStateBuf[STATE_SELECTION] = index; + mAllocStateBuf[STATE_DONE] = 1; + mAllocState.data(mAllocStateBuf); + } + + private Resources mRes; + private RenderScript mRS; + private RenderScript.Script mScript; private RenderScript.Sampler mSampler; private RenderScript.ProgramFragmentStore mPFSBackground; - private RenderScript.ProgramFragmentStore mPFSImages; private RenderScript.ProgramFragment mPFBackground; private RenderScript.ProgramFragment mPFImages; private RenderScript.ProgramVertex mPV; @@ -77,6 +104,9 @@ public class RolloRS { private int[] mAllocIconIDBuf; private RenderScript.Allocation mAllocIconID; + private int[] mAllocScratchBuf; + private RenderScript.Allocation mAllocScratch; + private void initNamed() { mRS.samplerBegin(); mRS.samplerSet(RenderScript.SamplerParam.FILTER_MIN, @@ -92,16 +122,13 @@ public class RolloRS { mRS.programFragmentBegin(null, null); mRS.programFragmentSetTexEnable(0, true); - //mRS.programFragmentSetTexEnable(1, true); - //mRS.programFragmentSetEnvMode(0, RS_TEX_ENV_MODE_REPLACE); - //mRS.programFragmentSetEnvMode(1, RS_TEX_ENV_MODE_MODULATE); + mRS.programFragmentSetTexEnvMode(0, RenderScript.EnvMode.MODULATE); mPFImages = mRS.programFragmentCreate(); mPFImages.setName("PF"); mPFImages.bindSampler(mSampler, 0); - mPFImages.bindSampler(mSampler, 1); mRS.programFragmentStoreBegin(null, null); - mRS.programFragmentStoreDepthFunc(RenderScript.DepthFunc.ALWAYS); + mRS.programFragmentStoreDepthFunc(RenderScript.DepthFunc.LESS); mRS.programFragmentStoreDitherEnable(false); mRS.programFragmentStoreDepthMask(false); mRS.programFragmentStoreBlendFunc(RenderScript.BlendSrcFunc.ONE, @@ -109,7 +136,6 @@ public class RolloRS { mPFSBackground = mRS.programFragmentStoreCreate(); mPFSBackground.setName("PFS"); - mPVAlloc = new ProgramVertexAlloc(mRS); mRS.programVertexBegin(null, null); mRS.programVertexSetTextureMatrixEnable(true); @@ -123,10 +149,18 @@ public class RolloRS { //mPVAlloc.setupOrthoNormalized(320, 480); mRS.contextBindProgramVertex(mPV); + mAllocScratchBuf = new int[32]; + for(int ct=0; ct < mAllocScratchBuf.length; ct++) { + mAllocScratchBuf[ct] = 0; + } + mAllocScratch = mRS.allocationCreatePredefSized( + RenderScript.ElementPredefined.USER_I32, mAllocScratchBuf.length); + mAllocScratch.data(mAllocScratchBuf); Log.e("rs", "Done loading named"); + { mIcons = new RenderScript.Allocation[4]; mAllocIconIDBuf = new int[mIcons.length]; @@ -134,20 +168,21 @@ public class RolloRS { RenderScript.ElementPredefined.USER_I32, mAllocIconIDBuf.length); - BitmapDrawable bd; Bitmap b; - - bd = (BitmapDrawable)mRes.getDrawable(R.drawable.browser); - mIcons[0] = mRS.allocationCreateFromBitmap(bd.getBitmap(), RenderScript.ElementPredefined.RGB_565, true); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inScaled = false; - bd = (BitmapDrawable)mRes.getDrawable(R.drawable.market); - mIcons[1] = mRS.allocationCreateFromBitmap(bd.getBitmap(), RenderScript.ElementPredefined.RGB_565, true); + b = BitmapFactory.decodeResource(mRes, R.raw.browser, opts); + mIcons[0] = mRS.allocationCreateFromBitmap(b, RenderScript.ElementPredefined.RGB_565, true); - bd = (BitmapDrawable)mRes.getDrawable(R.drawable.photos); - mIcons[2] = mRS.allocationCreateFromBitmap(bd.getBitmap(), RenderScript.ElementPredefined.RGB_565, true); + b = BitmapFactory.decodeResource(mRes, R.raw.market, opts); + mIcons[1] = mRS.allocationCreateFromBitmap(b, RenderScript.ElementPredefined.RGB_565, true); - bd = (BitmapDrawable)mRes.getDrawable(R.drawable.settings); - mIcons[3] = mRS.allocationCreateFromBitmap(bd.getBitmap(), RenderScript.ElementPredefined.RGB_565, true); + b = BitmapFactory.decodeResource(mRes, R.raw.photos, opts); + mIcons[2] = mRS.allocationCreateFromBitmap(b, RenderScript.ElementPredefined.RGB_565, true); + + b = BitmapFactory.decodeResource(mRes, R.raw.settings, opts); + mIcons[3] = mRS.allocationCreateFromBitmap(b, RenderScript.ElementPredefined.RGB_565, true); for(int ct=0; ct < mIcons.length; ct++) { mIcons[ct].uploadToTexture(0); @@ -190,17 +225,23 @@ public class RolloRS { private void initRS() { mRS.scriptCBegin(); - mRS.scriptCSetClearColor(0.0f, 0.0f, 0.1f, 1.0f); + mRS.scriptCSetClearColor(0.0f, 0.0f, 0.0f, 0.0f); mRS.scriptCSetScript(mRes, R.raw.rollo); + //mRS.scriptCSetScript(mRes, R.raw.rollo2); mRS.scriptCSetRoot(true); + //mRS.scriptCSetClearDepth(0); mScript = mRS.scriptCCreate(); - mAllocStateBuf = new int[] {0, 38, 0}; + mAllocStateBuf = new int[] {0, 0, 0, 8, 0, 0, 0, 0, 38, 0, 0}; mAllocState = mRS.allocationCreatePredefSized( RenderScript.ElementPredefined.USER_I32, mAllocStateBuf.length); mScript.bindAllocation(mAllocState, 0); mScript.bindAllocation(mAllocIconID, 1); - setPosition(0, 0); + mScript.bindAllocation(mAllocScratch, 2); + setPosition(0); + setZoom(1); + + //RenderScript.File f = mRS.fileOpen("/sdcard/test.a3d"); mRS.contextBindRootScript(mScript); } diff --git a/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java b/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java index 27d2dd67d11e..b5e02af9867a 100644 --- a/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java +++ b/libs/rs/java/Rollo/src/com/android/rollo/RolloView.java @@ -19,6 +19,7 @@ package com.android.rollo; import java.io.Writer; import java.util.ArrayList; import java.util.concurrent.Semaphore; +import java.lang.Float; import android.renderscript.RSSurfaceView; import android.renderscript.RenderScript; @@ -37,13 +38,14 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.KeyEvent; import android.view.MotionEvent; +import android.graphics.PixelFormat; -public class RolloView extends RSSurfaceView { +public class RolloView extends RSSurfaceView { public RolloView(Context context) { super(context); - - //setFocusable(true); + setFocusable(true); + getHolder().setFormat(PixelFormat.TRANSLUCENT); } private RenderScript mRS; @@ -65,6 +67,48 @@ public class RolloView extends RSSurfaceView { return super.onKeyDown(keyCode, event); } + boolean mControlMode = false; + boolean mZoomMode = false; + boolean mFlingMode = false; + float mFlingX = 0; + float mFlingY = 0; + float mColumn = -1; + float mOldColumn; + float mZoom = 1; + + int mIconCount = 38; + int mRows = 4; + int mColumns = (mIconCount + mRows - 1) / mRows; + + float mMaxZoom = ((float)mColumns) / 3.f; + + + void setColumn(boolean clamp) + { + //Log.e("rs", " col = " + Float.toString(mColumn)); + float c = mColumn; + if(c > (mColumns -2)) { + c = (mColumns -2); + } + if(c < 1) { + c = 1; + } + mRender.setPosition(c); + if(clamp) { + mColumn = c; + } + } + + void computeSelection(float x, float y) + { + float col = mColumn + (x - 0.5f) * 3; + int iCol = (int)(col + 0.25f); + + float row = (y / 0.8f) * mRows; + int iRow = (int)(row - 0.25f); + + mRender.setSelected(iCol * mRows + iRow); + } @Override public boolean onTouchEvent(MotionEvent ev) @@ -74,14 +118,91 @@ public class RolloView extends RSSurfaceView { if (act == ev.ACTION_UP) { ret = false; } - float x = ev.getX(); - x = (x - 180) / 40; - //Log.e("rs", Float(x).toString()); - mRender.setPosition(x, ev.getPressure()); - //mRender.newTouchPosition((int)ev.getX(), (int)ev.getY()); + float nx = ev.getX() / getWidth(); + float ny = ev.getY() / getHeight(); + + mRender.setTouch(ret); + + if((ny > 0.85f) || mControlMode) { + mFlingMode = false; + + // Projector control + if((nx > 0.2f) && (nx < 0.8f) || mControlMode) { + if(act != ev.ACTION_UP) { + float zoom = mMaxZoom; + if(mControlMode) { + if(!mZoomMode) { + zoom = 1.f; + } + float dx = nx - mFlingX; + + if((ny < 0.9) && mZoomMode) { + zoom = mMaxZoom - ((0.9f - ny) * 10.f); + if(zoom < 1) { + zoom = 1; + mZoomMode = false; + } + mOldColumn = mColumn; + } + mColumn += dx * 4;// * zoom; + if(zoom > 1.01f) { + mColumn += (mZoom - zoom) * (nx - 0.5f) * 4 * zoom; + } + } else { + mOldColumn = mColumn; + mColumn = ((float)mColumns) / 2; + mControlMode = true; + mZoomMode = true; + } + mZoom = zoom; + mFlingX = nx; + mRender.setZoom(zoom); + } else { + if(mControlMode && (mZoom < 1.01f)) { + computeSelection(nx, ny); + } + mControlMode = false; + mColumn = mOldColumn; + mRender.setZoom(1.f); + } + } else { + // Do something with corners here.... + } + setColumn(true); + + } else { + // icon control + if(act != ev.ACTION_UP) { + if(mFlingMode) { + mColumn += (mFlingX - nx) * 4; + setColumn(true); + } + mFlingMode = true; + mFlingX = nx; + mFlingY = ny; + } else { + mFlingMode = false; + } + } + + return ret; } + + @Override + public boolean onTrackballEvent(MotionEvent ev) + { + float x = ev.getX(); + float y = ev.getY(); + //Float tx = new Float(x); + //Float ty = new Float(y); + //Log.e("rs", "tbe " + tx.toString() + ", " + ty.toString()); + + + return true; + } + } diff --git a/libs/rs/jni/RenderScript_jni.cpp b/libs/rs/jni/RenderScript_jni.cpp index 248a6bd7c264..17476734f178 100644 --- a/libs/rs/jni/RenderScript_jni.cpp +++ b/libs/rs/jni/RenderScript_jni.cpp @@ -810,6 +810,14 @@ nProgramVertexSetTextureMatrixEnable(JNIEnv *_env, jobject _this, jboolean enabl rsProgramVertexSetTextureMatrixEnable(enable); } +static void +nProgramVertexAddLight(JNIEnv *_env, jobject _this, jint light) +{ + RsContext con = (RsContext)(_env->GetIntField(_this, gContextId)); + LOG_API("nProgramVertexAddLight, con(%p), light(%p)", con, (RsLight)light); + rsProgramVertexAddLight((RsLight)light); +} + static jint nProgramVertexCreate(JNIEnv *_env, jobject _this) { @@ -1048,6 +1056,7 @@ static JNINativeMethod methods[] = { {"nProgramVertexBegin", "(II)V", (void*)nProgramVertexBegin }, {"nProgramVertexSetType", "(II)V", (void*)nProgramVertexSetType }, {"nProgramVertexSetTextureMatrixEnable", "(Z)V", (void*)nProgramVertexSetTextureMatrixEnable }, +{"nProgramVertexAddLight", "(I)V", (void*)nProgramVertexAddLight }, {"nProgramVertexCreate", "()I", (void*)nProgramVertexCreate }, {"nLightBegin", "()V", (void*)nLightBegin }, diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec index 62533af020bb..2f99808b8682 100644 --- a/libs/rs/rs.spec +++ b/libs/rs/rs.spec @@ -403,6 +403,10 @@ ProgramVertexSetTextureMatrixEnable { param bool enable } +ProgramVertexAddLight { + param RsLight light + } + LightBegin { } diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp index 4025ff81a008..78b8bf89e9e8 100644 --- a/libs/rs/rsContext.cpp +++ b/libs/rs/rsContext.cpp @@ -35,9 +35,16 @@ void Context::initEGL() EGLint s_configAttribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, +#if 1 + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, +#else EGL_RED_SIZE, 5, EGL_GREEN_SIZE, 6, EGL_BLUE_SIZE, 5, +#endif EGL_DEPTH_SIZE, 16, EGL_NONE }; diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h index a7fd17c51614..497dbcf7620f 100644 --- a/libs/rs/rsContext.h +++ b/libs/rs/rsContext.h @@ -17,6 +17,8 @@ #ifndef ANDROID_RS_CONTEXT_H #define ANDROID_RS_CONTEXT_H +#include "rsUtils.h" + #include <utils/Vector.h> #include <ui/Surface.h> @@ -30,10 +32,10 @@ #include "rsAllocation.h" #include "rsAdapter.h" #include "rsSampler.h" +#include "rsLight.h" #include "rsProgramFragment.h" #include "rsProgramFragmentStore.h" #include "rsProgramVertex.h" -#include "rsLight.h" #include "rsgApiStructs.h" #include "rsLocklessFifo.h" diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp index f669417d11cb..86d294bf3160 100644 --- a/libs/rs/rsFileA3D.cpp +++ b/libs/rs/rsFileA3D.cpp @@ -261,9 +261,12 @@ void FileA3D::processChunk_Primitive(Context *rsc, IO *io, A3DIndexEntry *ie) p->mIndexCount = io->loadU32(); uint32_t vertIdx = io->loadU32(); + p->mRestartCounts = io->loadU16(); uint32_t bits = io->loadU8(); p->mType = (RsPrimitive)io->loadU8(); + LOGE("processChunk_Primitive count %i, bits %i", p->mIndexCount, bits); + p->mVerticies = (Mesh::Verticies_t *)mIndex[vertIdx].mRsObj; p->mIndicies = new uint16_t[p->mIndexCount]; @@ -279,6 +282,27 @@ void FileA3D::processChunk_Primitive(Context *rsc, IO *io, A3DIndexEntry *ie) p->mIndicies[ct] = io->loadU32(); break; } + LOGE(" idx %i", p->mIndicies[ct]); + } + + if (p->mRestartCounts) { + p->mRestarts = new uint16_t[p->mRestartCounts]; + for (uint32_t ct = 0; ct < p->mRestartCounts; ct++) { + switch(bits) { + case 8: + p->mRestarts[ct] = io->loadU8(); + break; + case 16: + p->mRestarts[ct] = io->loadU16(); + break; + case 32: + p->mRestarts[ct] = io->loadU32(); + break; + } + LOGE(" idx %i", p->mRestarts[ct]); + } + } else { + p->mRestarts = NULL; } ie->mRsObj = p; @@ -289,16 +313,17 @@ void FileA3D::processChunk_Verticies(Context *rsc, IO *io, A3DIndexEntry *ie) Mesh::Verticies_t *cv = new Mesh::Verticies_t; cv->mAllocationCount = io->loadU32(); cv->mAllocations = new Allocation *[cv->mAllocationCount]; + LOGE("processChunk_Verticies count %i", cv->mAllocationCount); for (uint32_t ct = 0; ct < cv->mAllocationCount; ct++) { uint32_t i = io->loadU32(); cv->mAllocations[ct] = (Allocation *)mIndex[i].mRsObj; + LOGE(" idx %i", i); } ie->mRsObj = cv; } void FileA3D::processChunk_Element(Context *rsc, IO *io, A3DIndexEntry *ie) { - LOGE("processChunk_Element ie %p", ie); rsi_ElementBegin(rsc); uint32_t count = io->loadU32(); @@ -320,6 +345,8 @@ void FileA3D::processChunk_ElementSource(Context *rsc, IO *io, A3DIndexEntry *ie uint32_t index = io->loadU32(); uint32_t count = io->loadU32(); + LOGE("processChunk_ElementSource count %i, index %i", count, index); + RsElement e = (RsElement)mIndex[index].mRsObj; RsAllocation a = rsi_AllocationCreateSized(rsc, e, count); @@ -328,6 +355,7 @@ void FileA3D::processChunk_ElementSource(Context *rsc, IO *io, A3DIndexEntry *ie float * data = (float *)alloc->getPtr(); while(count--) { *data = io->loadF(); + LOGE(" %f", *data); data++; } ie->mRsObj = alloc; diff --git a/libs/rs/rsLight.cpp b/libs/rs/rsLight.cpp index 67d009544402..24b58b622ac6 100644 --- a/libs/rs/rsLight.cpp +++ b/libs/rs/rsLight.cpp @@ -16,6 +16,8 @@ #include "rsContext.h" +#include <GLES/gl.h> + using namespace android; using namespace android::renderscript; @@ -25,13 +27,15 @@ Light::Light(bool isLocal, bool isMono) mIsLocal = isLocal; mIsMono = isMono; - mX = 0; - mY = 0; - mZ = 0; + mPosition[0] = 0; + mPosition[1] = 0; + mPosition[2] = 1; + mPosition[3] = 0; - mR = 1.f; - mG = 1.f; - mB = 1.f; + mColor[0] = 1.f; + mColor[1] = 1.f; + mColor[2] = 1.f; + mColor[3] = 1.f; } Light::~Light() @@ -40,16 +44,23 @@ Light::~Light() void Light::setPosition(float x, float y, float z) { - mX = x; - mY = y; - mZ = z; + mPosition[0] = x; + mPosition[1] = y; + mPosition[2] = z; } void Light::setColor(float r, float g, float b) { - mR = r; - mG = g; - mB = b; + mColor[0] = r; + mColor[1] = g; + mColor[2] = b; +} + +void Light::setupGL(uint32_t num) const +{ + glLightfv(GL_LIGHT0 + num, GL_DIFFUSE, mColor); + glLightfv(GL_LIGHT0 + num, GL_SPECULAR, mColor); + glLightfv(GL_LIGHT0 + num, GL_POSITION, mPosition); } //////////////////////////////////////////// diff --git a/libs/rs/rsLight.h b/libs/rs/rsLight.h index 76d1eccacdb7..b0c33862f52d 100644 --- a/libs/rs/rsLight.h +++ b/libs/rs/rsLight.h @@ -36,9 +36,11 @@ public: void setPosition(float x, float y, float z); void setColor(float r, float g, float b); + void setupGL(uint32_t num) const; + protected: - float mR, mG, mB; - float mX, mY, mZ; + float mColor[4]; + float mPosition[4]; bool mIsLocal; bool mIsMono; }; diff --git a/libs/rs/rsMesh.h b/libs/rs/rsMesh.h index 1db36e1aa635..be207a3adab0 100644 --- a/libs/rs/rsMesh.h +++ b/libs/rs/rsMesh.h @@ -57,6 +57,9 @@ public: uint32_t mIndexCount; uint16_t *mIndicies; + + uint32_t mRestartCounts; + uint16_t *mRestarts; }; Verticies_t * mVerticies; diff --git a/libs/rs/rsProgramFragmentStore.cpp b/libs/rs/rsProgramFragmentStore.cpp index 0b6568036bb5..9ee270f2226e 100644 --- a/libs/rs/rsProgramFragmentStore.cpp +++ b/libs/rs/rsProgramFragmentStore.cpp @@ -61,8 +61,10 @@ void ProgramFragmentStore::setupGL() glDisable(GL_BLEND); } + //LOGE("pfs %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc); + glDepthMask(mDepthWriteEnable); - if(mDepthTestEnable) { + if(mDepthTestEnable || mDepthWriteEnable) { glEnable(GL_DEPTH_TEST); glDepthFunc(mDepthFunc); } else { diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp index 4089507bbb98..417ba6aed13e 100644 --- a/libs/rs/rsProgramVertex.cpp +++ b/libs/rs/rsProgramVertex.cpp @@ -28,6 +28,7 @@ ProgramVertex::ProgramVertex(Element *in, Element *out) : Program(in, out) { mTextureMatrixEnable = false; + mLightCount = 0; } ProgramVertex::~ProgramVertex() @@ -54,8 +55,29 @@ void ProgramVertex::setupGL() glLoadIdentity(); } - //logMatrix("prog", &f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]); - //logMatrix("model", &f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET]); + + LOGE("lights %i ", mLightCount); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + if (mLightCount) { + int v = 1; + glEnable(GL_LIGHTING); + glLightModelxv(GL_LIGHT_MODEL_TWO_SIDE, &v); + for (uint32_t ct = 0; ct < mLightCount; ct++) { + const Light *l = mLights[ct].get(); + glEnable(GL_LIGHT0 + ct); + l->setupGL(ct); + } + for (uint32_t ct = mLightCount; ct < MAX_LIGHTS; ct++) { + glDisable(GL_LIGHT0 + ct); + } + } else { + glDisable(GL_LIGHTING); + } + + if (!f) { + LOGE("Must bind constants to vertex program"); + } glMatrixMode(GL_PROJECTION); glLoadMatrixf(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]); @@ -73,6 +95,14 @@ void ProgramVertex::bindAllocation(uint32_t slot, Allocation *a) mConstants[slot].set(a); } +void ProgramVertex::addLight(const Light *l) +{ + if (mLightCount < MAX_LIGHTS) { + mLights[mLightCount].set(l); + mLightCount++; + } +} + ProgramVertexState::ProgramVertexState() { @@ -136,6 +166,10 @@ void rsi_ProgramVertexSetTextureMatrixEnable(Context *rsc, bool enable) rsc->mStateVertex.mPV->setTextureMatrixEnable(enable); } +void rsi_ProgramVertexAddLight(Context *rsc, RsLight light) +{ + rsc->mStateVertex.mPV->addLight(static_cast<const Light *>(light)); +} } diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h index 1a92f013c7b1..ac15b70a7cb6 100644 --- a/libs/rs/rsProgramVertex.h +++ b/libs/rs/rsProgramVertex.h @@ -28,6 +28,7 @@ class ProgramVertex : public Program { public: const static uint32_t MAX_CONSTANTS = 2; + const static uint32_t MAX_LIGHTS = 8; ProgramVertex(Element *in, Element *out); virtual ~ProgramVertex(); @@ -38,12 +39,16 @@ public: void setConstantType(uint32_t slot, const Type *); void bindAllocation(uint32_t slot, Allocation *); void setTextureMatrixEnable(bool e) {mTextureMatrixEnable = e;} + void addLight(const Light *); protected: bool mDirty; + uint32_t mLightCount; ObjectBaseRef<Allocation> mConstants[MAX_CONSTANTS]; ObjectBaseRef<const Type> mConstantTypes[MAX_CONSTANTS]; + ObjectBaseRef<const Light> mLights[MAX_LIGHTS]; + // Hacks to create a program for now bool mTextureMatrixEnable; @@ -61,6 +66,8 @@ public: ObjectBaseRef<ProgramVertex> mDefault; ObjectBaseRef<Allocation> mDefaultAlloc; + + ProgramVertex *mPV; diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index 0ec6bafe9730..842c836b9ec5 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -46,415 +46,6 @@ ScriptC::~ScriptC() } } -extern "C" float fixedToFloat(int32_t f) -{ - return ((float)f) / 0x10000; -} - -extern "C" float intToFloat(int32_t f) -{ - return (float)f; -} - -extern "C" void matrixLoadIdentity(rsc_Matrix *mat) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->loadIdentity(); -} - -extern "C" void matrixLoadFloat(rsc_Matrix *mat, const float *f) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->load(f); -} - -extern "C" void matrixLoadMat(rsc_Matrix *mat, const rsc_Matrix *newmat) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->load(reinterpret_cast<const Matrix *>(newmat)); -} - -extern "C" void matrixLoadRotate(rsc_Matrix *mat, float rot, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->loadRotate(rot, x, y, z); -} - -extern "C" void matrixLoadScale(rsc_Matrix *mat, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->loadScale(x, y, z); -} - -extern "C" void matrixLoadTranslate(rsc_Matrix *mat, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->loadTranslate(x, y, z); -} - -extern "C" void matrixLoadMultiply(rsc_Matrix *mat, const rsc_Matrix *lhs, const rsc_Matrix *rhs) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->loadMultiply(reinterpret_cast<const Matrix *>(lhs), - reinterpret_cast<const Matrix *>(rhs)); -} - -extern "C" void matrixMultiply(rsc_Matrix *mat, const rsc_Matrix *rhs) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->multiply(reinterpret_cast<const Matrix *>(rhs)); -} - -extern "C" void matrixRotate(rsc_Matrix *mat, float rot, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->rotate(rot, x, y, z); -} - -extern "C" void matrixScale(rsc_Matrix *mat, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->scale(x, y, z); -} - -extern "C" void matrixTranslate(rsc_Matrix *mat, float x, float y, float z) -{ - Matrix *m = reinterpret_cast<Matrix *>(mat); - m->translate(x, y, z); -} - - -extern "C" const void * loadVp(uint32_t bank, uint32_t offset) -{ - GET_TLS(); - return &static_cast<const uint8_t *>(sc->mSlots[bank]->getPtr())[offset]; -} - -extern "C" float loadF(uint32_t bank, uint32_t offset) -{ - GET_TLS(); - return static_cast<const float *>(sc->mSlots[bank]->getPtr())[offset]; -} - -extern "C" int32_t loadI32(uint32_t bank, uint32_t offset) -{ - GET_TLS(); - return static_cast<const int32_t *>(sc->mSlots[bank]->getPtr())[offset]; -} - -extern "C" uint32_t loadU32(uint32_t bank, uint32_t offset) -{ - GET_TLS(); - return static_cast<const uint32_t *>(sc->mSlots[bank]->getPtr())[offset]; -} - -extern "C" void loadEnvVec4(uint32_t bank, uint32_t offset, rsc_Vector4 *v) -{ - GET_TLS(); - memcpy(v, &static_cast<const float *>(sc->mSlots[bank]->getPtr())[offset], sizeof(rsc_Vector4)); -} - -extern "C" void loadEnvMatrix(uint32_t bank, uint32_t offset, rsc_Matrix *m) -{ - GET_TLS(); - memcpy(m, &static_cast<const float *>(sc->mSlots[bank]->getPtr())[offset], sizeof(rsc_Matrix)); -} - - -extern "C" void storeF(uint32_t bank, uint32_t offset, float v) -{ - GET_TLS(); - static_cast<float *>(sc->mSlots[bank]->getPtr())[offset] = v; -} - -extern "C" void storeI32(uint32_t bank, uint32_t offset, int32_t v) -{ - GET_TLS(); - static_cast<int32_t *>(sc->mSlots[bank]->getPtr())[offset] = v; -} - -extern "C" void storeU32(uint32_t bank, uint32_t offset, uint32_t v) -{ - GET_TLS(); - static_cast<uint32_t *>(sc->mSlots[bank]->getPtr())[offset] = v; -} - -extern "C" void storeEnvVec4(uint32_t bank, uint32_t offset, const rsc_Vector4 *v) -{ - GET_TLS(); - memcpy(&static_cast<float *>(sc->mSlots[bank]->getPtr())[offset], v, sizeof(rsc_Vector4)); -} - -extern "C" void storeEnvMatrix(uint32_t bank, uint32_t offset, const rsc_Matrix *m) -{ - GET_TLS(); - memcpy(&static_cast<float *>(sc->mSlots[bank]->getPtr())[offset], m, sizeof(rsc_Matrix)); -} - - -extern "C" void color(float r, float g, float b, float a) -{ - glColor4f(r, g, b, a); -} - -extern "C" void renderTriangleMesh(RsTriangleMesh mesh) -{ - GET_TLS(); - rsi_TriangleMeshRender(rsc, mesh); -} - -extern "C" void renderTriangleMeshRange(RsTriangleMesh mesh, uint32_t start, uint32_t count) -{ - GET_TLS(); - rsi_TriangleMeshRenderRange(rsc, mesh, start, count); -} - -extern "C" void materialDiffuse(float r, float g, float b, float a) -{ - float v[] = {r, g, b, a}; - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, v); -} - -extern "C" void materialSpecular(float r, float g, float b, float a) -{ - float v[] = {r, g, b, a}; - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, v); -} - -extern "C" void lightPosition(float x, float y, float z, float w) -{ - float v[] = {x, y, z, w}; - glLightfv(GL_LIGHT0, GL_POSITION, v); -} - -extern "C" void materialShininess(float s) -{ - glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &s); -} - -extern "C" void uploadToTexture(RsAllocation va, uint32_t baseMipLevel) -{ - GET_TLS(); - rsi_AllocationUploadToTexture(rsc, va, baseMipLevel); -} - -extern "C" void enable(uint32_t p) -{ - glEnable(p); -} - -extern "C" void disable(uint32_t p) -{ - glDisable(p); -} - -extern "C" uint32_t scriptRand(uint32_t max) -{ - return (uint32_t)(((float)rand()) * max / RAND_MAX); -} - -// Assumes (GL_FIXED) x,y,z (GL_UNSIGNED_BYTE)r,g,b,a -extern "C" void drawTriangleArray(RsAllocation alloc, uint32_t count) -{ - GET_TLS(); - - const Allocation *a = (const Allocation *)alloc; - const uint32_t *ptr = (const uint32_t *)a->getPtr(); - - rsc->setupCheck(); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); - - glEnableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - glVertexPointer(2, GL_FIXED, 12, ptr + 1); - //glTexCoordPointer(2, GL_FIXED, 24, ptr + 1); - glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); - - glDrawArrays(GL_TRIANGLES, 0, count * 3); -} - -extern "C" void drawRect(int32_t x1, int32_t x2, int32_t y1, int32_t y2) -{ - GET_TLS(); - x1 = (x1 << 16); - x2 = (x2 << 16); - y1 = (y1 << 16); - y2 = (y2 << 16); - - int32_t vtx[] = {x1,y1, x1,y2, x2,y1, x2,y2}; - static const int32_t tex[] = {0,0, 0,0x10000, 0x10000,0, 0x10000,0x10000}; - - - rsc->setupCheck(); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - glVertexPointer(2, GL_FIXED, 8, vtx); - glTexCoordPointer(2, GL_FIXED, 8, tex); - //glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); -} - -extern "C" void drawQuad(int32_t x1, int32_t y1, int32_t z1, - int32_t x2, int32_t y2, int32_t z2, - int32_t x3, int32_t y3, int32_t z3, - int32_t x4, int32_t y4, int32_t z4) -{ - GET_TLS(); - //x1 = (x1 << 16); - //x2 = (x2 << 16); - //y1 = (y1 << 16); - //y2 = (y2 << 16); - - //LOGE("Quad"); - //LOGE("0x%08x, 0x%08x, 0x%08x", x1, y1, z1); - //LOGE("0x%08x, 0x%08x, 0x%08x", x2, y2, z2); - //LOGE("0x%08x, 0x%08x, 0x%08x", x3, y3, z3); - //LOGE("0x%08x, 0x%08x, 0x%08x", x4, y4, z4); - - int32_t vtx[] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4}; - static const int32_t tex[] = {0,0, 0,0x10000, 0x10000,0x10000, 0x10000,0}; - - - rsc->setupCheck(); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); - - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(3, GL_FIXED, 0, vtx); - - glClientActiveTexture(GL_TEXTURE0); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexCoordPointer(2, GL_FIXED, 0, tex); - glClientActiveTexture(GL_TEXTURE1); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexCoordPointer(2, GL_FIXED, 0, tex); - glClientActiveTexture(GL_TEXTURE0); - - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - //glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); -} - -extern "C" int32_t sinx(int32_t angle) -{ - float a = ((float)angle) / 0x10000; - a *= 3.14f / 180.f; - float s = (float)sin(a); - return int32_t(s * 0x10000); -} - -extern "C" int32_t cosx(int32_t angle) -{ - float a = ((float)angle) / 0x10000; - a *= 3.14f / 180.f; - float s = (float)cos(a); - return int32_t(s * 0x10000); -} - -extern "C" void pfBindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va) -{ - GET_TLS(); - rsi_ProgramFragmentBindTexture(rsc, - static_cast<ProgramFragment *>(vpf), - slot, - static_cast<Allocation *>(va)); - -} - -extern "C" void pfBindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs) -{ - GET_TLS(); - rsi_ProgramFragmentBindSampler(rsc, - static_cast<ProgramFragment *>(vpf), - slot, - static_cast<Sampler *>(vs)); - -} - -extern "C" void contextBindProgramFragmentStore(RsProgramFragmentStore pfs) -{ - GET_TLS(); - rsi_ContextBindProgramFragmentStore(rsc, pfs); - -} - -extern "C" void contextBindProgramFragment(RsProgramFragment pf) -{ - GET_TLS(); - rsi_ContextBindProgramFragment(rsc, pf); - -} - - -static rsc_FunctionTable scriptCPtrTable = { - loadVp, - loadF, - loadI32, - loadU32, - loadEnvVec4, - loadEnvMatrix, - - storeF, - storeI32, - storeU32, - storeEnvVec4, - storeEnvMatrix, - - matrixLoadIdentity, - matrixLoadFloat, - matrixLoadMat, - matrixLoadRotate, - matrixLoadScale, - matrixLoadTranslate, - matrixLoadMultiply, - matrixMultiply, - matrixRotate, - matrixScale, - matrixTranslate, - - color, - - pfBindTexture, - pfBindSampler, - - materialDiffuse, - materialSpecular, - lightPosition, - materialShininess, - uploadToTexture, - enable, - disable, - - scriptRand, - contextBindProgramFragment, - contextBindProgramFragmentStore, - - - renderTriangleMesh, - renderTriangleMeshRange, - - drawTriangleArray, - drawRect - -}; - bool ScriptC::run(Context *rsc, uint32_t launchIndex) { @@ -471,9 +62,11 @@ bool ScriptC::run(Context *rsc, uint32_t launchIndex) rsc->setVertex(mEnviroment.mVertex.get()); } + bool ret = false; tls->mScript = this; - return mProgram.mScript(launchIndex, &scriptCPtrTable) != 0; + ret = mProgram.mScript(launchIndex) != 0; tls->mScript = NULL; + return ret; } ScriptCState::ScriptCState() @@ -507,6 +100,15 @@ void ScriptCState::clear() } +static ACCvoid* symbolLookup(ACCvoid* pContext, const ACCchar* name) +{ + const ScriptCState::SymbolTable_t *sym = ScriptCState::lookupSymbol(name); + if (sym) { + return sym->mPtr; + } + LOGE("ScriptC sym lookup failed for %s", name); + return NULL; +} void ScriptCState::runCompiler(Context *rsc) { @@ -514,14 +116,25 @@ void ScriptCState::runCompiler(Context *rsc) String8 tmp; rsc->appendNameDefines(&tmp); + appendDecls(&tmp); + tmp.append("#line 1\n"); const char* scriptSource[] = {tmp.string(), mProgram.mScriptText}; int scriptLength[] = {tmp.length(), mProgram.mScriptTextLength} ; accScriptSource(mAccScript, sizeof(scriptLength) / sizeof(int), scriptSource, scriptLength); + accRegisterSymbolCallback(mAccScript, symbolLookup, NULL); accCompileScript(mAccScript); accGetScriptLabel(mAccScript, "main", (ACCvoid**) &mProgram.mScript); rsAssert(mProgram.mScript); + if (!mProgram.mScript) { + ACCchar buf[4096]; + ACCsizei len; + accGetScriptInfoLog(mAccScript, sizeof(buf), &len, buf); + LOGE(buf); + + } + mEnviroment.mFragment.set(rsc->getDefaultProgramFragment()); mEnviroment.mVertex.set(rsc->getDefaultProgramVertex()); mEnviroment.mFragmentStore.set(rsc->getDefaultProgramFragmentStore()); @@ -638,7 +251,7 @@ void rsi_ScriptCAddType(Context * rsc, RsType vt) void rsi_ScriptCSetScript(Context * rsc, void *vp) { ScriptCState *ss = &rsc->mScriptC; - ss->mProgram.mScript = reinterpret_cast<rsc_RunScript>(vp); + ss->mProgram.mScript = reinterpret_cast<ScriptC::RunScript_t>(vp); } void rsi_ScriptCSetRoot(Context * rsc, bool isRoot) diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h index c46901b14806..860b4d19e1ab 100644 --- a/libs/rs/rsScriptC.h +++ b/libs/rs/rsScriptC.h @@ -32,6 +32,7 @@ namespace renderscript { class ScriptC : public Script { public: + typedef int (*RunScript_t)(uint32_t launchIndex); ScriptC(); virtual ~ScriptC(); @@ -44,7 +45,7 @@ public: int mVersionMajor; int mVersionMinor; - rsc_RunScript mScript; + RunScript_t mScript; }; Program_t mProgram; @@ -69,6 +70,16 @@ public: void clear(); void runCompiler(Context *rsc); + + struct SymbolTable_t { + const char * mName; + void * mPtr; + const char * mRet; + const char * mParam; + }; + static SymbolTable_t gSyms[]; + static const SymbolTable_t * lookupSymbol(const char *); + static void appendDecls(String8 *str); }; diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp new file mode 100644 index 000000000000..129b19fc5a54 --- /dev/null +++ b/libs/rs/rsScriptC_Lib.cpp @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rsContext.h" +#include "rsScriptC.h" +#include "rsMatrix.h" + +#include "acc/acc.h" +#include "utils/String8.h" + +#include <GLES/gl.h> +#include <GLES/glext.h> + +using namespace android; +using namespace android::renderscript; + +#define GET_TLS() Context::ScriptTLSStruct * tls = \ + (Context::ScriptTLSStruct *)pthread_getspecific(Context::gThreadTLSKey); \ + Context * rsc = tls->mContext; \ + ScriptC * sc = (ScriptC *) tls->mScript + + +////////////////////////////////////////////////////////////////////////////// +// IO routines +////////////////////////////////////////////////////////////////////////////// + +static float SC_loadF(uint32_t bank, uint32_t offset) +{ + GET_TLS(); + const void *vp = sc->mSlots[bank]->getPtr(); + const float *f = static_cast<const float *>(vp); + //LOGE("loadF %i %i = %f %x", bank, offset, f, ((int *)&f)[0]); + return f[offset]; +} + +static int32_t SC_loadI32(uint32_t bank, uint32_t offset) +{ + GET_TLS(); + const void *vp = sc->mSlots[bank]->getPtr(); + const int32_t *i = static_cast<const int32_t *>(vp); + //LOGE("loadI32 %i %i = %i", bank, offset, t); + return i[offset]; +} + +static uint32_t SC_loadU32(uint32_t bank, uint32_t offset) +{ + GET_TLS(); + const void *vp = sc->mSlots[bank]->getPtr(); + const uint32_t *i = static_cast<const uint32_t *>(vp); + return i[offset]; +} + +static void SC_loadVec4(uint32_t bank, uint32_t offset, rsc_Vector4 *v) +{ + GET_TLS(); + const void *vp = sc->mSlots[bank]->getPtr(); + const float *f = static_cast<const float *>(vp); + memcpy(v, &f[offset], sizeof(rsc_Vector4)); +} + +static void SC_loadMatrix(uint32_t bank, uint32_t offset, rsc_Matrix *m) +{ + GET_TLS(); + const void *vp = sc->mSlots[bank]->getPtr(); + const float *f = static_cast<const float *>(vp); + memcpy(m, &f[offset], sizeof(rsc_Matrix)); +} + + +static void SC_storeF(uint32_t bank, uint32_t offset, float v) +{ + //LOGE("storeF %i %i %f", bank, offset, v); + GET_TLS(); + void *vp = sc->mSlots[bank]->getPtr(); + float *f = static_cast<float *>(vp); + f[offset] = v; +} + +static void SC_storeI32(uint32_t bank, uint32_t offset, int32_t v) +{ + GET_TLS(); + void *vp = sc->mSlots[bank]->getPtr(); + int32_t *f = static_cast<int32_t *>(vp); + static_cast<int32_t *>(sc->mSlots[bank]->getPtr())[offset] = v; +} + +static void SC_storeU32(uint32_t bank, uint32_t offset, uint32_t v) +{ + GET_TLS(); + void *vp = sc->mSlots[bank]->getPtr(); + uint32_t *f = static_cast<uint32_t *>(vp); + static_cast<uint32_t *>(sc->mSlots[bank]->getPtr())[offset] = v; +} + +static void SC_storeVec4(uint32_t bank, uint32_t offset, const rsc_Vector4 *v) +{ + GET_TLS(); + void *vp = sc->mSlots[bank]->getPtr(); + float *f = static_cast<float *>(vp); + memcpy(&f[offset], v, sizeof(rsc_Vector4)); +} + +static void SC_storeMatrix(uint32_t bank, uint32_t offset, const rsc_Matrix *m) +{ + GET_TLS(); + void *vp = sc->mSlots[bank]->getPtr(); + float *f = static_cast<float *>(vp); + memcpy(&f[offset], m, sizeof(rsc_Matrix)); +} + + +////////////////////////////////////////////////////////////////////////////// +// Math routines +////////////////////////////////////////////////////////////////////////////// + +static float SC_randf(float max) +{ + float r = (float)rand(); + return r / RAND_MAX * max; +} + + + + + +////////////////////////////////////////////////////////////////////////////// +// Matrix routines +////////////////////////////////////////////////////////////////////////////// + + +static void SC_matrixLoadIdentity(rsc_Matrix *mat) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->loadIdentity(); +} + +static void SC_matrixLoadFloat(rsc_Matrix *mat, const float *f) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->load(f); +} + +static void SC_matrixLoadMat(rsc_Matrix *mat, const rsc_Matrix *newmat) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->load(reinterpret_cast<const Matrix *>(newmat)); +} + +static void SC_matrixLoadRotate(rsc_Matrix *mat, float rot, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->loadRotate(rot, x, y, z); +} + +static void SC_matrixLoadScale(rsc_Matrix *mat, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->loadScale(x, y, z); +} + +static void SC_matrixLoadTranslate(rsc_Matrix *mat, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->loadTranslate(x, y, z); +} + +static void SC_matrixLoadMultiply(rsc_Matrix *mat, const rsc_Matrix *lhs, const rsc_Matrix *rhs) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->loadMultiply(reinterpret_cast<const Matrix *>(lhs), + reinterpret_cast<const Matrix *>(rhs)); +} + +static void SC_matrixMultiply(rsc_Matrix *mat, const rsc_Matrix *rhs) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->multiply(reinterpret_cast<const Matrix *>(rhs)); +} + +static void SC_matrixRotate(rsc_Matrix *mat, float rot, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->rotate(rot, x, y, z); +} + +static void SC_matrixScale(rsc_Matrix *mat, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->scale(x, y, z); +} + +static void SC_matrixTranslate(rsc_Matrix *mat, float x, float y, float z) +{ + Matrix *m = reinterpret_cast<Matrix *>(mat); + m->translate(x, y, z); +} + + + + +////////////////////////////////////////////////////////////////////////////// +// Context +////////////////////////////////////////////////////////////////////////////// + +static void SC_bindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va) +{ + GET_TLS(); + rsi_ProgramFragmentBindTexture(rsc, + static_cast<ProgramFragment *>(vpf), + slot, + static_cast<Allocation *>(va)); + +} + +static void SC_bindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs) +{ + GET_TLS(); + rsi_ProgramFragmentBindSampler(rsc, + static_cast<ProgramFragment *>(vpf), + slot, + static_cast<Sampler *>(vs)); + +} + +static void SC_bindProgramFragmentStore(RsProgramFragmentStore pfs) +{ + GET_TLS(); + rsi_ContextBindProgramFragmentStore(rsc, pfs); + +} + +static void SC_bindProgramFragment(RsProgramFragment pf) +{ + GET_TLS(); + rsi_ContextBindProgramFragment(rsc, pf); + +} + +static void SC_bindProgramVertex(RsProgramVertex pv) +{ + GET_TLS(); + rsi_ContextBindProgramVertex(rsc, pv); + +} + +////////////////////////////////////////////////////////////////////////////// +// Drawing +////////////////////////////////////////////////////////////////////////////// + +static void SC_drawTriangleMesh(RsTriangleMesh mesh) +{ + GET_TLS(); + rsi_TriangleMeshRender(rsc, mesh); +} + +static void SC_drawTriangleMeshRange(RsTriangleMesh mesh, uint32_t start, uint32_t count) +{ + GET_TLS(); + rsi_TriangleMeshRenderRange(rsc, mesh, start, count); +} + +// Assumes (GL_FIXED) x,y,z (GL_UNSIGNED_BYTE)r,g,b,a +static void SC_drawTriangleArray(int ialloc, uint32_t count) +{ + GET_TLS(); + RsAllocation alloc = (RsAllocation)ialloc; + + const Allocation *a = (const Allocation *)alloc; + const uint32_t *ptr = (const uint32_t *)a->getPtr(); + + rsc->setupCheck(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); + + glEnableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FIXED, 12, ptr + 1); + //glTexCoordPointer(2, GL_FIXED, 24, ptr + 1); + glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); + + glDrawArrays(GL_TRIANGLES, 0, count * 3); +} + +static void SC_drawQuad(float x1, float y1, float z1, + float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) +{ + GET_TLS(); + + //LOGE("Quad"); + //LOGE("%4.2f, %4.2f, %4.2f", x1, y1, z1); + //LOGE("%4.2f, %4.2f, %4.2f", x2, y2, z2); + //LOGE("%4.2f, %4.2f, %4.2f", x3, y3, z3); + //LOGE("%4.2f, %4.2f, %4.2f", x4, y4, z4); + + float vtx[] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4}; + static const float tex[] = {0,1, 1,1, 1,0, 0,0}; + + + rsc->setupCheck(); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tm->mBufferObjects[1]); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, vtx); + + glClientActiveTexture(GL_TEXTURE0); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, tex); + glClientActiveTexture(GL_TEXTURE1); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, tex); + glClientActiveTexture(GL_TEXTURE0); + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + //glColorPointer(4, GL_UNSIGNED_BYTE, 12, ptr); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +} + +////////////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////////////// + +extern "C" const void * loadVp(uint32_t bank, uint32_t offset) +{ + GET_TLS(); + return &static_cast<const uint8_t *>(sc->mSlots[bank]->getPtr())[offset]; +} + + + +static void SC_color(float r, float g, float b, float a) +{ + glColor4f(r, g, b, a); +} + + +extern "C" void materialDiffuse(float r, float g, float b, float a) +{ + float v[] = {r, g, b, a}; + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, v); +} + +extern "C" void materialSpecular(float r, float g, float b, float a) +{ + float v[] = {r, g, b, a}; + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, v); +} + +extern "C" void lightPosition(float x, float y, float z, float w) +{ + float v[] = {x, y, z, w}; + glLightfv(GL_LIGHT0, GL_POSITION, v); +} + +extern "C" void materialShininess(float s) +{ + glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &s); +} + +extern "C" void uploadToTexture(RsAllocation va, uint32_t baseMipLevel) +{ + GET_TLS(); + rsi_AllocationUploadToTexture(rsc, va, baseMipLevel); +} + +extern "C" void enable(uint32_t p) +{ + glEnable(p); +} + +extern "C" void disable(uint32_t p) +{ + glDisable(p); +} + + + +static void SC_ClearColor(float r, float g, float b, float a) +{ + //LOGE("c %f %f %f %f", r, g, b, a); + GET_TLS(); + sc->mEnviroment.mClearColor[0] = r; + sc->mEnviroment.mClearColor[1] = g; + sc->mEnviroment.mClearColor[2] = b; + sc->mEnviroment.mClearColor[3] = a; +} + + + +////////////////////////////////////////////////////////////////////////////// +// Class implementation +////////////////////////////////////////////////////////////////////////////// + +ScriptCState::SymbolTable_t ScriptCState::gSyms[] = { + // IO + { "loadI32", (void *)&SC_loadI32, + "int", "(int, int)" }, + //{ "loadU32", (void *)&SC_loadU32, "unsigned int", "(int, int)" }, + { "loadF", (void *)&SC_loadF, + "float", "(int, int)" }, + { "loadVec4", (void *)&SC_loadVec4, + "void", "(int, int, float *)" }, + { "loadMatrix", (void *)&SC_loadMatrix, + "void", "(int, int, float *)" }, + { "storeI32", (void *)&SC_storeI32, + "void", "(int, int, int)" }, + //{ "storeU32", (void *)&SC_storeU32, "void", "(int, int, unsigned int)" }, + { "storeF", (void *)&SC_storeF, + "void", "(int, int, float)" }, + { "storeVec4", (void *)&SC_storeVec4, + "void", "(int, int, float *)" }, + { "storeMatrix", (void *)&SC_storeMatrix, + "void", "(int, int, float *)" }, + + // math + { "sinf", (void *)&sinf, + "float", "(float)" }, + { "cosf", (void *)&cosf, + "float", "(float)" }, + { "fabs", (void *)&fabs, + "float", "(float)" }, + { "randf", (void *)&SC_randf, + "float", "(float)" }, + + // matrix + { "matrixLoadIdentity", (void *)&SC_matrixLoadIdentity, + "void", "(float *mat)" }, + { "matrixLoadFloat", (void *)&SC_matrixLoadFloat, + "void", "(float *mat, float *f)" }, + { "matrixLoadMat", (void *)&SC_matrixLoadMat, + "void", "(float *mat, float *newmat)" }, + { "matrixLoadRotate", (void *)&SC_matrixLoadRotate, + "void", "(float *mat, float rot, float x, float y, float z)" }, + { "matrixLoadScale", (void *)&SC_matrixLoadScale, + "void", "(float *mat, float x, float y, float z)" }, + { "matrixLoadTranslate", (void *)&SC_matrixLoadTranslate, + "void", "(float *mat, float x, float y, float z)" }, + { "matrixLoadMultiply", (void *)&SC_matrixLoadMultiply, + "void", "(float *mat, float *lhs, float *rhs)" }, + { "matrixMultiply", (void *)&SC_matrixMultiply, + "void", "(float *mat, float *rhs)" }, + { "matrixRotate", (void *)&SC_matrixRotate, + "void", "(float *mat, float rot, float x, float y, float z)" }, + { "matrixScale", (void *)&SC_matrixScale, + "void", "(float *mat, float x, float y, float z)" }, + { "matrixTranslate", (void *)&SC_matrixTranslate, + "void", "(float *mat, float x, float y, float z)" }, + + // context + { "bindProgramFragment", (void *)&SC_bindProgramFragment, + "void", "(int)" }, + { "bindProgramFragmentStore", (void *)&SC_bindProgramFragmentStore, + "void", "(int)" }, + { "bindProgramVertex", (void *)&SC_bindProgramVertex, + "void", "(int)" }, + { "bindSampler", (void *)&SC_bindSampler, + "void", "(int, int, int)" }, + { "bindTexture", (void *)&SC_bindTexture, + "void", "(int, int, int)" }, + + // drawing + { "drawQuad", (void *)&SC_drawQuad, + "void", "(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4)" }, + { "drawTriangleArray", (void *)&SC_drawTriangleArray, + "void", "(int ialloc, int count)" }, + { "drawTriangleMesh", (void *)&SC_drawTriangleMesh, + "void", "(int mesh)" }, + { "drawTriangleMeshRange", (void *)&SC_drawTriangleMeshRange, + "void", "(int mesh, int start, int count)" }, + + + // misc + { "pfClearColor", (void *)&SC_ClearColor, + "void", "(float, float, float, float)" }, + + { "color", (void *)&SC_color, + "void", "(float, float, float, float)" }, + + { NULL, NULL, NULL, NULL } +}; + +const ScriptCState::SymbolTable_t * ScriptCState::lookupSymbol(const char *sym) +{ + ScriptCState::SymbolTable_t *syms = gSyms; + + while (syms->mPtr) { + if (!strcmp(syms->mName, sym)) { + return syms; + } + syms++; + } + return NULL; +} + +void ScriptCState::appendDecls(String8 *str) +{ + ScriptCState::SymbolTable_t *syms = gSyms; + while (syms->mPtr) { + str->append(syms->mRet); + str->append(" "); + str->append(syms->mName); + str->append(syms->mParam); + str->append(";\n"); + syms++; + } +} + + diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h index 3d3437b298ca..b13e88fdbe91 100644 --- a/libs/rs/rsUtils.h +++ b/libs/rs/rsUtils.h @@ -24,6 +24,9 @@ #include <stdlib.h> #include <pthread.h> +#include <EGL/egl.h> +#include <math.h> + #include "RenderScript.h" namespace android { diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 13c30a795cb2..59c9476287f0 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -22,7 +22,6 @@ #include <utils/Log.h> #include <utils/Timers.h> #include <utils/threads.h> -#include <utils/List.h> #include <utils/Errors.h> #include <stdlib.h> @@ -84,7 +83,7 @@ EventHub::EventHub(void) : mError(NO_INIT), mHaveFirstKeyboard(false), mFirstKeyboardId(0) , mDevicesById(0), mNumDevicesById(0) , mOpeningDevices(0), mClosingDevices(0) - , mDevices(0), mFDs(0), mFDCount(0) + , mDevices(0), mFDs(0), mFDCount(0), mOpened(false) { acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); #ifdef EV_SW @@ -101,11 +100,6 @@ EventHub::~EventHub(void) // we should free stuff here... } -void EventHub::onFirstRef() -{ - mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR; -} - status_t EventHub::errorCheck() const { return mError; @@ -240,6 +234,41 @@ int EventHub::getKeycodeState(int32_t deviceId, int code) const return 0; } +status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const +{ + AutoMutex _l(mLock); + device_t* device = getDevice(deviceId); + + if (device != NULL && device->layoutMap != NULL) { + status_t err = device->layoutMap->map(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + + if (mHaveFirstKeyboard) { + device = getDevice(mFirstKeyboardId); + + if (device != NULL && device->layoutMap != NULL) { + status_t err = device->layoutMap->map(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + } + + *outKeycode = 0; + *outFlags = 0; + return NAME_NOT_FOUND; +} + +void EventHub::addExcludedDevice(const char* deviceName) +{ + String8 name(deviceName); + mExcludedDevices.push_back(name); +} + EventHub::device_t* EventHub::getDevice(int32_t deviceId) const { if (deviceId == 0) deviceId = mFirstKeyboardId; @@ -277,7 +306,12 @@ bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, // Note that we only allow one caller to getEvent(), so don't need // to do locking here... only when adding/removing devices. - + + if (!mOpened) { + mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR; + mOpened = true; + } + while(1) { // First, report any devices that had last been added/removed. @@ -475,6 +509,20 @@ int EventHub::open_device(const char *deviceName) //fprintf(stderr, "could not get device name for %s, %s\n", deviceName, strerror(errno)); name[0] = '\0'; } + + // check to see if the device is on our excluded list + List<String8>::iterator iter = mExcludedDevices.begin(); + List<String8>::iterator end = mExcludedDevices.end(); + for ( ; iter != end; iter++) { + const char* test = *iter; + if (strcmp(name, test) == 0) { + LOGI("ignoring event id %s driver %s\n", deviceName, test); + close(fd); + fd = -1; + return -1; + } + } + if(ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) { //fprintf(stderr, "could not get location for %s, %s\n", deviceName, strerror(errno)); location[0] = '\0'; @@ -734,6 +782,7 @@ int EventHub::read_notify(int nfd) int event_pos = 0; struct inotify_event *event; +LOGD("EventHub::read_notify nfd: %d\n", nfd); res = read(nfd, event_buf, sizeof(event_buf)); if(res < (int)sizeof(*event)) { if(errno == EINTR) diff --git a/libs/ui/Overlay.cpp b/libs/ui/Overlay.cpp index a092f8dd54e0..4854d6a2a26f 100644 --- a/libs/ui/Overlay.cpp +++ b/libs/ui/Overlay.cpp @@ -59,6 +59,18 @@ status_t Overlay::queueBuffer(overlay_buffer_t buffer) return mOverlayData->queueBuffer(mOverlayData, buffer); } +status_t Overlay::setCrop(uint32_t x, uint32_t y, uint32_t w, uint32_t h) +{ + if (mStatus != NO_ERROR) return mStatus; + return mOverlayData->setCrop(mOverlayData, x, y, w, h); +} + +status_t Overlay::getCrop(uint32_t* x, uint32_t* y, uint32_t* w, uint32_t* h) +{ + if (mStatus != NO_ERROR) return mStatus; + return mOverlayData->getCrop(mOverlayData, x, y, w, h); +} + int32_t Overlay::getBufferCount() const { if (mStatus != NO_ERROR) return mStatus; @@ -73,6 +85,15 @@ void* Overlay::getBufferAddress(overlay_buffer_t buffer) void Overlay::destroy() { if (mStatus != NO_ERROR) return; + + // Must delete the objects in reverse creation order, thus the + // data side must be closed first and then the destroy send to + // the control side. + if (mOverlayData) { + overlay_data_close(mOverlayData); + mOverlayData = NULL; + } + mOverlayRef->mOverlayChannel->destroy(); } diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp index cce754a08213..be04777528ea 100644 --- a/libs/utils/BackupData.cpp +++ b/libs/utils/BackupData.cpp @@ -298,10 +298,12 @@ BackupDataReader::SkipEntityData() } if (m_header.entity.dataSize > 0) { int pos = lseek(m_fd, m_dataEndPos, SEEK_SET); - return pos == -1 ? (int)errno : (int)NO_ERROR; - } else { - return NO_ERROR; + if (pos == -1) { + return errno; + } } + SKIP_PADDING(); + return NO_ERROR; } ssize_t diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 87edb0125e73..98d450b76edc 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -4007,7 +4007,16 @@ void ResTable::print(bool inclValues) const printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - printf(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%d key=%d infl=%d nav=%d w=%d h=%d lyt=%d\n", + char density[16]; + uint16_t dval = dtohs(type->config.density); + if (dval == ResTable_config::DENSITY_DEFAULT) { + strcpy(density, "def"); + } else if (dval == ResTable_config::DENSITY_NONE) { + strcpy(density, "non"); + } else { + sprintf(density, "%d", (int)dval); + } + printf(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%s key=%d infl=%d nav=%d w=%d h=%d lyt=%d\n", (int)configIndex, type->config.language[0] ? type->config.language[0] : '-', type->config.language[1] ? type->config.language[1] : '-', @@ -4015,7 +4024,7 @@ void ResTable::print(bool inclValues) const type->config.country[1] ? type->config.country[1] : '-', type->config.orientation, type->config.touchscreen, - dtohs(type->config.density), + density, type->config.keyboard, type->config.inputFlags, type->config.navigation, diff --git a/libs/utils/ZipUtils.cpp b/libs/utils/ZipUtils.cpp index 5df94cbbd9a0..9138878ff776 100644 --- a/libs/utils/ZipUtils.cpp +++ b/libs/utils/ZipUtils.cpp @@ -210,7 +210,7 @@ bail: LOGV("+++ reading %ld bytes (%ld left)\n", getSize, compRemaining); - int cc = fread(readBuf, getSize, 1, fp); + int cc = fread(readBuf, 1, getSize, fp); if (cc != (int) getSize) { LOGD("inflate read failed (%d vs %ld)\n", cc, getSize); @@ -341,4 +341,3 @@ bail: return true; } - diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index 3f0c2347d4d3..9e1a72c1a8af 100755 --- a/location/java/com/android/internal/location/GpsLocationProvider.java +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -184,8 +184,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { // number of fixes we have received since we started navigating private int mFixCount; - private boolean mAgpsConfigured; - // true if we started navigation private boolean mStarted; @@ -356,7 +354,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { try { int port = Integer.parseInt(portString); native_set_agps_server(AGPS_TYPE_SUPL, host, port); - mAgpsConfigured = true; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse SUPL_PORT: " + portString); } @@ -368,7 +365,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { try { int port = Integer.parseInt(portString); native_set_agps_server(AGPS_TYPE_C2K, host, port); - mAgpsConfigured = true; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse C2K_PORT: " + portString); } @@ -722,7 +718,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { if (DEBUG) Log.d(TAG, "startNavigating"); mStarted = true; int positionMode; - if (mAgpsConfigured && Settings.Secure.getInt(mContext.getContentResolver(), + if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 0) != 0) { positionMode = GPS_POSITION_MODE_MS_BASED; } else { diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index ee41021d0a05..58c04f3a1f74 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1327,10 +1327,12 @@ public class AudioService extends IAudioService.Stub { } private void persistVolume(VolumeStreamState streamState) { - System.putInt(mContentResolver, streamState.mVolumeIndexSettingName, - streamState.mIndex); - System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName, - streamState.mLastAudibleIndex); + if (streamState.mStreamType != AudioManager.STREAM_BLUETOOTH_SCO) { + System.putInt(mContentResolver, streamState.mVolumeIndexSettingName, + streamState.mIndex); + System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName, + streamState.mLastAudibleIndex); + } } private void persistRingerMode() { diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 84c1a92b51ca..d5801f7b3cdf 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -663,7 +663,7 @@ public class MediaScanner ContentValues values = toValues(); String title = values.getAsString(MediaStore.MediaColumns.TITLE); - if (TextUtils.isEmpty(title)) { + if (title == null || TextUtils.isEmpty(title.trim())) { title = values.getAsString(MediaStore.MediaColumns.DATA); // extract file name after last slash int lastSlash = title.lastIndexOf('/'); diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java index 6525109945ff..70e89a2c82a9 100644 --- a/media/java/android/media/Metadata.java +++ b/media/java/android/media/Metadata.java @@ -20,10 +20,12 @@ import android.graphics.Bitmap; import android.os.Parcel; import android.util.Log; +import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.Iterator; +import java.util.HashMap; import java.util.Set; +import java.util.TimeZone; /** @@ -54,7 +56,7 @@ public class Metadata // // We manually serialize the data in Parcels. For large memory // blob (bitmaps, raw pictures) we use MemoryFile which allow the - // client to make the data purgeable once it is done with it. + // client to make the data purge-able once it is done with it. // public static final int ANY = 0; // Never used for metadata returned, only for filtering. @@ -95,110 +97,322 @@ public class Metadata public static final int VIDEO_WIDTH = 26; // Integer public static final int NUM_TRACKS = 27; // Integer public static final int DRM_CRIPPLED = 28; // Boolean - public static final int LAST_SYSTEM = 29; - public static final int FIRST_CUSTOM = 8092; + + // Playback capabilities. + public static final int PAUSE_AVAILABLE = 29; // Boolean + public static final int SEEK_BACKWARD_AVAILABLE = 30; // Boolean + public static final int SEEK_FORWARD_AVAILABLE = 31; // Boolean + + private static final int LAST_SYSTEM = 31; + private static final int FIRST_CUSTOM = 8092; // Shorthands to set the MediaPlayer's metadata filter. public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET; public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY); - private static final int STRING_VAL = 1; - private static final int INTEGER_VAL = 2; - private static final int LONG_VAL = 3; - private static final int DOUBLE_VAL = 4; - private static final int TIMED_TEXT_VAL = 2; + public static final int STRING_VAL = 1; + public static final int INTEGER_VAL = 2; + public static final int BOOLEAN_VAL = 3; + public static final int LONG_VAL = 4; + public static final int DOUBLE_VAL = 5; + public static final int TIMED_TEXT_VAL = 6; + public static final int DATE_VAL = 7; + public static final int BYTE_ARRAY_VAL = 8; + // FIXME: misses a type for shared heap is missing (MemoryFile). + // FIXME: misses a type for bitmaps. + private static final int LAST_TYPE = 8; + + private static final String TAG = "media.Metadata"; + private static final int kInt32Size = 4; + private static final int kMetaHeaderSize = 2 * kInt32Size; // size + marker + private static final int kRecordHeaderSize = 3 * kInt32Size; // size + id + type + + private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' + + // After a successful parsing, set the parcel with the serialized metadata. + private Parcel mParcel; + + // Map to associate a Metadata key (e.g TITLE) with the offset of + // the record's payload in the parcel. + // Used to look up if a key was present too. + // Key: Metadata ID + // Value: Offset of the metadata type field in the record. + private final HashMap<Integer, Integer> mKeyToPosMap = + new HashMap<Integer, Integer>(); /** - * Helper class to hold a pair (time, text). Can be used to implement caption. + * Helper class to hold a triple (time, duration, text). Can be used to + * implement caption. */ public class TimedText { private Date mTime; + private int mDuration; // millisec private String mText; - public TimedText(final Date time, final String text) { + + public TimedText(Date time, int duration, String text) { mTime = time; + mDuration = duration; mText = text; } + public String toString() { StringBuilder res = new StringBuilder(80); - res.append(mTime).append(":").append(mText); + res.append(mTime).append("-").append(mDuration) + .append(":").append(mText); return res.toString(); } } - /* package */ Metadata() {} + public Metadata() { } - /* package */ boolean parse(Parcel data) { - // FIXME: Implement. - return true; + /** + * Go over all the records, collecting metadata keys and records' + * type field offset in the Parcel. These are stored in + * mKeyToPosMap for latter retrieval. + * Format of a metadata record: + <pre> + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | record size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | metadata key | // TITLE + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | metadata type | // STRING_VAL + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | .... metadata payload .... | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + </pre> + * @param parcel With the serialized records. + * @param bytesLeft How many bytes in the parcel should be processed. + * @return false if an error occurred during parsing. + */ + private boolean scanAllRecords(Parcel parcel, int bytesLeft) { + int recCount = 0; + boolean error = false; + + mKeyToPosMap.clear(); + while (bytesLeft > kRecordHeaderSize) { + final int start = parcel.dataPosition(); + // Check the size. + final int size = parcel.readInt(); + + if (size <= kRecordHeaderSize) { // at least 1 byte should be present. + Log.e(TAG, "Record is too short"); + error = true; + break; + } + + // Check the metadata key. + final int metadataId = parcel.readInt(); + if (!checkMetadataId(metadataId)) { + error = true; + break; + } + + // Store the record offset which points to the type + // field so we can later on read/unmarshall the record + // payload. + if (mKeyToPosMap.containsKey(metadataId)) { + Log.e(TAG, "Duplicate metadata ID found"); + error = true; + break; + } + + mKeyToPosMap.put(metadataId, parcel.dataPosition()); + + // Check the metadata type. + final int metadataType = parcel.readInt(); + if (metadataType <= 0 || metadataType > LAST_TYPE) { + Log.e(TAG, "Invalid metadata type " + metadataType); + error = true; + break; + } + + // Skip to the next one. + parcel.setDataPosition(start + size); + bytesLeft -= size; + ++recCount; + } + + if (0 != bytesLeft || error) { + Log.e(TAG, "Ran out of data or error on record " + recCount); + mKeyToPosMap.clear(); + return false; + } else { + return true; + } } /** - * @return the number of element in this metadata set. + * Check a parcel containing metadata is well formed. The header + * is checked as well as the individual records format. However, the + * data inside the record is not checked because we do lazy access + * (we check/unmarshall only data the user asks for.) + * + * Format of a metadata parcel: + <pre> + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | metadata total size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 'M' | 'E' | 'T' | 'A' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | .... metadata records .... | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + </pre> + * + * @param parcel With the serialized data. Metadata keeps a + * reference on it to access it later on. The caller + * should not modify the parcel after this call (and + * not call recycle on it.) + * @return false if an error occurred. */ - public int size() { - // FIXME: Implement. - return 0; + public boolean parse(Parcel parcel) { + if (parcel.dataAvail() < kMetaHeaderSize) { + Log.e(TAG, "Not enough data " + parcel.dataAvail()); + return false; + } + + final int pin = parcel.dataPosition(); // to roll back in case of errors. + final int size = parcel.readInt(); + + // The extra kInt32Size below is to account for the int32 'size' just read. + if (parcel.dataAvail() + kInt32Size < size || size < kMetaHeaderSize) { + Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin); + parcel.setDataPosition(pin); + return false; + } + + // Checks if the 'M' 'E' 'T' 'A' marker is present. + final int kShouldBeMetaMarker = parcel.readInt(); + if (kShouldBeMetaMarker != kMetaMarker ) { + Log.e(TAG, "Marker missing " + Integer.toHexString(kShouldBeMetaMarker)); + parcel.setDataPosition(pin); + return false; + } + + // Scan the records to collect metadata ids and offsets. + if (!scanAllRecords(parcel, size - kMetaHeaderSize)) { + parcel.setDataPosition(pin); + return false; + } + mParcel = parcel; + return true; } /** - * @return an iterator over the keys. + * @return The set of metadata ID found. */ - public Iterator<Integer> iterator() { - // FIXME: Implement. - return new java.util.HashSet<Integer>().iterator(); + public Set<Integer> keySet() { + return mKeyToPosMap.keySet(); } /** * @return true if a value is present for the given key. */ - public boolean has(final int key) { - if (key <= ANY) { - throw new IllegalArgumentException("Invalid key: " + key); - } - if (LAST_SYSTEM <= key && key < FIRST_CUSTOM) { - throw new IllegalArgumentException("Key in reserved range: " + key); + public boolean has(final int metadataId) { + if (!checkMetadataId(metadataId)) { + throw new IllegalArgumentException("Invalid key: " + metadataId); } - // FIXME: Implement. - return true; + return mKeyToPosMap.containsKey(metadataId); } - // Accessors + // Accessors. + // Caller must make sure the key is present using the {@code has} + // method otherwise a RuntimeException will occur. + public String getString(final int key) { - // FIXME: Implement. - return new String(); + checkType(key, STRING_VAL); + return mParcel.readString(); } public int getInt(final int key) { - // FIXME: Implement. - return 0; + checkType(key, INTEGER_VAL); + return mParcel.readInt(); + } + + public boolean getBoolean(final int key) { + checkType(key, BOOLEAN_VAL); + return mParcel.readInt() == 1; } public long getLong(final int key) { - // FIXME: Implement. - return 0; + checkType(key, LONG_VAL); + return mParcel.readLong(); } public double getDouble(final int key) { - // FIXME: Implement. - return 0.0; + checkType(key, DOUBLE_VAL); + return mParcel.readDouble(); } public byte[] getByteArray(final int key) { - return new byte[0]; - } - - public Bitmap getBitmap(final int key) { - // FIXME: Implement. - return null; + checkType(key, BYTE_ARRAY_VAL); + return mParcel.createByteArray(); } public Date getDate(final int key) { - // FIXME: Implement. - return new Date(); + checkType(key, DATE_VAL); + final long timeSinceEpoch = mParcel.readLong(); + final String timeZone = mParcel.readString(); + + if (timeZone.length() == 0) { + return new Date(timeSinceEpoch); + } else { + TimeZone tz = TimeZone.getTimeZone(timeZone); + Calendar cal = Calendar.getInstance(tz); + + cal.setTimeInMillis(timeSinceEpoch); + return cal.getTime(); + } } public TimedText getTimedText(final int key) { - // FIXME: Implement. - return new TimedText(new Date(0), "<missing>"); + checkType(key, TIMED_TEXT_VAL); + final Date startTime = new Date(mParcel.readLong()); // epoch + final int duration = mParcel.readInt(); // millisec + + return new TimedText(startTime, + duration, + mParcel.readString()); + } + + // @return the last available system metadata id. Ids are + // 1-indexed. + public static int lastSytemId() { return LAST_SYSTEM; } + + // @return the first available cutom metadata id. + public static int firstCustomId() { return FIRST_CUSTOM; } + + // @return the last value of known type. Types are 1-indexed. + public static int lastType() { return LAST_TYPE; } + + // Check val is either a system id or a custom one. + // @param val Metadata key to test. + // @return true if it is in a valid range. + private boolean checkMetadataId(final int val) { + if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) { + Log.e(TAG, "Invalid metadata ID " + val); + return false; + } + return true; + } + + // Check the type of the data match what is expected. + private void checkType(final int key, final int expectedType) { + final int pos = mKeyToPosMap.get(key); + + mParcel.setDataPosition(pos); + + final int type = mParcel.readInt(); + if (type != expectedType) { + throw new IllegalStateException("Wrong type " + expectedType + " but got " + type); + } } } diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk index ebbf13fa3bc2..cdaab045b762 100644 --- a/media/libmedia/Android.mk +++ b/media/libmedia/Android.mk @@ -18,7 +18,8 @@ LOCAL_SRC_FILES:= \ IMediaMetadataRetriever.cpp \ mediametadataretriever.cpp \ ToneGenerator.cpp \ - JetPlayer.cpp + JetPlayer.cpp \ + IOMX.cpp LOCAL_SHARED_LIBRARIES := \ libui libcutils libutils libbinder libsonivox @@ -34,6 +35,7 @@ LOCAL_SHARED_LIBRARIES += libdl endif LOCAL_C_INCLUDES := \ - $(call include-path-for, graphics corecg) + $(call include-path-for, graphics corecg) \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include include $(BUILD_SHARED_LIBRARY) diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index af7dae5f502f..7b9eda76d820 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -891,7 +891,7 @@ void AudioTrack::AudioTrackThread::onFirstRef() // ========================================================================= audio_track_cblk_t::audio_track_cblk_t() - : user(0), server(0), userBase(0), serverBase(0), buffers(0), frameCount(0), + : lock(Mutex::SHARED), user(0), server(0), userBase(0), serverBase(0), buffers(0), frameCount(0), loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), volumeLR(0), flowControlFlag(1), forceReady(0) { } diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp index 0f64259012f0..8d2c36074ab7 100644 --- a/media/libmedia/IMediaPlayerService.cpp +++ b/media/libmedia/IMediaPlayerService.cpp @@ -17,12 +17,14 @@ #include <stdint.h> #include <sys/types.h> -#include <binder/Parcel.h> +#include <binder/Parcel.h> #include <binder/IMemory.h> -#include <utils/Errors.h> // for status_t #include <media/IMediaPlayerService.h> #include <media/IMediaRecorder.h> +#include <media/IOMX.h> + +#include <utils/Errors.h> // for status_t namespace android { @@ -33,6 +35,7 @@ enum { DECODE_FD, CREATE_MEDIA_RECORDER, CREATE_METADATA_RETRIEVER, + CREATE_OMX, }; class BpMediaPlayerService: public BpInterface<IMediaPlayerService> @@ -110,6 +113,13 @@ public: *pFormat = reply.readInt32(); return interface_cast<IMemory>(reply.readStrongBinder()); } + + virtual sp<IOMX> createOMX() { + Parcel data, reply; + data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); + remote()->transact(CREATE_OMX, data, &reply); + return interface_cast<IOMX>(reply.readStrongBinder()); + } }; IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.media.IMediaPlayerService"); @@ -182,6 +192,12 @@ status_t BnMediaPlayerService::onTransact( reply->writeStrongBinder(retriever->asBinder()); return NO_ERROR; } break; + case CREATE_OMX: { + CHECK_INTERFACE(IMediaPlayerService, data, reply); + sp<IOMX> omx = createOMX(); + reply->writeStrongBinder(omx->asBinder()); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp new file mode 100644 index 000000000000..f2a657aa0357 --- /dev/null +++ b/media/libmedia/IOMX.cpp @@ -0,0 +1,561 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "IOMX" +#include <utils/Log.h> + +#include <binder/IMemory.h> +#include <binder/Parcel.h> +#include <media/IOMX.h> + +namespace android { + +enum { + CONNECT = IBinder::FIRST_CALL_TRANSACTION, + LIST_NODES, + ALLOCATE_NODE, + FREE_NODE, + SEND_COMMAND, + GET_PARAMETER, + SET_PARAMETER, + USE_BUFFER, + ALLOC_BUFFER, + ALLOC_BUFFER_WITH_BACKUP, + FREE_BUFFER, + OBSERVE_NODE, + FILL_BUFFER, + EMPTY_BUFFER, + OBSERVER_ON_MSG, +}; + +static void *readVoidStar(const Parcel *parcel) { + // FIX if sizeof(void *) != sizeof(int32) + return (void *)parcel->readInt32(); +} + +static void writeVoidStar(void *x, Parcel *parcel) { + // FIX if sizeof(void *) != sizeof(int32) + parcel->writeInt32((int32_t)x); +} + +class BpOMX : public BpInterface<IOMX> { +public: + BpOMX(const sp<IBinder> &impl) + : BpInterface<IOMX>(impl) { + } + +#if IOMX_USES_SOCKETS + virtual status_t connect(int *sd) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + remote()->transact(CONNECT, data, &reply); + + status_t err = reply.readInt32(); + if (err == OK) { + *sd = dup(reply.readFileDescriptor()); + } else { + *sd = -1; + } + + return reply.readInt32(); + } +#endif + + virtual status_t list_nodes(List<String8> *list) { + list->clear(); + + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + remote()->transact(LIST_NODES, data, &reply); + + int32_t n = reply.readInt32(); + for (int32_t i = 0; i < n; ++i) { + String8 s = reply.readString8(); + + list->push_back(s); + } + + return OK; + } + + virtual status_t allocate_node(const char *name, node_id *node) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + data.writeCString(name); + remote()->transact(ALLOCATE_NODE, data, &reply); + + status_t err = reply.readInt32(); + if (err == OK) { + *node = readVoidStar(&reply); + } else { + *node = 0; + } + + return err; + } + + virtual status_t free_node(node_id node) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + remote()->transact(FREE_NODE, data, &reply); + + return reply.readInt32(); + } + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(cmd); + data.writeInt32(param); + remote()->transact(SEND_COMMAND, data, &reply); + + return reply.readInt32(); + } + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(index); + data.writeInt32(size); + data.write(params, size); + remote()->transact(GET_PARAMETER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + return err; + } + + reply.read(params, size); + + return OK; + } + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(index); + data.writeInt32(size); + data.write(params, size); + remote()->transact(SET_PARAMETER, data, &reply); + + return reply.readInt32(); + } + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeStrongBinder(params->asBinder()); + remote()->transact(USE_BUFFER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeInt32(size); + remote()->transact(ALLOC_BUFFER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeStrongBinder(params->asBinder()); + remote()->transact(ALLOC_BUFFER_WITH_BACKUP, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + writeVoidStar(buffer, &data); + remote()->transact(FREE_BUFFER, data, &reply); + + return reply.readInt32(); + } + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeStrongBinder(observer->asBinder()); + remote()->transact(OBSERVE_NODE, data, &reply); + + return reply.readInt32(); + } + + virtual void fill_buffer(node_id node, buffer_id buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + writeVoidStar(buffer, &data); + remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + } + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + writeVoidStar(buffer, &data); + data.writeInt32(range_offset); + data.writeInt32(range_length); + data.writeInt32(flags); + data.writeInt64(timestamp); + remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + } +#endif +}; + +IMPLEMENT_META_INTERFACE(OMX, "android.hardware.IOMX"); + +//////////////////////////////////////////////////////////////////////////////// + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnOMX::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { +#if IOMX_USES_SOCKETS + case CONNECT: + { + CHECK_INTERFACE(IOMX, data, reply); + + int s; + status_t err = connect(&s); + + reply->writeInt32(err); + if (err == OK) { + assert(s >= 0); + reply->writeDupFileDescriptor(s); + close(s); + s = -1; + } else { + assert(s == -1); + } + + return NO_ERROR; + } +#endif + + case LIST_NODES: + { + CHECK_INTERFACE(IOMX, data, reply); + + List<String8> list; + list_nodes(&list); + + reply->writeInt32(list.size()); + for (List<String8>::iterator it = list.begin(); + it != list.end(); ++it) { + reply->writeString8(*it); + } + + return NO_ERROR; + } + + case ALLOCATE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node; + status_t err = allocate_node(data.readCString(), &node); + reply->writeInt32(err); + if (err == OK) { + writeVoidStar(node, reply); + } + + return NO_ERROR; + } + + case FREE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + + reply->writeInt32(free_node(node)); + + return NO_ERROR; + } + + case SEND_COMMAND: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + + OMX_COMMANDTYPE cmd = + static_cast<OMX_COMMANDTYPE>(data.readInt32()); + + OMX_S32 param = data.readInt32(); + reply->writeInt32(send_command(node, cmd, param)); + + return NO_ERROR; + } + + case GET_PARAMETER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32()); + + size_t size = data.readInt32(); + + // XXX I am not happy with this but Parcel::readInplace didn't work. + void *params = malloc(size); + data.read(params, size); + + status_t err = get_parameter(node, index, params, size); + + reply->writeInt32(err); + + if (err == OK) { + reply->write(params, size); + } + + free(params); + params = NULL; + + return NO_ERROR; + } + + case SET_PARAMETER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32()); + + size_t size = data.readInt32(); + void *params = const_cast<void *>(data.readInplace(size)); + + reply->writeInt32(set_parameter(node, index, params, size)); + + return NO_ERROR; + } + + case USE_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + sp<IMemory> params = + interface_cast<IMemory>(data.readStrongBinder()); + + buffer_id buffer; + status_t err = use_buffer(node, port_index, params, &buffer); + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case ALLOC_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + size_t size = data.readInt32(); + + buffer_id buffer; + status_t err = allocate_buffer(node, port_index, size, &buffer); + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case ALLOC_BUFFER_WITH_BACKUP: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + sp<IMemory> params = + interface_cast<IMemory>(data.readStrongBinder()); + + buffer_id buffer; + status_t err = allocate_buffer_with_backup( + node, port_index, params, &buffer); + + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case FREE_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + buffer_id buffer = readVoidStar(&data); + reply->writeInt32(free_buffer(node, port_index, buffer)); + + return NO_ERROR; + } + +#if !IOMX_USES_SOCKETS + case OBSERVE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + sp<IOMXObserver> observer = + interface_cast<IOMXObserver>(data.readStrongBinder()); + reply->writeInt32(observe_node(node, observer)); + + return NO_ERROR; + } + + case FILL_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + buffer_id buffer = readVoidStar(&data); + fill_buffer(node, buffer); + + return NO_ERROR; + } + + case EMPTY_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + buffer_id buffer = readVoidStar(&data); + OMX_U32 range_offset = data.readInt32(); + OMX_U32 range_length = data.readInt32(); + OMX_U32 flags = data.readInt32(); + OMX_TICKS timestamp = data.readInt64(); + + empty_buffer( + node, buffer, range_offset, range_length, + flags, timestamp); + + return NO_ERROR; + } +#endif + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +class BpOMXObserver : public BpInterface<IOMXObserver> { +public: + BpOMXObserver(const sp<IBinder> &impl) + : BpInterface<IOMXObserver>(impl) { + } + + virtual void on_message(const omx_message &msg) { + Parcel data, reply; + data.writeInterfaceToken(IOMXObserver::getInterfaceDescriptor()); + data.write(&msg, sizeof(msg)); + + remote()->transact(OBSERVER_ON_MSG, data, &reply, IBinder::FLAG_ONEWAY); + } +}; + +IMPLEMENT_META_INTERFACE(OMXObserver, "android.hardware.IOMXObserver"); + +status_t BnOMXObserver::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { + case OBSERVER_ON_MSG: + { + CHECK_INTERFACE(IOMXObserver, data, reply); + + omx_message msg; + data.read(&msg, sizeof(msg)); + + // XXX Could use readInplace maybe? + on_message(msg); + + return NO_ERROR; + } + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +} // namespace android diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index 0877142e65f6..f74ef3a30372 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -10,6 +10,7 @@ LOCAL_SRC_FILES:= \ MediaRecorderClient.cpp \ MediaPlayerService.cpp \ MetadataRetrieverClient.cpp \ + TestPlayerStub.cpp \ VorbisPlayer.cpp \ MidiFile.cpp @@ -28,10 +29,27 @@ LOCAL_SHARED_LIBRARIES := \ libmedia \ libandroid_runtime +ifneq ($(TARGET_SIMULATOR),true) +LOCAL_SHARED_LIBRARIES += libdl +endif + LOCAL_C_INCLUDES := external/tremor/Tremor \ - $(call include-path-for, graphics corecg) + $(call include-path-for, graphics corecg) \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include LOCAL_MODULE:= libmediaplayerservice +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + LOCAL_SRC_FILES += StagefrightPlayer.cpp + + LOCAL_SHARED_LIBRARIES += \ + libstagefright \ + libstagefright_omx + + LOCAL_C_INCLUDES += $(TOP)/frameworks/base/media/libstagefright/omx + + LOCAL_CFLAGS += -DBUILD_WITH_STAGEFRIGHT -DUSE_STAGEFRIGHT +endif + include $(BUILD_SHARED_LIBRARY) diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 493dc13a2959..5e62f9d8f8e5 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -29,7 +29,7 @@ #include <string.h> #include <cutils/atomic.h> -#include <cutils/properties.h> +#include <cutils/properties.h> // for property_get #include <utils/misc.h> @@ -58,6 +58,19 @@ #include "MidiFile.h" #include "VorbisPlayer.h" #include <media/PVPlayer.h> +#include "TestPlayerStub.h" + +#if USE_STAGEFRIGHT +#include "StagefrightPlayer.h" +#endif + +#ifdef BUILD_WITH_STAGEFRIGHT +#include <OMX.h> +#else +#include <media/IOMX.h> +#endif + + /* desktop Linux needs a little help with gettid() */ #if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS) @@ -86,6 +99,8 @@ const int kMaxFilterSize = 64; // I pulled that out of thin air. // Keep in sync with ANY in Metadata.java const int32_t kAny = 0; +const int32_t kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' + // Unmarshall a filter from a Parcel. // Filter format in a parcel: @@ -186,6 +201,10 @@ typedef struct { const player_type playertype; } extmap; extmap FILE_EXTS [] = { +#if USE_STAGEFRIGHT + {".mp4", STAGEFRIGHT_PLAYER}, + {".3gp", STAGEFRIGHT_PLAYER}, +#endif {".mid", SONIVOX_PLAYER}, {".midi", SONIVOX_PLAYER}, {".smf", SONIVOX_PLAYER}, @@ -271,6 +290,14 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie return c; } +sp<IOMX> MediaPlayerService::createOMX() { +#ifdef BUILD_WITH_STAGEFRIGHT + return new OMX; +#else + return NULL; +#endif +} + status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const { const size_t SIZE = 256; @@ -577,6 +604,7 @@ void MediaPlayerService::Client::disconnect() p = mPlayer; } mClient.clear(); + mPlayer.clear(); // clear the notification to prevent callbacks to dead client @@ -624,12 +652,19 @@ static player_type getPlayerType(int fd, int64_t offset, int64_t length) EAS_Shutdown(easdata); } +#if USE_STAGEFRIGHT + return STAGEFRIGHT_PLAYER; +#endif + // Fall through to PV return PV_PLAYER; } static player_type getPlayerType(const char* url) { + if (TestPlayerStub::canBeUsed(url)) { + return TEST_PLAYER; + } // use MidiFile for MIDI extensions int lenURL = strlen(url); @@ -643,6 +678,10 @@ static player_type getPlayerType(const char* url) } } +#if USE_STAGEFRIGHT + return STAGEFRIGHT_PLAYER; +#endif + // Fall through to PV return PV_PLAYER; } @@ -666,6 +705,21 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, LOGV(" create VorbisPlayer"); p = new VorbisPlayer(); break; +#if USE_STAGEFRIGHT + case STAGEFRIGHT_PLAYER: + LOGV(" create StagefrightPlayer"); + p = new StagefrightPlayer; + break; +#else + case STAGEFRIGHT_PLAYER: + LOG_ALWAYS_FATAL( + "Should not be here, stagefright player not enabled."); + break; +#endif + case TEST_PLAYER: + LOGV("Create Test Player stub"); + p = new TestPlayerStub(); + break; } if (p != NULL) { if (p->initCheck() == NO_ERROR) { @@ -730,7 +784,11 @@ status_t MediaPlayerService::Client::setDataSource(const char *url) // now set data source LOGV(" setDataSource"); mStatus = p->setDataSource(url); - if (mStatus == NO_ERROR) mPlayer = p; + if (mStatus == NO_ERROR) { + mPlayer = p; + } else { + LOGE(" error: %d", mStatus); + } return mStatus; } } @@ -814,10 +872,14 @@ status_t MediaPlayerService::Client::setMetadataFilter(const Parcel& filter) status_t MediaPlayerService::Client::getMetadata( bool update_only, bool apply_filter, Parcel *reply) { + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + status_t status; - reply->writeInt32(-1); // Placeholder for the return code + // Placeholder for the return code, updated by the caller. + reply->writeInt32(-1); - SortedVector<MetadataType> updates; + SortedVector<MetadataType> ids; // We don't block notifications while we fetch the data. We clear // mMetadataUpdated first so we don't lose notifications happening @@ -825,15 +887,34 @@ status_t MediaPlayerService::Client::getMetadata( { Mutex::Autolock lock(mLock); if (update_only) { - updates = mMetadataUpdated; + ids = mMetadataUpdated; } mMetadataUpdated.clear(); } - // FIXME: Implement, query the native player and do the optional filtering, etc... - status = OK; + const size_t begin = reply->dataPosition(); + reply->writeInt32(-1); // Placeholder for the length of the metadata + reply->writeInt32(kMetaMarker); - return status; + status = p->getMetadata(ids, reply); + + if (status != OK) { + reply->setDataPosition(begin); + LOGE("getMetadata failed %d", status); + return status; + } + + // FIXME: Implement filtering on the result. Not critical since + // filtering takes place on the update notifications already. This + // would be when all the metadata are fetch and a filter is set. + + const size_t end = reply->dataPosition(); + + // Everything is fine, update the metadata length. + reply->setDataPosition(begin); + reply->writeInt32(end - begin); + reply->setDataPosition(end); + return OK; } status_t MediaPlayerService::Client::prepareAsync() @@ -1136,7 +1217,8 @@ Exit: #undef LOG_TAG #define LOG_TAG "AudioSink" MediaPlayerService::AudioOutput::AudioOutput() -{ + : mCallback(NULL), + mCallbackCookie(NULL) { mTrack = 0; mStreamType = AudioSystem::MUSIC; mLeftVolume = 1.0; @@ -1206,8 +1288,13 @@ float MediaPlayerService::AudioOutput::msecsPerFrame() const return mMsecsPerFrame; } -status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelCount, int format, int bufferCount) +status_t MediaPlayerService::AudioOutput::open( + uint32_t sampleRate, int channelCount, int format, int bufferCount, + AudioCallback cb, void *cookie) { + mCallback = cb; + mCallbackCookie = cookie; + // Check argument "bufferCount" against the mininum buffer count if (bufferCount < mMinBufferCount) { LOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount); @@ -1228,7 +1315,17 @@ status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelC } frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; - AudioTrack *t = new AudioTrack(mStreamType, sampleRate, format, channelCount, frameCount); + + AudioTrack *t; + if (mCallback != NULL) { + t = new AudioTrack( + mStreamType, sampleRate, format, channelCount, frameCount, + 0 /* flags */, CallbackWrapper, this); + } else { + t = new AudioTrack( + mStreamType, sampleRate, format, channelCount, frameCount); + } + if ((t == 0) || (t->initCheck() != NO_ERROR)) { LOGE("Unable to create audio track"); delete t; @@ -1254,6 +1351,8 @@ void MediaPlayerService::AudioOutput::start() ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) { + LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); + //LOGV("write(%p, %u)", buffer, size); if (mTrack) return mTrack->write(buffer, size); return NO_INIT; @@ -1294,6 +1393,20 @@ void MediaPlayerService::AudioOutput::setVolume(float left, float right) } } +// static +void MediaPlayerService::AudioOutput::CallbackWrapper( + int event, void *cookie, void *info) { + if (event != AudioTrack::EVENT_MORE_DATA) { + return; + } + + AudioOutput *me = (AudioOutput *)cookie; + AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; + + (*me->mCallback)( + me, buffer->raw, buffer->size, me->mCallbackCookie); +} + #undef LOG_TAG #define LOG_TAG "AudioCache" MediaPlayerService::AudioCache::AudioCache(const char* name) : @@ -1314,8 +1427,14 @@ float MediaPlayerService::AudioCache::msecsPerFrame() const return mMsecsPerFrame; } -status_t MediaPlayerService::AudioCache::open(uint32_t sampleRate, int channelCount, int format, int bufferCount) +status_t MediaPlayerService::AudioCache::open( + uint32_t sampleRate, int channelCount, int format, int bufferCount, + AudioCallback cb, void *cookie) { + if (cb != NULL) { + return UNKNOWN_ERROR; // TODO: implement this. + } + LOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount); if (mHeap->getHeapID() < 0) return NO_INIT; mSampleRate = sampleRate; diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index db3d5d7c506c..94cb9178a3ec 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -35,6 +35,7 @@ typedef int32_t MetadataType; class IMediaRecorder; class IMediaMetadataRetriever; +class IOMX; #define CALLBACK_ANTAGONIZER 0 #if CALLBACK_ANTAGONIZER @@ -75,7 +76,12 @@ class MediaPlayerService : public BnMediaPlayerService virtual ssize_t frameSize() const; virtual uint32_t latency() const; virtual float msecsPerFrame() const; - virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=4); + + virtual status_t open( + uint32_t sampleRate, int channelCount, + int format, int bufferCount, + AudioCallback cb, void *cookie); + virtual void start(); virtual ssize_t write(const void* buffer, size_t size); virtual void stop(); @@ -90,8 +96,12 @@ class MediaPlayerService : public BnMediaPlayerService static int getMinBufferCount(); private: static void setMinBufferCount(); + static void CallbackWrapper( + int event, void *me, void *info); AudioTrack* mTrack; + AudioCallback mCallback; + void * mCallbackCookie; int mStreamType; float mLeftVolume; float mRightVolume; @@ -119,7 +129,12 @@ class MediaPlayerService : public BnMediaPlayerService virtual ssize_t frameSize() const { return ssize_t(mChannelCount * ((mFormat == AudioSystem::PCM_16_BIT)?sizeof(int16_t):sizeof(u_int8_t))); } virtual uint32_t latency() const; virtual float msecsPerFrame() const; - virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=1); + + virtual status_t open( + uint32_t sampleRate, int channelCount, int format, + int bufferCount = 1, + AudioCallback cb = NULL, void *cookie = NULL); + virtual void start() {} virtual ssize_t write(const void* buffer, size_t size); virtual void stop() {} @@ -166,6 +181,7 @@ public: virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length); virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); + virtual sp<IOMX> createOMX(); virtual status_t dump(int fd, const Vector<String16>& args); diff --git a/media/libmediaplayerservice/MidiFile.h b/media/libmediaplayerservice/MidiFile.h index 83d97fee6dfa..30b6a2e0c307 100644 --- a/media/libmediaplayerservice/MidiFile.h +++ b/media/libmediaplayerservice/MidiFile.h @@ -46,7 +46,13 @@ public: virtual status_t reset(); virtual status_t setLooping(int loop); virtual player_type playerType() { return SONIVOX_PLAYER; } - virtual status_t invoke(const Parcel& request, Parcel *reply) {return INVALID_OPERATION;} + virtual status_t invoke(const Parcel& request, Parcel *reply) { + return INVALID_OPERATION; + } + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records) { + return INVALID_OPERATION; + } private: status_t createOutputTrack(); diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp new file mode 100644 index 000000000000..8597275048e7 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -0,0 +1,213 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "StagefrightPlayer" +#include <utils/Log.h> + +#include "StagefrightPlayer.h" +#include <media/stagefright/MediaPlayerImpl.h> + +namespace android { + +StagefrightPlayer::StagefrightPlayer() + : mPlayer(NULL) { + LOGV("StagefrightPlayer"); +} + +StagefrightPlayer::~StagefrightPlayer() { + LOGV("~StagefrightPlayer"); + reset(); + LOGV("~StagefrightPlayer done."); +} + +status_t StagefrightPlayer::initCheck() { + LOGV("initCheck"); + return OK; +} + +status_t StagefrightPlayer::setDataSource(const char *url) { + LOGV("setDataSource('%s')", url); + + reset(); + mPlayer = new MediaPlayerImpl(url); + + status_t err = mPlayer->initCheck(); + if (err != OK) { + delete mPlayer; + mPlayer = NULL; + } else { + mPlayer->setAudioSink(mAudioSink); + } + + return err; +} + +status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); + + reset(); + mPlayer = new MediaPlayerImpl(fd, offset, length); + + status_t err = mPlayer->initCheck(); + if (err != OK) { + delete mPlayer; + mPlayer = NULL; + } else { + mPlayer->setAudioSink(mAudioSink); + } + + return err; +} + +status_t StagefrightPlayer::setVideoSurface(const sp<ISurface> &surface) { + LOGV("setVideoSurface"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->setISurface(surface); + + return OK; +} + +status_t StagefrightPlayer::prepare() { + LOGV("prepare"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + sendEvent( + MEDIA_SET_VIDEO_SIZE, + mPlayer->getWidth(), mPlayer->getHeight()); + + return OK; +} + +status_t StagefrightPlayer::prepareAsync() { + LOGV("prepareAsync"); + + status_t err = prepare(); + + if (err != OK) { + return err; + } + + sendEvent(MEDIA_PREPARED); + + return OK; +} + +status_t StagefrightPlayer::start() { + LOGV("start"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->play(); + + return OK; +} + +status_t StagefrightPlayer::stop() { + LOGV("stop"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + reset(); + + return OK; +} + +status_t StagefrightPlayer::pause() { + LOGV("pause"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->pause(); + + return OK; +} + +bool StagefrightPlayer::isPlaying() { + LOGV("isPlaying"); + return mPlayer != NULL && mPlayer->isPlaying(); +} + +status_t StagefrightPlayer::seekTo(int msec) { + LOGV("seekTo"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + status_t err = mPlayer->seekTo((int64_t)msec * 1000); + + sendEvent(MEDIA_SEEK_COMPLETE); + + return err; +} + +status_t StagefrightPlayer::getCurrentPosition(int *msec) { + LOGV("getCurrentPosition"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + *msec = mPlayer->getPosition() / 1000; + return OK; +} + +status_t StagefrightPlayer::getDuration(int *msec) { + LOGV("getDuration"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + *msec = mPlayer->getDuration() / 1000; + return OK; +} + +status_t StagefrightPlayer::reset() { + LOGV("reset"); + + delete mPlayer; + mPlayer = NULL; + + return OK; +} + +status_t StagefrightPlayer::setLooping(int loop) { + LOGV("setLooping"); + return UNKNOWN_ERROR; +} + +player_type StagefrightPlayer::playerType() { + LOGV("playerType"); + return STAGEFRIGHT_PLAYER; +} + +status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) { + return INVALID_OPERATION; +} + +void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) { + MediaPlayerInterface::setAudioSink(audioSink); + + if (mPlayer != NULL) { + mPlayer->setAudioSink(audioSink); + } +} + +status_t StagefrightPlayer::getMetadata( + const SortedVector<MetadataType> &ids, Parcel *records) { + return INVALID_OPERATION; +} + +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h new file mode 100644 index 000000000000..f93c1f854999 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -0,0 +1,63 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef ANDROID_STAGEFRIGHTPLAYER_H +#define ANDROID_STAGEFRIGHTPLAYER_H + +#include <media/MediaPlayerInterface.h> + +namespace android { + +class MediaPlayerImpl; + +class StagefrightPlayer : public MediaPlayerInterface { +public: + StagefrightPlayer(); + virtual ~StagefrightPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<ISurface> &surface); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t invoke(const Parcel &request, Parcel *reply); + virtual void setAudioSink(const sp<AudioSink> &audioSink); + + virtual status_t getMetadata( + const SortedVector<MetadataType> &ids, Parcel *records); + +private: + MediaPlayerImpl *mPlayer; + + StagefrightPlayer(const StagefrightPlayer &); + StagefrightPlayer &operator=(const StagefrightPlayer &); +}; + +} // namespace android + +#endif // ANDROID_STAGEFRIGHTPLAYER_H diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp new file mode 100644 index 000000000000..862770864565 --- /dev/null +++ b/media/libmediaplayerservice/TestPlayerStub.cpp @@ -0,0 +1,196 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "TestPlayerStub" +#include "utils/Log.h" + +#include "TestPlayerStub.h" + +#include <dlfcn.h> // for dlopen/dlclose +#include <stdlib.h> +#include <string.h> +#include <cutils/properties.h> +#include <utils/Errors.h> // for status_t + +#include "media/MediaPlayerInterface.h" + + +namespace { +using android::status_t; +using android::MediaPlayerBase; + +const char *kTestUrlScheme = "test:"; +const char *kUrlParam = "url="; + +const char *kBuildTypePropName = "ro.build.type"; +const char *kEngBuild = "eng"; +const char *kTestBuild = "test"; + +// @return true if the current build is 'eng' or 'test'. +bool isTestBuild() +{ + char prop[PROPERTY_VALUE_MAX] = { '\0', }; + + property_get(kBuildTypePropName, prop, '\0'); + return strcmp(prop, kEngBuild) == 0 || strcmp(prop, kTestBuild) == 0; +} + +// @return true if the url scheme is 'test:' +bool isTestUrl(const char *url) +{ + return url && strncmp(url, kTestUrlScheme, strlen(kTestUrlScheme)) == 0; +} + +} // anonymous namespace + +namespace android { + +TestPlayerStub::TestPlayerStub() + :mUrl(NULL), mFilename(NULL), mContentUrl(NULL), + mHandle(NULL), mNewPlayer(NULL), mDeletePlayer(NULL), + mPlayer(NULL) { } + +TestPlayerStub::~TestPlayerStub() +{ + resetInternal(); +} + +status_t TestPlayerStub::initCheck() +{ + return isTestBuild() ? OK : INVALID_OPERATION; +} + +// Parse mUrl to get: +// * The library to be dlopened. +// * The url to be passed to the real setDataSource impl. +// +// mUrl is expected to be in following format: +// +// test:<name of the .so>?url=<url for setDataSource> +// +// The value of the url parameter is treated as a string (no +// unescaping of illegal charaters). +status_t TestPlayerStub::parseUrl() +{ + if (strlen(mUrl) < strlen(kTestUrlScheme)) { + resetInternal(); + return BAD_VALUE; + } + + char *i = mUrl + strlen(kTestUrlScheme); + + mFilename = i; + + while (*i != '\0' && *i != '?') { + ++i; + } + + if (*i == '\0' || strncmp(i + 1, kUrlParam, strlen(kUrlParam)) != 0) { + resetInternal(); + return BAD_VALUE; + } + *i = '\0'; // replace '?' to nul-terminate mFilename + + mContentUrl = i + 1 + strlen(kUrlParam); + return OK; +} + +// Load the dynamic library. +// Create the test player. +// Call setDataSource on the test player with the url in param. +status_t TestPlayerStub::setDataSource(const char *url) +{ + if (!isTestUrl(url) || NULL != mHandle) { + return INVALID_OPERATION; + } + + mUrl = strdup(url); + + status_t status = parseUrl(); + + if (OK != status) { + resetInternal(); + return status; + } + + ::dlerror(); // Clears any pending error. + + // Load the test player from the url. dlopen will fail if the lib + // is not there. dls are under /system/lib + // None of the entry points should be NULL. + mHandle = ::dlopen(mFilename, RTLD_NOW | RTLD_GLOBAL); + if (!mHandle) { + LOGE("dlopen failed: %s", ::dlerror()); + resetInternal(); + return UNKNOWN_ERROR; + } + + // Load the 2 entry points to create and delete instances. + const char *err; + mNewPlayer = reinterpret_cast<NEW_PLAYER>(dlsym(mHandle, + "newPlayer")); + err = ::dlerror(); + if (err || mNewPlayer == NULL) { + // if err is NULL the string <null> is inserted in the logs => + // mNewPlayer was NULL. + LOGE("dlsym for newPlayer failed %s", err); + resetInternal(); + return UNKNOWN_ERROR; + } + + mDeletePlayer = reinterpret_cast<DELETE_PLAYER>(dlsym(mHandle, + "deletePlayer")); + err = ::dlerror(); + if (err || mDeletePlayer == NULL) { + LOGE("dlsym for deletePlayer failed %s", err); + resetInternal(); + return UNKNOWN_ERROR; + } + + mPlayer = (*mNewPlayer)(); + return mPlayer->setDataSource(mContentUrl); +} + +// Internal cleanup. +status_t TestPlayerStub::resetInternal() +{ + if(mUrl) { + free(mUrl); + mUrl = NULL; + } + mFilename = NULL; + mContentUrl = NULL; + + if (mPlayer) { + LOG_ASSERT(mDeletePlayer != NULL); + (*mDeletePlayer)(mPlayer); + mPlayer = NULL; + } + + if (mHandle) { + ::dlclose(mHandle); + mHandle = NULL; + } + return OK; +} + +/* static */ bool TestPlayerStub::canBeUsed(const char *url) +{ + return isTestBuild() && isTestUrl(url); +} + +} // namespace android diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h new file mode 100644 index 000000000000..339b10851bc6 --- /dev/null +++ b/media/libmediaplayerservice/TestPlayerStub.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_FRAMEWORKS_BASE_MEDIA_LIBMEDIAPLAYERSERVICE_TESTPLAYERSTUB_H__ +#define ANDROID_FRAMEWORKS_BASE_MEDIA_LIBMEDIAPLAYERSERVICE_TESTPLAYERSTUB_H__ + +#include <media/MediaPlayerInterface.h> +#include <utils/Errors.h> + +namespace android { +class MediaPlayerBase; // in media/MediaPlayerInterface.h + +// Wrapper around a test media player that gets dynamically loaded. +// +// The URL passed to setDataSource has this format: +// +// test:<name of the .so>?url=<url for the real setDataSource impl.> +// +// e.g: +// test:invoke_test_media_player.so?url=http://youtube.com/ +// test:invoke_test_media_player.so?url=speedtest +// +// TestPlayerStub::setDataSource loads the library in the test url. 2 +// entry points with C linkage are expected. One to create the test +// player and one to destroy it. +// +// extern "C" android::MediaPlayerBase* newPlayer(); +// extern "C" android::status_t deletePlayer(android::MediaPlayerBase *p); +// +// Once the test player has been loaded, its setDataSource +// implementation is called with the value of the 'url' parameter. +// +// typical usage in a java test: +// ============================ +// +// MediaPlayer p = new MediaPlayer(); +// p.setDataSource("test:invoke_mock_media_player.so?url=http://youtube.com"); +// p.prepare(); +// ... +// p.release(); + +class TestPlayerStub : public MediaPlayerInterface { + public: + typedef MediaPlayerBase* (*NEW_PLAYER)(); + typedef status_t (*DELETE_PLAYER)(MediaPlayerBase *); + + TestPlayerStub(); + virtual ~TestPlayerStub(); + + // Called right after the constructor. Check if the current build + // allows test players. + virtual status_t initCheck(); + + // @param url Should be a test url. See class comment. + virtual status_t setDataSource(const char* url); + + // Test player for a file descriptor source is not supported. + virtual status_t setDataSource(int, int64_t, int64_t) { + return INVALID_OPERATION; + } + + + // All the methods below wrap the mPlayer instance. + virtual status_t setVideoSurface(const android::sp<android::ISurface>& s) { + return mPlayer->setVideoSurface(s); + } + virtual status_t prepare() {return mPlayer->prepare();} + virtual status_t prepareAsync() {return mPlayer->prepareAsync();} + virtual status_t start() {return mPlayer->start();} + virtual status_t stop() {return mPlayer->stop();} + virtual status_t pause() {return mPlayer->pause();} + virtual bool isPlaying() {return mPlayer->isPlaying();} + virtual status_t seekTo(int msec) {return mPlayer->seekTo(msec);} + virtual status_t getCurrentPosition(int *p) { + return mPlayer->getCurrentPosition(p); + } + virtual status_t getDuration(int *d) {return mPlayer->getDuration(d);} + virtual status_t reset() {return mPlayer->reset();} + virtual status_t setLooping(int b) {return mPlayer->setLooping(b);} + virtual player_type playerType() {return mPlayer->playerType();} + virtual status_t invoke(const android::Parcel& in, android::Parcel *out) { + return mPlayer->invoke(in, out); + } + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records) { + return INVALID_OPERATION; + } + + + // @return true if the current build is 'eng' or 'test' and the + // url's scheme is 'test:' + static bool canBeUsed(const char *url); + + private: + // Release the player, dlclose the library. + status_t resetInternal(); + status_t parseUrl(); + + char *mUrl; // test:foo.so?url=http://bar + char *mFilename; // foo.so + char *mContentUrl; // http://bar + void *mHandle; // returned by dlopen + NEW_PLAYER mNewPlayer; + DELETE_PLAYER mDeletePlayer; + MediaPlayerBase *mPlayer; // wrapped player +}; + +} // namespace android + +#endif diff --git a/media/libmediaplayerservice/VorbisPlayer.h b/media/libmediaplayerservice/VorbisPlayer.h index 40246543d4ce..040eb36a7c7d 100644 --- a/media/libmediaplayerservice/VorbisPlayer.h +++ b/media/libmediaplayerservice/VorbisPlayer.h @@ -54,6 +54,10 @@ public: virtual status_t setLooping(int loop); virtual player_type playerType() { return VORBIS_PLAYER; } virtual status_t invoke(const Parcel& request, Parcel *reply) {return INVALID_OPERATION;} + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records) { + return INVALID_OPERATION; + } private: status_t setdatasource(const char *path, int fd, int64_t offset, int64_t length); diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk new file mode 100644 index 000000000000..5944d9c2bf04 --- /dev/null +++ b/media/libstagefright/Android.mk @@ -0,0 +1,60 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + CachingDataSource.cpp \ + DataSource.cpp \ + FileSource.cpp \ + HTTPDataSource.cpp \ + HTTPStream.cpp \ + MP3Extractor.cpp \ + MPEG4Extractor.cpp \ + MPEG4Writer.cpp \ + MediaBuffer.cpp \ + MediaBufferGroup.cpp \ + MediaExtractor.cpp \ + MediaPlayerImpl.cpp \ + MediaSource.cpp \ + MetaData.cpp \ + MmapSource.cpp \ + QComHardwareRenderer.cpp \ + SampleTable.cpp \ + ShoutcastSource.cpp \ + SoftwareRenderer.cpp \ + SurfaceRenderer.cpp \ + TimeSource.cpp \ + TimedEventQueue.cpp \ + Utils.cpp \ + AudioPlayer.cpp \ + ESDS.cpp \ + OMXClient.cpp \ + OMXDecoder.cpp \ + string.cpp + +LOCAL_C_INCLUDES:= \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libmedia \ + libutils \ + libcutils \ + libui + +ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) + LOCAL_LDLIBS += -lpthread +endif + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_PRELINK_MODULE:= false + +LOCAL_MODULE:= libstagefright + +include $(BUILD_SHARED_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp new file mode 100644 index 000000000000..17c72b926e3e --- /dev/null +++ b/media/libstagefright/AudioPlayer.cpp @@ -0,0 +1,285 @@ +/* + * 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. + */ + +#undef NDEBUG +#include <assert.h> + +#define LOG_TAG "AudioPlayer" +#include <utils/Log.h> + +#include <media/AudioTrack.h> +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink) + : mSource(NULL), + mAudioTrack(NULL), + mInputBuffer(NULL), + mSampleRate(0), + mLatencyUs(0), + mFrameSize(0), + mNumFramesPlayed(0), + mPositionTimeMediaUs(-1), + mPositionTimeRealUs(-1), + mSeeking(false), + mStarted(false), + mAudioSink(audioSink) { +} + +AudioPlayer::~AudioPlayer() { + if (mStarted) { + stop(); + } +} + +void AudioPlayer::setSource(MediaSource *source) { + assert(mSource == NULL); + mSource = source; +} + +void AudioPlayer::start() { + assert(!mStarted); + assert(mSource != NULL); + + status_t err = mSource->start(); + assert(err == OK); + + sp<MetaData> format = mSource->getFormat(); + const char *mime; + bool success = format->findCString(kKeyMIMEType, &mime); + assert(success); + assert(!strcasecmp(mime, "audio/raw")); + + success = format->findInt32(kKeySampleRate, &mSampleRate); + assert(success); + + int32_t numChannels; + success = format->findInt32(kKeyChannelCount, &numChannels); + assert(success); + + if (mAudioSink.get() != NULL) { + status_t err = mAudioSink->open( + mSampleRate, numChannels, AudioSystem::PCM_16_BIT, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &AudioPlayer::AudioSinkCallback, this); + assert(err == OK); + + mLatencyUs = (int64_t)mAudioSink->latency() * 1000; + mFrameSize = mAudioSink->frameSize(); + + mAudioSink->start(); + } else { + mAudioTrack = new AudioTrack( + AudioSystem::MUSIC, mSampleRate, AudioSystem::PCM_16_BIT, + numChannels, 8192, 0, &AudioCallback, this, 0); + + assert(mAudioTrack->initCheck() == OK); + + mLatencyUs = (int64_t)mAudioTrack->latency() * 1000; + mFrameSize = mAudioTrack->frameSize(); + + mAudioTrack->start(); + } + + mStarted = true; +} + +void AudioPlayer::pause() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->pause(); + } else { + mAudioTrack->stop(); + } +} + +void AudioPlayer::resume() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->start(); + } else { + mAudioTrack->start(); + } +} + +void AudioPlayer::stop() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->stop(); + } else { + mAudioTrack->stop(); + + delete mAudioTrack; + mAudioTrack = NULL; + } + + // Make sure to release any buffer we hold onto so that the + // source is able to stop(). + if (mInputBuffer != NULL) { + LOGI("AudioPlayer releasing input buffer."); + + mInputBuffer->release(); + mInputBuffer = NULL; + } + + mSource->stop(); + + mNumFramesPlayed = 0; + mPositionTimeMediaUs = -1; + mPositionTimeRealUs = -1; + mSeeking = false; + mStarted = false; +} + +// static +void AudioPlayer::AudioCallback(int event, void *user, void *info) { + static_cast<AudioPlayer *>(user)->AudioCallback(event, info); +} + +// static +void AudioPlayer::AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *buffer, size_t size, void *cookie) { + AudioPlayer *me = (AudioPlayer *)cookie; + + me->fillBuffer(buffer, size); +} + +void AudioPlayer::AudioCallback(int event, void *info) { + if (event != AudioTrack::EVENT_MORE_DATA) { + return; + } + + AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; + fillBuffer(buffer->raw, buffer->size); +} + +void AudioPlayer::fillBuffer(void *data, size_t size) { + if (mNumFramesPlayed == 0) { + LOGI("AudioCallback"); + } + + size_t size_done = 0; + size_t size_remaining = size; + while (size_remaining > 0) { + MediaSource::ReadOptions options; + + { + Mutex::Autolock autoLock(mLock); + + if (mSeeking) { + options.setSeekTo(mSeekTimeUs); + + if (mInputBuffer != NULL) { + mInputBuffer->release(); + mInputBuffer = NULL; + } + mSeeking = false; + } + } + + if (mInputBuffer == NULL) { + status_t err = mSource->read(&mInputBuffer, &options); + + assert((err == OK && mInputBuffer != NULL) + || (err != OK && mInputBuffer == NULL)); + + if (err != OK) { + memset((char *)data + size_done, 0, size_remaining); + break; + } + + int32_t units, scale; + bool success = + mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units); + success = success && + mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + Mutex::Autolock autoLock(mLock); + mPositionTimeMediaUs = (int64_t)units * 1000000 / scale; + mPositionTimeRealUs = + ((mNumFramesPlayed + size_done / 4) * 1000000) / mSampleRate; // XXX + } + + if (mInputBuffer->range_length() == 0) { + mInputBuffer->release(); + mInputBuffer = NULL; + + continue; + } + + size_t copy = size_remaining; + if (copy > mInputBuffer->range_length()) { + copy = mInputBuffer->range_length(); + } + + memcpy((char *)data + size_done, + (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), + copy); + + mInputBuffer->set_range(mInputBuffer->range_offset() + copy, + mInputBuffer->range_length() - copy); + + size_done += copy; + size_remaining -= copy; + } + + Mutex::Autolock autoLock(mLock); + mNumFramesPlayed += size / mFrameSize; +} + +int64_t AudioPlayer::getRealTimeUs() { + Mutex::Autolock autoLock(mLock); + return getRealTimeUsLocked(); +} + +int64_t AudioPlayer::getRealTimeUsLocked() const { + return -mLatencyUs + (mNumFramesPlayed * 1000000) / mSampleRate; +} + +int64_t AudioPlayer::getMediaTimeUs() { + Mutex::Autolock autoLock(mLock); + + return mPositionTimeMediaUs + (getRealTimeUsLocked() - mPositionTimeRealUs); +} + +bool AudioPlayer::getMediaTimeMapping( + int64_t *realtime_us, int64_t *mediatime_us) { + Mutex::Autolock autoLock(mLock); + + *realtime_us = mPositionTimeRealUs; + *mediatime_us = mPositionTimeMediaUs; + + return mPositionTimeRealUs != -1 || mPositionTimeMediaUs != -1; +} + +status_t AudioPlayer::seekTo(int64_t time_us) { + Mutex::Autolock autoLock(mLock); + + mSeeking = true; + mSeekTimeUs = time_us; + + return OK; +} + +} diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp new file mode 100644 index 000000000000..0fd71d5b9c18 --- /dev/null +++ b/media/libstagefright/CachingDataSource.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/CachingDataSource.h> + +#undef NDEBUG +#include <assert.h> + +#include <stdlib.h> +#include <string.h> + +namespace android { + +CachingDataSource::CachingDataSource( + DataSource *source, size_t pageSize, int numPages) + : mSource(source), + mData(malloc(pageSize * numPages)), + mPageSize(pageSize), + mFirst(NULL), + mLast(NULL) { + for (int i = 0; i < numPages; ++i) { + Page *page = new Page; + page->mPrev = mLast; + page->mNext = NULL; + + if (mLast == NULL) { + mFirst = page; + } else { + mLast->mNext = page; + } + + mLast = page; + + page->mOffset = -1; + page->mLength = 0; + page->mData = (char *)mData + mPageSize * i; + } +} + +CachingDataSource::~CachingDataSource() { + Page *page = mFirst; + while (page != NULL) { + Page *next = page->mNext; + delete page; + page = next; + } + mFirst = mLast = NULL; + + free(mData); + mData = NULL; + + delete mSource; + mSource = NULL; +} + +status_t CachingDataSource::InitCheck() const { + return OK; +} + +ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + size_t total = 0; + while (size > 0) { + Page *page = mFirst; + while (page != NULL) { + if (page->mOffset >= 0 && offset >= page->mOffset + && offset < page->mOffset + page->mLength) { + break; + } + page = page->mNext; + } + + if (page == NULL) { + page = allocate_page(); + page->mOffset = offset - offset % mPageSize; + ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize); + if (n < 0) { + page->mLength = 0; + } else { + page->mLength = (size_t)n; + } + mFirst->mPrev = page; + page->mNext = mFirst; + page->mPrev = NULL; + mFirst = page; + + if (n < 0) { + return n; + } + + if (offset >= page->mOffset + page->mLength) { + break; + } + } else { + // Move "page" to the front in LRU order. + if (page->mNext != NULL) { + page->mNext->mPrev = page->mPrev; + } else { + mLast = page->mPrev; + } + + if (page->mPrev != NULL) { + page->mPrev->mNext = page->mNext; + } else { + mFirst = page->mNext; + } + + mFirst->mPrev = page; + page->mNext = mFirst; + page->mPrev = NULL; + mFirst = page; + } + + size_t copy = page->mLength - (offset - page->mOffset); + if (copy > size) { + copy = size; + } + memcpy(data,(const char *)page->mData + (offset - page->mOffset), + copy); + + total += copy; + + if (page->mLength < mPageSize) { + // This was the final page. There is no more data beyond it. + break; + } + + offset += copy; + size -= copy; + data = (char *)data + copy; + } + + return total; +} + +CachingDataSource::Page *CachingDataSource::allocate_page() { + // The last page is the least recently used, i.e. oldest. + + Page *page = mLast; + + page->mPrev->mNext = NULL; + mLast = page->mPrev; + page->mPrev = NULL; + + return page; +} + +} // namespace android diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp new file mode 100644 index 000000000000..ee1287335bf0 --- /dev/null +++ b/media/libstagefright/CameraSource.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <binder/IServiceManager.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <ui/ICameraClient.h> +#include <ui/ICameraService.h> +#include <ui/Overlay.h> +#include <utils/String16.h> + +namespace android { + +class CameraBuffer : public MediaBuffer { +public: + CameraBuffer(const sp<IMemory> &frame) + : MediaBuffer(frame->pointer(), frame->size()), + mFrame(frame) { + } + + sp<IMemory> releaseFrame() { + sp<IMemory> frame = mFrame; + mFrame.clear(); + return frame; + } + +private: + sp<IMemory> mFrame; +}; + +class CameraSourceClient : public BnCameraClient { +public: + CameraSourceClient() + : mSource(NULL) { + } + + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { + assert(mSource != NULL); + mSource->notifyCallback(msgType, ext1, ext2); + } + + virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) { + assert(mSource != NULL); + mSource->dataCallback(msgType, data); + } + + void setCameraSource(CameraSource *source) { + mSource = source; + } + +private: + CameraSource *mSource; +}; + +class DummySurface : public BnSurface { +public: + DummySurface() {} + + virtual status_t registerBuffers(const BufferHeap &buffers) { + return OK; + } + + virtual void postBuffer(ssize_t offset) { + } + + virtual void unregisterBuffers() { + } + + virtual sp<OverlayRef> createOverlay( + uint32_t w, uint32_t h, int32_t format) { + return NULL; + } +}; + +// static +CameraSource *CameraSource::Create() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<ICameraService> service = + interface_cast<ICameraService>( + sm->getService(String16("media.camera"))); + + sp<CameraSourceClient> client = new CameraSourceClient; + sp<ICamera> camera = service->connect(client); + + CameraSource *source = new CameraSource(camera, client); + client->setCameraSource(source); + + return source; +} + +CameraSource::CameraSource( + const sp<ICamera> &camera, const sp<ICameraClient> &client) + : mCamera(camera), + mCameraClient(client), + mNumFrames(0), + mStarted(false) { + printf("params: \"%s\"\n", mCamera->getParameters().string()); +} + +CameraSource::~CameraSource() { + if (mStarted) { + stop(); + } + + mCamera->disconnect(); +} + +status_t CameraSource::start(MetaData *) { + assert(!mStarted); + + status_t err = mCamera->lock(); + assert(err == OK); + + err = mCamera->setPreviewDisplay(new DummySurface); + assert(err == OK); + mCamera->setPreviewCallbackFlag(1); + mCamera->startPreview(); + assert(err == OK); + + mStarted = true; + + return OK; +} + +status_t CameraSource::stop() { + assert(mStarted); + + mCamera->stopPreview(); + mCamera->unlock(); + + mStarted = false; + + return OK; +} + +sp<MetaData> CameraSource::getFormat() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, "video/raw"); + meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar); + meta->setInt32(kKeyWidth, 480); + meta->setInt32(kKeyHeight, 320); + + return meta; +} + +status_t CameraSource::read( + MediaBuffer **buffer, const ReadOptions *options) { + assert(mStarted); + + *buffer = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + return ERROR_UNSUPPORTED; + } + + sp<IMemory> frame; + + { + Mutex::Autolock autoLock(mLock); + while (mFrames.empty()) { + mFrameAvailableCondition.wait(mLock); + } + + frame = *mFrames.begin(); + mFrames.erase(mFrames.begin()); + } + + int count = mNumFrames++; + + *buffer = new CameraBuffer(frame); + + (*buffer)->meta_data()->clear(); + (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15); + (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count); + + (*buffer)->add_ref(); + (*buffer)->setObserver(this); + + return OK; +} + +void CameraSource::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { + printf("notifyCallback %d, %d, %d\n", msgType, ext1, ext2); +} + +void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) { + Mutex::Autolock autoLock(mLock); + + mFrames.push_back(data); + mFrameAvailableCondition.signal(); +} + +void CameraSource::signalBufferReturned(MediaBuffer *_buffer) { + CameraBuffer *buffer = static_cast<CameraBuffer *>(_buffer); + + mCamera->releaseRecordingFrame(buffer->releaseFrame()); + + buffer->setObserver(NULL); + buffer->release(); + buffer = NULL; +} + +} // namespace android diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp new file mode 100644 index 000000000000..6e6b43def271 --- /dev/null +++ b/media/libstagefright/DataSource.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <utils/String8.h> + +namespace android { + +status_t DataSource::getSize(off_t *size) { + *size = 0; + + return ERROR_UNSUPPORTED; +} + +//////////////////////////////////////////////////////////////////////////////// + +Mutex DataSource::gSnifferMutex; +List<DataSource::SnifferFunc> DataSource::gSniffers; + +bool DataSource::sniff(String8 *mimeType, float *confidence) { + *mimeType = ""; + *confidence = 0.0f; + + Mutex::Autolock autoLock(gSnifferMutex); + for (List<SnifferFunc>::iterator it = gSniffers.begin(); + it != gSniffers.end(); ++it) { + String8 newMimeType; + float newConfidence; + if ((*it)(this, &newMimeType, &newConfidence)) { + if (newConfidence > *confidence) { + *mimeType = newMimeType; + *confidence = newConfidence; + } + } + } + + return *confidence > 0.0; +} + +// static +void DataSource::RegisterSniffer(SnifferFunc func) { + Mutex::Autolock autoLock(gSnifferMutex); + + for (List<SnifferFunc>::iterator it = gSniffers.begin(); + it != gSniffers.end(); ++it) { + if (*it == func) { + return; + } + } + + gSniffers.push_back(func); +} + +// static +void DataSource::RegisterDefaultSniffers() { + RegisterSniffer(SniffMP3); + RegisterSniffer(SniffMPEG4); +} + +} // namespace android diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp new file mode 100644 index 000000000000..53b92a07df40 --- /dev/null +++ b/media/libstagefright/ESDS.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/ESDS.h> + +#include <string.h> + +namespace android { + +ESDS::ESDS(const void *data, size_t size) + : mData(new uint8_t[size]), + mSize(size), + mInitCheck(NO_INIT), + mDecoderSpecificOffset(0), + mDecoderSpecificLength(0) { + memcpy(mData, data, size); + + mInitCheck = parse(); +} + +ESDS::~ESDS() { + delete[] mData; + mData = NULL; +} + +status_t ESDS::InitCheck() const { + return mInitCheck; +} + +status_t ESDS::getCodecSpecificInfo(const void **data, size_t *size) const { + if (mInitCheck != OK) { + return mInitCheck; + } + + *data = &mData[mDecoderSpecificOffset]; + *size = mDecoderSpecificLength; + + return OK; +} + +status_t ESDS::skipDescriptorHeader( + size_t offset, size_t size, + uint8_t *tag, size_t *data_offset, size_t *data_size) const { + if (size == 0) { + return ERROR_MALFORMED; + } + + *tag = mData[offset++]; + --size; + + *data_size = 0; + bool more; + do { + if (size == 0) { + return ERROR_MALFORMED; + } + + uint8_t x = mData[offset++]; + --size; + + *data_size = (*data_size << 7) | (x & 0x7f); + more = (x & 0x80) != 0; + } + while (more); + + if (*data_size > size) { + return ERROR_MALFORMED; + } + + *data_offset = offset; + + return OK; +} + +status_t ESDS::parse() { + uint8_t tag; + size_t data_offset; + size_t data_size; + status_t err = + skipDescriptorHeader(0, mSize, &tag, &data_offset, &data_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_ESDescriptor) { + return ERROR_MALFORMED; + } + + return parseESDescriptor(data_offset, data_size); +} + +status_t ESDS::parseESDescriptor(size_t offset, size_t size) { + if (size < 3) { + return ERROR_MALFORMED; + } + + offset += 2; // skip ES_ID + size -= 2; + + unsigned streamDependenceFlag = mData[offset] & 0x80; + unsigned URL_Flag = mData[offset] & 0x40; + unsigned OCRstreamFlag = mData[offset] & 0x20; + + ++offset; + --size; + + if (streamDependenceFlag) { + offset += 2; + size -= 2; + } + + if (URL_Flag) { + if (offset >= size) { + return ERROR_MALFORMED; + } + unsigned URLlength = mData[offset]; + offset += URLlength + 1; + size -= URLlength + 1; + } + + if (OCRstreamFlag) { + offset += 2; + size -= 2; + } + + if (offset >= size) { + return ERROR_MALFORMED; + } + + uint8_t tag; + size_t sub_offset, sub_size; + status_t err = skipDescriptorHeader( + offset, size, &tag, &sub_offset, &sub_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_DecoderConfigDescriptor) { + return ERROR_MALFORMED; + } + + err = parseDecoderConfigDescriptor(sub_offset, sub_size); + + return err; +} + +status_t ESDS::parseDecoderConfigDescriptor(size_t offset, size_t size) { + if (size < 13) { + return ERROR_MALFORMED; + } + + offset += 13; + size -= 13; + + if (size == 0) { + mDecoderSpecificOffset = 0; + mDecoderSpecificLength = 0; + return OK; + } + + uint8_t tag; + size_t sub_offset, sub_size; + status_t err = skipDescriptorHeader( + offset, size, &tag, &sub_offset, &sub_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_DecoderSpecificInfo) { + return ERROR_MALFORMED; + } + + mDecoderSpecificOffset = sub_offset; + mDecoderSpecificLength = sub_size; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp new file mode 100644 index 000000000000..c26d0a0f5c07 --- /dev/null +++ b/media/libstagefright/FileSource.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/FileSource.h> + +#undef NDEBUG +#include <assert.h> + +namespace android { + +FileSource::FileSource(const char *filename) + : mFile(fopen(filename, "rb")) { +} + +FileSource::~FileSource() { + if (mFile != NULL) { + fclose(mFile); + mFile = NULL; + } +} + +status_t FileSource::InitCheck() const { + return mFile != NULL ? OK : NO_INIT; +} + +ssize_t FileSource::read_at(off_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + int err = fseeko(mFile, offset, SEEK_SET); + assert(err != -1); + + ssize_t result = fread(data, 1, size, mFile); + + return result; +} + +} // namespace android diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp new file mode 100644 index 000000000000..d1f8cd415e14 --- /dev/null +++ b/media/libstagefright/HTTPDataSource.cpp @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <stdlib.h> + +#include <media/stagefright/HTTPDataSource.h> +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/string.h> + +namespace android { + +HTTPDataSource::HTTPDataSource(const char *uri) + : mHost(NULL), + mPort(0), + mPath(NULL), + mBuffer(malloc(kBufferSize)), + mBufferLength(0), + mBufferOffset(0) { + assert(!strncasecmp("http://", uri, 7)); + + string host; + string path; + int port; + + char *slash = strchr(uri + 7, '/'); + if (slash == NULL) { + host = uri + 7; + path = "/"; + } else { + host = string(uri + 7, slash - (uri + 7)); + path = slash; + } + + char *colon = strchr(host.c_str(), ':'); + if (colon == NULL) { + port = 80; + } else { + char *end; + long tmp = strtol(colon + 1, &end, 10); + assert(end > colon + 1); + assert(tmp > 0 && tmp < 65536); + port = tmp; + + host = string(host, 0, colon - host.c_str()); + } + + LOGI("Connecting to host '%s', port %d, path '%s'", + host.c_str(), port, path.c_str()); + + mHost = strdup(host.c_str()); + mPort = port; + mPath = strdup(path.c_str()); + + status_t err = mHttp.connect(mHost, mPort); + assert(err == OK); +} + +HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path) + : mHost(strdup(host)), + mPort(port), + mPath(strdup(path)), + mBuffer(malloc(kBufferSize)), + mBufferLength(0), + mBufferOffset(0) { + status_t err = mHttp.connect(mHost, mPort); + assert(err == OK); +} + +HTTPDataSource::~HTTPDataSource() { + mHttp.disconnect(); + + free(mBuffer); + mBuffer = NULL; + + free(mPath); + mPath = NULL; +} + +ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { + if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) { + size_t num_bytes_available = mBufferLength - (offset - mBufferOffset); + + size_t copy = num_bytes_available; + if (copy > size) { + copy = size; + } + + memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy); + + return copy; + } + + mBufferOffset = offset; + mBufferLength = 0; + + char host[128]; + sprintf(host, "Host: %s\r\n", mHost); + + char range[128]; + sprintf(range, "Range: bytes=%ld-%ld\r\n\r\n", + mBufferOffset, mBufferOffset + kBufferSize - 1); + + int http_status; + + status_t err; + int attempt = 1; + for (;;) { + if ((err = mHttp.send("GET ")) != OK + || (err = mHttp.send(mPath)) != OK + || (err = mHttp.send(" HTTP/1.1\r\n")) != OK + || (err = mHttp.send(host)) != OK + || (err = mHttp.send(range)) != OK + || (err = mHttp.send("\r\n")) != OK + || (err = mHttp.receive_header(&http_status)) != OK) { + + if (attempt == 3) { + return err; + } + + mHttp.connect(mHost, mPort); + ++attempt; + } else { + break; + } + } + + if ((http_status / 100) != 2) { + return UNKNOWN_ERROR; + } + + string value; + if (!mHttp.find_header_value("Content-Length", &value)) { + return UNKNOWN_ERROR; + } + + char *end; + unsigned long contentLength = strtoul(value.c_str(), &end, 10); + + ssize_t num_bytes_received = mHttp.receive(mBuffer, contentLength); + + if (num_bytes_received <= 0) { + return num_bytes_received; + } + + mBufferLength = (size_t)num_bytes_received; + + size_t copy = mBufferLength; + if (copy > size) { + copy = size; + } + + memcpy(data, mBuffer, copy); + + return copy; +} + +} // namespace android + diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp new file mode 100644 index 000000000000..29e6f726b99f --- /dev/null +++ b/media/libstagefright/HTTPStream.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/socket.h> + +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <media/stagefright/HTTPStream.h> + +namespace android { + +// static +const char *HTTPStream::kStatusKey = ":status:"; + +HTTPStream::HTTPStream() + : mState(READY), + mSocket(-1) { +} + +HTTPStream::~HTTPStream() { + disconnect(); +} + +status_t HTTPStream::connect(const char *server, int port) { + status_t err = OK; + + if (mState == CONNECTED) { + return ERROR_ALREADY_CONNECTED; + } + + assert(mSocket == -1); + mSocket = socket(AF_INET, SOCK_STREAM, 0); + + if (mSocket < 0) { + return UNKNOWN_ERROR; + } + + struct hostent *ent = gethostbyname(server); + if (ent == NULL) { + err = ERROR_UNKNOWN_HOST; + goto exit1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; + memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + + if (::connect(mSocket, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + err = ERROR_CANNOT_CONNECT; + goto exit1; + } + + mState = CONNECTED; + + return OK; + +exit1: + close(mSocket); + mSocket = -1; + + return err; +} + +status_t HTTPStream::disconnect() { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + assert(mSocket >= 0); + close(mSocket); + mSocket = -1; + + mState = READY; + + return OK; +} + +status_t HTTPStream::send(const char *data, size_t size) { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + while (size > 0) { + ssize_t n = ::send(mSocket, data, size, 0); + + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + size -= (size_t)n; + data += (size_t)n; + } + + return OK; +} + +status_t HTTPStream::send(const char *data) { + return send(data, strlen(data)); +} + +status_t HTTPStream::receive_line(char *line, size_t size) { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + bool saw_CR = false; + size_t length = 0; + + for (;;) { + char c; + ssize_t n = recv(mSocket, &c, 1, 0); + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + if (saw_CR && c == '\n') { + // We have a complete line. + + line[length - 1] = '\0'; + return OK; + } + + saw_CR = (c == '\r'); + + assert(length + 1 < size); + line[length++] = c; + } +} + +status_t HTTPStream::receive_header(int *http_status) { + *http_status = -1; + mHeaders.clear(); + + char line[256]; + status_t err = receive_line(line, sizeof(line)); + if (err != OK) { + return err; + } + + mHeaders.add(string(kStatusKey), string(line)); + + char *spacePos = strchr(line, ' '); + if (spacePos == NULL) { + // Malformed response? + return UNKNOWN_ERROR; + } + + char *status_start = spacePos + 1; + char *status_end = status_start; + while (isdigit(*status_end)) { + ++status_end; + } + + if (status_end == status_start) { + // Malformed response, status missing? + return UNKNOWN_ERROR; + } + + memmove(line, status_start, status_end - status_start); + line[status_end - status_start] = '\0'; + + long tmp = strtol(line, NULL, 10); + if (tmp < 0 || tmp > 999) { + return UNKNOWN_ERROR; + } + + *http_status = (int)tmp; + + for (;;) { + err = receive_line(line, sizeof(line)); + if (err != OK) { + return err; + } + + if (*line == '\0') { + // Empty line signals the end of the header. + break; + } + + // puts(line); + + char *colonPos = strchr(line, ':'); + if (colonPos == NULL) { + mHeaders.add(string(line), string()); + } else { + char *end_of_key = colonPos; + while (end_of_key > line && isspace(end_of_key[-1])) { + --end_of_key; + } + + char *start_of_value = colonPos + 1; + while (isspace(*start_of_value)) { + ++start_of_value; + } + + *end_of_key = '\0'; + + mHeaders.add(string(line), string(start_of_value)); + } + } + + return OK; +} + +ssize_t HTTPStream::receive(void *data, size_t size) { + size_t total = 0; + while (total < size) { + ssize_t n = recv(mSocket, (char *)data + total, size - total, 0); + + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + total += (size_t)n; + } + + return (ssize_t)total; +} + +bool HTTPStream::find_header_value(const string &key, string *value) const { + ssize_t index = mHeaders.indexOfKey(key); + if (index < 0) { + value->clear(); + return false; + } + + *value = mHeaders.valueAt(index); + + return true; +} + +} // namespace android + diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp new file mode 100644 index 000000000000..6b47a3808a0b --- /dev/null +++ b/media/libstagefright/MP3Extractor.cpp @@ -0,0 +1,527 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MP3Extractor" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +static bool get_mp3_frame_size( + uint32_t header, size_t *frame_size, + int *out_sampling_rate = NULL, int *out_channels = NULL, + int *out_bitrate = NULL) { + *frame_size = 0; + + if (out_sampling_rate) { + *out_sampling_rate = 0; + } + + if (out_channels) { + *out_channels = 0; + } + + if (out_bitrate) { + *out_bitrate = 0; + } + + if ((header & 0xffe00000) != 0xffe00000) { + return false; + } + + unsigned version = (header >> 19) & 3; + + if (version == 0x01) { + return false; + } + + unsigned layer = (header >> 17) & 3; + + if (layer == 0x00) { + return false; + } + + unsigned protection = (header >> 16) & 1; + + unsigned bitrate_index = (header >> 12) & 0x0f; + + if (bitrate_index == 0 || bitrate_index == 0x0f) { + // Disallow "free" bitrate. + + LOGE("We disallow 'free' bitrate for now."); + return false; + } + + unsigned sampling_rate_index = (header >> 10) & 3; + + if (sampling_rate_index == 3) { + return false; + } + + static const int kSamplingRateV1[] = { 44100, 48000, 32000 }; + int sampling_rate = kSamplingRateV1[sampling_rate_index]; + if (version == 2 /* V2 */) { + sampling_rate /= 2; + } else if (version == 0 /* V2.5 */) { + sampling_rate /= 4; + } + + unsigned padding = (header >> 9) & 1; + + if (layer == 3) { + // layer I + + static const int kBitrateV1[] = { + 32, 64, 96, 128, 160, 192, 224, 256, + 288, 320, 352, 384, 416, 448 + }; + + static const int kBitrateV2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 144, 160, 176, 192, 224, 256 + }; + + int bitrate = + (version == 3 /* V1 */) + ? kBitrateV1[bitrate_index - 1] + : kBitrateV2[bitrate_index - 1]; + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + *frame_size = (12000 * bitrate / sampling_rate + padding) * 4; + } else { + // layer II or III + + static const int kBitrateV1L2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384 + }; + + static const int kBitrateV1L3[] = { + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320 + }; + + static const int kBitrateV2[] = { + 8, 16, 24, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 144, 160 + }; + + int bitrate; + if (version == 3 /* V1 */) { + bitrate = (layer == 2 /* L2 */) + ? kBitrateV1L2[bitrate_index - 1] + : kBitrateV1L3[bitrate_index - 1]; + } else { + // V2 (or 2.5) + + bitrate = kBitrateV2[bitrate_index - 1]; + } + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + *frame_size = 144000 * bitrate / sampling_rate + padding; + } + + if (out_sampling_rate) { + *out_sampling_rate = sampling_rate; + } + + if (out_channels) { + int channel_mode = (header >> 6) & 3; + + *out_channels = (channel_mode == 3) ? 1 : 2; + } + + return true; +} + +static bool Resync( + DataSource *source, uint32_t match_header, + off_t *inout_pos, uint32_t *out_header) { + // Everything must match except for + // protection, bitrate, padding, private bits and mode extension. + const uint32_t kMask = 0xfffe0ccf; + + const size_t kMaxFrameSize = 4096; + uint8_t *buffer = new uint8_t[kMaxFrameSize]; + + off_t pos = *inout_pos - kMaxFrameSize; + size_t buffer_offset = kMaxFrameSize; + size_t buffer_length = kMaxFrameSize; + bool valid = false; + do { + if (buffer_offset + 3 >= buffer_length) { + if (buffer_length < kMaxFrameSize) { + break; + } + + pos += buffer_offset; + + if (pos >= *inout_pos + 128 * 1024) { + // Don't scan forever. + LOGV("giving up at offset %ld", pos); + break; + } + + memmove(buffer, &buffer[buffer_offset], buffer_length - buffer_offset); + buffer_length = buffer_length - buffer_offset; + buffer_offset = 0; + + ssize_t n = source->read_at( + pos, &buffer[buffer_length], kMaxFrameSize - buffer_length); + + if (n <= 0) { + break; + } + + buffer_length += (size_t)n; + + continue; + } + + uint32_t header = U32_AT(&buffer[buffer_offset]); + + if (match_header != 0 && (header & kMask) != (match_header & kMask)) { + ++buffer_offset; + continue; + } + + size_t frame_size; + int sample_rate, num_channels, bitrate; + if (!get_mp3_frame_size(header, &frame_size, + &sample_rate, &num_channels, &bitrate)) { + ++buffer_offset; + continue; + } + + LOGV("found possible 1st frame at %ld", pos + buffer_offset); + + // We found what looks like a valid frame, + // now find its successors. + + off_t test_pos = pos + buffer_offset + frame_size; + + valid = true; + for (int j = 0; j < 3; ++j) { + uint8_t tmp[4]; + if (source->read_at(test_pos, tmp, 4) < 4) { + valid = false; + break; + } + + uint32_t test_header = U32_AT(tmp); + + LOGV("subsequent header is %08x", test_header); + + if ((test_header & kMask) != (header & kMask)) { + valid = false; + break; + } + + size_t test_frame_size; + if (!get_mp3_frame_size(test_header, &test_frame_size)) { + valid = false; + break; + } + + LOGV("found subsequent frame #%d at %ld", j + 2, test_pos); + + test_pos += test_frame_size; + } + + if (valid) { + *inout_pos = pos + buffer_offset; + + if (out_header != NULL) { + *out_header = header; + } + } else { + LOGV("no dice, no valid sequence of frames found."); + } + + ++buffer_offset; + + } while (!valid); + + delete[] buffer; + buffer = NULL; + + return valid; +} + +class MP3Source : public MediaSource { +public: + MP3Source( + const sp<MetaData> &meta, DataSource *source, + off_t first_frame_pos, uint32_t fixed_header); + + virtual ~MP3Source(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + sp<MetaData> mMeta; + DataSource *mDataSource; + off_t mFirstFramePos; + uint32_t mFixedHeader; + off_t mCurrentPos; + int64_t mCurrentTimeUs; + bool mStarted; + + MediaBufferGroup *mGroup; + + MP3Source(const MP3Source &); + MP3Source &operator=(const MP3Source &); +}; + +MP3Extractor::MP3Extractor(DataSource *source) + : mDataSource(source), + mFirstFramePos(-1), + mFixedHeader(0) { + off_t pos = 0; + uint32_t header; + bool success = Resync(mDataSource, 0, &pos, &header); + assert(success); + + if (success) { + mFirstFramePos = pos; + mFixedHeader = header; + + size_t frame_size; + int sample_rate; + int num_channels; + int bitrate; + get_mp3_frame_size( + header, &frame_size, &sample_rate, &num_channels, &bitrate); + + mMeta = new MetaData; + + mMeta->setCString(kKeyMIMEType, "audio/mpeg"); + mMeta->setInt32(kKeySampleRate, sample_rate); + mMeta->setInt32(kKeyBitRate, bitrate); + mMeta->setInt32(kKeyChannelCount, num_channels); + + off_t fileSize; + if (mDataSource->getSize(&fileSize) == OK) { + mMeta->setInt32( + kKeyDuration, + 8 * (fileSize - mFirstFramePos) / bitrate); + mMeta->setInt32(kKeyTimeScale, 1000); + } + } +} + +MP3Extractor::~MP3Extractor() { + delete mDataSource; + mDataSource = NULL; +} + +status_t MP3Extractor::countTracks(int *num_tracks) { + *num_tracks = mFirstFramePos < 0 ? 0 : 1; + + return OK; +} + +status_t MP3Extractor::getTrack(int index, MediaSource **source) { + if (mFirstFramePos < 0 || index != 0) { + return ERROR_OUT_OF_RANGE; + } + + *source = new MP3Source( + mMeta, mDataSource, mFirstFramePos, mFixedHeader); + + return OK; +} + +sp<MetaData> MP3Extractor::getTrackMetaData(int index) { + if (mFirstFramePos < 0 || index != 0) { + return NULL; + } + + return mMeta; +} + +//////////////////////////////////////////////////////////////////////////////// + +MP3Source::MP3Source( + const sp<MetaData> &meta, DataSource *source, + off_t first_frame_pos, uint32_t fixed_header) + : mMeta(meta), + mDataSource(source), + mFirstFramePos(first_frame_pos), + mFixedHeader(fixed_header), + mCurrentPos(0), + mCurrentTimeUs(0), + mStarted(false), + mGroup(NULL) { +} + +MP3Source::~MP3Source() { + if (mStarted) { + stop(); + } +} + +status_t MP3Source::start(MetaData *) { + assert(!mStarted); + + mGroup = new MediaBufferGroup; + + const size_t kMaxFrameSize = 32768; + mGroup->add_buffer(new MediaBuffer(kMaxFrameSize)); + + mCurrentPos = mFirstFramePos; + mCurrentTimeUs = 0; + + mStarted = true; + + return OK; +} + +status_t MP3Source::stop() { + assert(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +sp<MetaData> MP3Source::getFormat() { + return mMeta; +} + +status_t MP3Source::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + if (options != NULL && options->getSeekTo(&seekTimeUs)) { + int32_t bitrate; + if (!mMeta->findInt32(kKeyBitRate, &bitrate)) { + // bitrate is in kbits/sec. + LOGI("no bitrate"); + + return ERROR_UNSUPPORTED; + } + + mCurrentTimeUs = seekTimeUs; + mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 1000000 * 125; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + size_t frame_size; + for (;;) { + ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4); + if (n < 4) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + uint32_t header = U32_AT((const uint8_t *)buffer->data()); + + if (get_mp3_frame_size(header, &frame_size)) { + break; + } + + // Lost sync. + LOGW("lost sync!\n"); + + off_t pos = mCurrentPos; + if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) { + LOGE("Unable to resync. Signalling end of stream."); + + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + mCurrentPos = pos; + + // Try again with the new position. + } + + assert(frame_size <= buffer->size()); + + ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size); + if (n < (ssize_t)frame_size) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + buffer->set_range(0, frame_size); + + buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000); + buffer->meta_data()->setInt32(kKeyTimeScale, 1000); + + mCurrentPos += frame_size; + mCurrentTimeUs += 1152 * 1000000 / 44100; + + *out = buffer; + + return OK; +} + +bool SniffMP3(DataSource *source, String8 *mimeType, float *confidence) { + off_t pos = 0; + uint32_t header; + if (!Resync(source, 0, &pos, &header)) { + return false; + } + + *mimeType = "audio/mpeg"; + *confidence = 0.3f; + + return true; +} + +} // namespace android diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp new file mode 100644 index 000000000000..caaec06af32d --- /dev/null +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -0,0 +1,937 @@ +/* + * 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. + */ + +#define LOG_TAG "MPEG4Extractor" +#include <utils/Log.h> + +#include <arpa/inet.h> + +#undef NDEBUG +#include <assert.h> + +#include <ctype.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/SampleTable.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +class MPEG4Source : public MediaSource { +public: + // Caller retains ownership of both "dataSource" and "sampleTable". + MPEG4Source(const sp<MetaData> &format, DataSource *dataSource, + SampleTable *sampleTable); + + virtual ~MPEG4Source(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + sp<MetaData> mFormat; + DataSource *mDataSource; + int32_t mTimescale; + SampleTable *mSampleTable; + uint32_t mCurrentSampleIndex; + + bool mIsAVC; + bool mStarted; + + MediaBufferGroup *mGroup; + + MediaBuffer *mBuffer; + size_t mBufferOffset; + size_t mBufferSizeRemaining; + + bool mNeedsNALFraming; + + MPEG4Source(const MPEG4Source &); + MPEG4Source &operator=(const MPEG4Source &); +}; + +static void hexdump(const void *_data, size_t size) { + const uint8_t *data = (const uint8_t *)_data; + size_t offset = 0; + while (offset < size) { + printf("0x%04x ", offset); + + size_t n = size - offset; + if (n > 16) { + n = 16; + } + + for (size_t i = 0; i < 16; ++i) { + if (i == 8) { + printf(" "); + } + + if (offset + i < size) { + printf("%02x ", data[offset + i]); + } else { + printf(" "); + } + } + + printf(" "); + + for (size_t i = 0; i < n; ++i) { + if (isprint(data[offset + i])) { + printf("%c", data[offset + i]); + } else { + printf("."); + } + } + + printf("\n"); + + offset += 16; + } +} + +static const char *const FourCC2MIME(uint32_t fourcc) { + switch (fourcc) { + case FOURCC('m', 'p', '4', 'a'): + return "audio/mp4a-latm"; + + case FOURCC('s', 'a', 'm', 'r'): + return "audio/3gpp"; + + case FOURCC('m', 'p', '4', 'v'): + return "video/mp4v-es"; + + case FOURCC('s', '2', '6', '3'): + return "video/3gpp"; + + case FOURCC('a', 'v', 'c', '1'): + return "video/avc"; + + default: + assert(!"should not be here."); + return NULL; + } +} + +MPEG4Extractor::MPEG4Extractor(DataSource *source) + : mDataSource(source), + mHaveMetadata(false), + mFirstTrack(NULL), + mLastTrack(NULL) { +} + +MPEG4Extractor::~MPEG4Extractor() { + Track *track = mFirstTrack; + while (track) { + Track *next = track->next; + + delete track->sampleTable; + track->sampleTable = NULL; + + delete track; + track = next; + } + mFirstTrack = mLastTrack = NULL; + + delete mDataSource; + mDataSource = NULL; +} + +status_t MPEG4Extractor::countTracks(int *num_tracks) { + status_t err; + if ((err = readMetaData()) != OK) { + return err; + } + + *num_tracks = 0; + Track *track = mFirstTrack; + while (track) { + ++*num_tracks; + track = track->next; + } + + return OK; +} + +sp<MetaData> MPEG4Extractor::getTrackMetaData(int index) { + if (index < 0) { + return NULL; + } + + status_t err; + if ((err = readMetaData()) != OK) { + return NULL; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return NULL; + } + + track = track->next; + --index; + } + + return track->meta; +} + +status_t MPEG4Extractor::readMetaData() { + if (mHaveMetadata) { + return OK; + } + + off_t offset = 0; + status_t err; + while ((err = parseChunk(&offset, 0)) == OK) { + } + + if (mHaveMetadata) { + return OK; + } + + return err; +} + +static void MakeFourCCString(uint32_t x, char *s) { + s[0] = x >> 24; + s[1] = (x >> 16) & 0xff; + s[2] = (x >> 8) & 0xff; + s[3] = x & 0xff; + s[4] = '\0'; +} + +status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { + uint32_t hdr[2]; + if (mDataSource->read_at(*offset, hdr, 8) < 8) { + return ERROR_IO; + } + uint64_t chunk_size = ntohl(hdr[0]); + uint32_t chunk_type = ntohl(hdr[1]); + off_t data_offset = *offset + 8; + + if (chunk_size == 1) { + if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + } + + char chunk[5]; + MakeFourCCString(chunk_type, chunk); + +#if 0 + static const char kWhitespace[] = " "; + const char *indent = &kWhitespace[sizeof(kWhitespace) - 1 - 2 * depth]; + printf("%sfound chunk '%s' of size %lld\n", indent, chunk, chunk_size); + + char buffer[256]; + if (chunk_size <= sizeof(buffer)) { + if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) { + return ERROR_IO; + } + + hexdump(buffer, chunk_size); + } +#endif + + off_t chunk_data_size = *offset + chunk_size - data_offset; + + switch(chunk_type) { + case FOURCC('m', 'o', 'o', 'v'): + case FOURCC('t', 'r', 'a', 'k'): + case FOURCC('m', 'd', 'i', 'a'): + case FOURCC('m', 'i', 'n', 'f'): + case FOURCC('d', 'i', 'n', 'f'): + case FOURCC('s', 't', 'b', 'l'): + case FOURCC('m', 'v', 'e', 'x'): + case FOURCC('m', 'o', 'o', 'f'): + case FOURCC('t', 'r', 'a', 'f'): + case FOURCC('m', 'f', 'r', 'a'): + case FOURCC('s', 'k', 'i' ,'p'): + { + off_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + + if (chunk_type == FOURCC('m', 'o', 'o', 'v')) { + mHaveMetadata = true; + + return UNKNOWN_ERROR; // Return a dummy error. + } + break; + } + + case FOURCC('t', 'k', 'h', 'd'): + { + assert(chunk_data_size >= 4); + + uint8_t version; + if (mDataSource->read_at(data_offset, &version, 1) < 1) { + return ERROR_IO; + } + + uint64_t ctime, mtime, duration; + int32_t id; + uint32_t width, height; + + if (version == 1) { + if (chunk_data_size != 36 + 60) { + return ERROR_MALFORMED; + } + + uint8_t buffer[36 + 60]; + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + ctime = U64_AT(&buffer[4]); + mtime = U64_AT(&buffer[12]); + id = U32_AT(&buffer[20]); + duration = U64_AT(&buffer[28]); + width = U32_AT(&buffer[88]); + height = U32_AT(&buffer[92]); + } else if (version == 0) { + if (chunk_data_size != 24 + 60) { + return ERROR_MALFORMED; + } + + uint8_t buffer[24 + 60]; + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + ctime = U32_AT(&buffer[4]); + mtime = U32_AT(&buffer[8]); + id = U32_AT(&buffer[12]); + duration = U32_AT(&buffer[20]); + width = U32_AT(&buffer[76]); + height = U32_AT(&buffer[80]); + } + + Track *track = new Track; + track->next = NULL; + if (mLastTrack) { + mLastTrack->next = track; + } else { + mFirstTrack = track; + } + mLastTrack = track; + + track->meta = new MetaData; + track->timescale = 0; + track->sampleTable = new SampleTable(mDataSource); + track->meta->setCString(kKeyMIMEType, "application/octet-stream"); + + *offset += chunk_size; + break; + } + + case FOURCC('m', 'd', 'h', 'd'): + { + if (chunk_data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t version; + if (mDataSource->read_at( + data_offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + + off_t timescale_offset; + + if (version == 1) { + timescale_offset = data_offset + 4 + 16; + } else if (version == 0) { + timescale_offset = data_offset + 4 + 8; + } else { + return ERROR_IO; + } + + uint32_t timescale; + if (mDataSource->read_at( + timescale_offset, ×cale, sizeof(timescale)) + < (ssize_t)sizeof(timescale)) { + return ERROR_IO; + } + + mLastTrack->timescale = ntohl(timescale); + mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale); + + int64_t duration; + if (version == 1) { + if (mDataSource->read_at( + timescale_offset + 4, &duration, sizeof(duration)) + < (ssize_t)sizeof(duration)) { + return ERROR_IO; + } + duration = ntoh64(duration); + } else { + int32_t duration32; + if (mDataSource->read_at( + timescale_offset + 4, &duration32, sizeof(duration32)) + < (ssize_t)sizeof(duration32)) { + return ERROR_IO; + } + duration = ntohl(duration32); + } + mLastTrack->meta->setInt32(kKeyDuration, duration); + + *offset += chunk_size; + break; + } + + case FOURCC('h', 'd', 'l', 'r'): + { + if (chunk_data_size < 25) { + return ERROR_MALFORMED; + } + + uint8_t buffer[24]; + if (mDataSource->read_at(data_offset, buffer, 24) < 24) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + if (U32_AT(&buffer[4]) != 0) { + // pre_defined should be 0. + return ERROR_MALFORMED; + } + + mHandlerType = U32_AT(&buffer[8]); + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'd'): + { + if (chunk_data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t buffer[8]; + assert(chunk_data_size >= (off_t)sizeof(buffer)); + if (mDataSource->read_at( + data_offset, buffer, 8) < 8) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + uint32_t entry_count = U32_AT(&buffer[4]); + + if (entry_count > 1) { + // For now we only support a single type of media per track. + return ERROR_UNSUPPORTED; + } + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + 8; + for (uint32_t i = 0; i < entry_count; ++i) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('m', 'p', '4', 'a'): + case FOURCC('s', 'a', 'm', 'r'): + { + if (mHandlerType != FOURCC('s', 'o', 'u', 'n')) { + return ERROR_MALFORMED; + } + + uint8_t buffer[8 + 20]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic AudioSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index = U16_AT(&buffer[6]); + uint16_t num_channels = U16_AT(&buffer[16]); + + if (!strcasecmp("audio/3gpp", FourCC2MIME(chunk_type))) { + // AMR audio is always mono. + num_channels = 1; + } + + uint16_t sample_size = U16_AT(&buffer[18]); + uint32_t sample_rate = U32_AT(&buffer[24]) >> 16; + + printf("*** coding='%s' %d channels, size %d, rate %d\n", + chunk, num_channels, sample_size, sample_rate); + + mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + mLastTrack->meta->setInt32(kKeyChannelCount, num_channels); + mLastTrack->meta->setInt32(kKeySampleRate, sample_rate); + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('m', 'p', '4', 'v'): + case FOURCC('s', '2', '6', '3'): + case FOURCC('a', 'v', 'c', '1'): + { + if (mHandlerType != FOURCC('v', 'i', 'd', 'e')) { + return ERROR_MALFORMED; + } + + uint8_t buffer[78]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic VideoSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index = U16_AT(&buffer[6]); + uint16_t width = U16_AT(&buffer[6 + 18]); + uint16_t height = U16_AT(&buffer[6 + 20]); + + printf("*** coding='%s' width=%d height=%d\n", + chunk, width, height); + + mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + mLastTrack->meta->setInt32(kKeyWidth, width); + mLastTrack->meta->setInt32(kKeyHeight, height); + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('s', 't', 'c', 'o'): + case FOURCC('c', 'o', '6', '4'): + { + status_t err = + mLastTrack->sampleTable->setChunkOffsetParams( + chunk_type, data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'c'): + { + status_t err = + mLastTrack->sampleTable->setSampleToChunkParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'z'): + case FOURCC('s', 't', 'z', '2'): + { + status_t err = + mLastTrack->sampleTable->setSampleSizeParams( + chunk_type, data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 't', 's'): + { + status_t err = + mLastTrack->sampleTable->setTimeToSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 's'): + { + status_t err = + mLastTrack->sampleTable->setSyncSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('e', 's', 'd', 's'): + { + if (chunk_data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t buffer[256]; + if (chunk_data_size > (off_t)sizeof(buffer)) { + return ERROR_BUFFER_TOO_SMALL; + } + + if (mDataSource->read_at( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + mLastTrack->meta->setData( + kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4); + + *offset += chunk_size; + break; + } + + case FOURCC('a', 'v', 'c', 'C'): + { + char buffer[256]; + if (chunk_data_size > (off_t)sizeof(buffer)) { + return ERROR_BUFFER_TOO_SMALL; + } + + if (mDataSource->read_at( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + mLastTrack->meta->setData( + kKeyAVCC, kTypeAVCC, buffer, chunk_data_size); + + *offset += chunk_size; + break; + } + + default: + { + *offset += chunk_size; + break; + } + } + + return OK; +} + +status_t MPEG4Extractor::getTrack(int index, MediaSource **source) { + *source = NULL; + + if (index < 0) { + return ERROR_OUT_OF_RANGE; + } + + status_t err; + if ((err = readMetaData()) != OK) { + return err; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return ERROR_OUT_OF_RANGE; + } + + track = track->next; + --index; + } + + *source = new MPEG4Source( + track->meta, mDataSource, track->sampleTable); + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG4Source::MPEG4Source( + const sp<MetaData> &format, + DataSource *dataSource, SampleTable *sampleTable) + : mFormat(format), + mDataSource(dataSource), + mTimescale(0), + mSampleTable(sampleTable), + mCurrentSampleIndex(0), + mIsAVC(false), + mStarted(false), + mGroup(NULL), + mBuffer(NULL), + mBufferOffset(0), + mBufferSizeRemaining(0), + mNeedsNALFraming(false) { + const char *mime; + bool success = mFormat->findCString(kKeyMIMEType, &mime); + assert(success); + + success = mFormat->findInt32(kKeyTimeScale, &mTimescale); + assert(success); + + mIsAVC = !strcasecmp(mime, "video/avc"); +} + +MPEG4Source::~MPEG4Source() { + if (mStarted) { + stop(); + } +} + +status_t MPEG4Source::start(MetaData *params) { + assert(!mStarted); + + int32_t val; + if (mIsAVC && params && params->findInt32(kKeyNeedsNALFraming, &val) + && val != 0) { + mNeedsNALFraming = true; + } else { + mNeedsNALFraming = false; + } + + mGroup = new MediaBufferGroup; + + size_t max_size; + status_t err = mSampleTable->getMaxSampleSize(&max_size); + assert(err == OK); + + // Add padding for de-framing of AVC content just in case. + mGroup->add_buffer(new MediaBuffer(max_size + 2)); + + mStarted = true; + + return OK; +} + +status_t MPEG4Source::stop() { + assert(mStarted); + + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + delete mGroup; + mGroup = NULL; + + mStarted = false; + mCurrentSampleIndex = 0; + + return OK; +} + +sp<MetaData> MPEG4Source::getFormat() { + return mFormat; +} + +status_t MPEG4Source::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + uint32_t sampleIndex; + status_t err = mSampleTable->findClosestSample( + seekTimeUs * mTimescale / 1000000, + &sampleIndex, SampleTable::kSyncSample_Flag); + + if (err != OK) { + return err; + } + + mCurrentSampleIndex = sampleIndex; + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + // fall through + } + + if (mBuffer == NULL) { + off_t offset; + size_t size; + status_t err = mSampleTable->getSampleOffsetAndSize( + mCurrentSampleIndex, &offset, &size); + + if (err != OK) { + return err; + } + + uint32_t dts; + err = mSampleTable->getDecodingTime(mCurrentSampleIndex, &dts); + + if (err != OK) { + return err; + } + + err = mGroup->acquire_buffer(&mBuffer); + if (err != OK) { + assert(mBuffer == NULL); + return err; + } + + assert(mBuffer->size() + 2 >= size); + + ssize_t num_bytes_read = + mDataSource->read_at(offset, (uint8_t *)mBuffer->data() + 2, size); + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + return err; + } + + mBuffer->set_range(2, size); + mBuffer->meta_data()->clear(); + mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts); + mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale); + + ++mCurrentSampleIndex; + + mBufferOffset = 2; + mBufferSizeRemaining = size; + } + + if (!mIsAVC) { + *out = mBuffer; + mBuffer = NULL; + + return OK; + } + + uint8_t *data = (uint8_t *)mBuffer->data() + mBufferOffset; + assert(mBufferSizeRemaining >= 2); + + size_t nal_length = (data[0] << 8) | data[1]; + assert(mBufferSizeRemaining >= 2 + nal_length); + + if (mNeedsNALFraming) { + // Insert marker. + data[-2] = data[-1] = data[0] = 0; + data[1] = 1; + + mBuffer->set_range(mBufferOffset - 2, nal_length + 4); + } else { + mBuffer->set_range(mBufferOffset + 2, nal_length); + } + + mBufferOffset += nal_length + 2; + mBufferSizeRemaining -= nal_length + 2; + + if (mBufferSizeRemaining > 0) { + *out = mBuffer->clone(); + } else { + *out = mBuffer; + mBuffer = NULL; + } + + return OK; +} + +bool SniffMPEG4(DataSource *source, String8 *mimeType, float *confidence) { + uint8_t header[8]; + + ssize_t n = source->read_at(4, header, sizeof(header)); + if (n < (ssize_t)sizeof(header)) { + return false; + } + + if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8) + || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)) { + *mimeType = "video/mp4"; + *confidence = 0.1; + + return true; + } + + return false; +} + +} // namespace android + diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp new file mode 100644 index 000000000000..6bdf282179f3 --- /dev/null +++ b/media/libstagefright/MPEG4Writer.cpp @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <arpa/inet.h> + +#undef NDEBUG +#include <assert.h> + +#include <ctype.h> +#include <pthread.h> + +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/Utils.h> + +namespace android { + +class MPEG4Writer::Track { +public: + Track(MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source); + ~Track(); + + void start(); + void stop(); + + int64_t getDuration() const; + void writeTrackHeader(int32_t trackID); + +private: + MPEG4Writer *mOwner; + sp<MetaData> mMeta; + MediaSource *mSource; + volatile bool mDone; + + pthread_t mThread; + + struct SampleInfo { + size_t size; + off_t offset; + int64_t timestamp; + }; + List<SampleInfo> mSampleInfos; + + void *mCodecSpecificData; + size_t mCodecSpecificDataSize; + + static void *ThreadWrapper(void *me); + void threadEntry(); + + Track(const Track &); + Track &operator=(const Track &); +}; + +MPEG4Writer::MPEG4Writer(const char *filename) + : mFile(fopen(filename, "wb")), + mOffset(0), + mMdatOffset(0) { + assert(mFile != NULL); +} + +MPEG4Writer::~MPEG4Writer() { + stop(); + + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + delete *it; + } + mTracks.clear(); +} + +void MPEG4Writer::addSource(const sp<MetaData> &meta, MediaSource *source) { + Track *track = new Track(this, meta, source); + mTracks.push_back(track); +} + +void MPEG4Writer::start() { + if (mFile == NULL) { + return; + } + + beginBox("ftyp"); + writeFourcc("isom"); + writeInt32(0); + writeFourcc("isom"); + endBox(); + + mMdatOffset = mOffset; + write("\x00\x00\x00\x01mdat????????", 16); + + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + (*it)->start(); + } +} + +void MPEG4Writer::stop() { + if (mFile == NULL) { + return; + } + + int64_t max_duration = 0; + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + (*it)->stop(); + + int64_t duration = (*it)->getDuration(); + if (duration > max_duration) { + max_duration = duration; + } + } + + // Fix up the size of the 'mdat' chunk. + fseek(mFile, mMdatOffset + 8, SEEK_SET); + int64_t size = mOffset - mMdatOffset; + size = hton64(size); + fwrite(&size, 1, 8, mFile); + fseek(mFile, mOffset, SEEK_SET); + + time_t now = time(NULL); + + beginBox("moov"); + + beginBox("mvhd"); + writeInt32(0); // version=0, flags=0 + writeInt32(now); // creation time + writeInt32(now); // modification time + writeInt32(1000); // timescale + writeInt32(max_duration); + writeInt32(0x10000); // rate + writeInt16(0x100); // volume + writeInt16(0); // reserved + writeInt32(0); // reserved + writeInt32(0); // reserved + writeInt32(0x10000); // matrix + writeInt32(0); + writeInt32(0); + writeInt32(0); + writeInt32(0x10000); + writeInt32(0); + writeInt32(0); + writeInt32(0); + writeInt32(0x40000000); + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(mTracks.size() + 1); // nextTrackID + endBox(); // mvhd + + int32_t id = 1; + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it, ++id) { + (*it)->writeTrackHeader(id); + } + endBox(); // moov + + assert(mBoxes.empty()); + + fclose(mFile); + mFile = NULL; +} + +off_t MPEG4Writer::addSample(MediaBuffer *buffer) { + Mutex::Autolock autoLock(mLock); + + off_t old_offset = mOffset; + + fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), + 1, buffer->range_length(), mFile); + + mOffset += buffer->range_length(); + + return old_offset; +} + +void MPEG4Writer::beginBox(const char *fourcc) { + assert(strlen(fourcc) == 4); + + mBoxes.push_back(mOffset); + + writeInt32(0); + writeFourcc(fourcc); +} + +void MPEG4Writer::endBox() { + assert(!mBoxes.empty()); + + off_t offset = *--mBoxes.end(); + mBoxes.erase(--mBoxes.end()); + + fseek(mFile, offset, SEEK_SET); + writeInt32(mOffset - offset); + mOffset -= 4; + fseek(mFile, mOffset, SEEK_SET); +} + +void MPEG4Writer::writeInt8(int8_t x) { + fwrite(&x, 1, 1, mFile); + ++mOffset; +} + +void MPEG4Writer::writeInt16(int16_t x) { + x = htons(x); + fwrite(&x, 1, 2, mFile); + mOffset += 2; +} + +void MPEG4Writer::writeInt32(int32_t x) { + x = htonl(x); + fwrite(&x, 1, 4, mFile); + mOffset += 4; +} + +void MPEG4Writer::writeInt64(int64_t x) { + x = hton64(x); + fwrite(&x, 1, 8, mFile); + mOffset += 8; +} + +void MPEG4Writer::writeCString(const char *s) { + size_t n = strlen(s); + + fwrite(s, 1, n + 1, mFile); + mOffset += n + 1; +} + +void MPEG4Writer::writeFourcc(const char *s) { + assert(strlen(s) == 4); + fwrite(s, 1, 4, mFile); + mOffset += 4; +} + +void MPEG4Writer::write(const void *data, size_t size) { + fwrite(data, 1, size, mFile); + mOffset += size; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG4Writer::Track::Track( + MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source) + : mOwner(owner), + mMeta(meta), + mSource(source), + mDone(false), + mCodecSpecificData(NULL), + mCodecSpecificDataSize(0) { +} + +MPEG4Writer::Track::~Track() { + stop(); + + if (mCodecSpecificData != NULL) { + free(mCodecSpecificData); + mCodecSpecificData = NULL; + } +} + +void MPEG4Writer::Track::start() { + mSource->start(); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + mDone = false; + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +} + +void MPEG4Writer::Track::stop() { + if (mDone) { + return; + } + + mDone = true; + + void *dummy; + pthread_join(mThread, &dummy); + + mSource->stop(); +} + +// static +void *MPEG4Writer::Track::ThreadWrapper(void *me) { + Track *track = static_cast<Track *>(me); + + track->threadEntry(); + + return NULL; +} + +void MPEG4Writer::Track::threadEntry() { + bool is_mpeg4 = false; + sp<MetaData> meta = mSource->getFormat(); + const char *mime; + meta->findCString(kKeyMIMEType, &mime); + is_mpeg4 = !strcasecmp(mime, "video/mp4v-es"); + + MediaBuffer *buffer; + while (!mDone && mSource->read(&buffer) == OK) { + if (buffer->range_length() == 0) { + buffer->release(); + buffer = NULL; + + continue; + } + + if (mCodecSpecificData == NULL && is_mpeg4) { + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + const size_t size = buffer->range_length(); + + size_t offset = 0; + while (offset + 3 < size) { + if (data[offset] == 0x00 && data[offset + 1] == 0x00 + && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) { + break; + } + + ++offset; + } + + assert(offset + 3 < size); + + mCodecSpecificDataSize = offset; + mCodecSpecificData = malloc(offset); + memcpy(mCodecSpecificData, data, offset); + + buffer->set_range(buffer->range_offset() + offset, size - offset); + } + + off_t offset = mOwner->addSample(buffer); + + SampleInfo info; + info.size = buffer->range_length(); + info.offset = offset; + + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + info.timestamp = (int64_t)units * 1000 / scale; + + mSampleInfos.push_back(info); + + buffer->release(); + buffer = NULL; + } +} + +int64_t MPEG4Writer::Track::getDuration() const { + return 10000; // XXX +} + +void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) { + const char *mime; + bool success = mMeta->findCString(kKeyMIMEType, &mime); + assert(success); + + bool is_audio = !strncasecmp(mime, "audio/", 6); + + time_t now = time(NULL); + + mOwner->beginBox("trak"); + + mOwner->beginBox("tkhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(now); // creation time + mOwner->writeInt32(now); // modification time + mOwner->writeInt32(trackID); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(getDuration()); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // layer + mOwner->writeInt16(0); // alternate group + mOwner->writeInt16(is_audio ? 0x100 : 0); // volume + mOwner->writeInt16(0); // reserved + + mOwner->writeInt32(0x10000); // matrix + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0x10000); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0x40000000); + + if (is_audio) { + mOwner->writeInt32(0); + mOwner->writeInt32(0); + } else { + int32_t width, height; + bool success = mMeta->findInt32(kKeyWidth, &width); + success = success && mMeta->findInt32(kKeyHeight, &height); + assert(success); + + mOwner->writeInt32(width); + mOwner->writeInt32(height); + } + mOwner->endBox(); // tkhd + + mOwner->beginBox("mdia"); + + mOwner->beginBox("mdhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(now); // creation time + mOwner->writeInt32(now); // modification time + mOwner->writeInt32(1000); // timescale + mOwner->writeInt32(getDuration()); + mOwner->writeInt16(0); // language code XXX + mOwner->writeInt16(0); // predefined + mOwner->endBox(); + + mOwner->beginBox("hdlr"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(0); // predefined + mOwner->writeFourcc(is_audio ? "soun" : "vide"); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeCString(""); // name + mOwner->endBox(); + + mOwner->beginBox("minf"); + + mOwner->beginBox("dinf"); + mOwner->beginBox("dref"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(1); + mOwner->beginBox("url "); + mOwner->writeInt32(1); // version=0, flags=1 + mOwner->endBox(); // url + mOwner->endBox(); // dref + mOwner->endBox(); // dinf + + if (is_audio) { + mOwner->beginBox("smhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt16(0); // balance + mOwner->writeInt16(0); // reserved + mOwner->endBox(); + } else { + mOwner->beginBox("vmhd"); + mOwner->writeInt32(0x00000001); // version=0, flags=1 + mOwner->writeInt16(0); // graphics mode + mOwner->writeInt16(0); // opcolor + mOwner->writeInt16(0); + mOwner->writeInt16(0); + mOwner->endBox(); + } + mOwner->endBox(); // minf + + mOwner->beginBox("stbl"); + + mOwner->beginBox("stsd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(1); // entry count + if (is_audio) { + mOwner->beginBox("xxxx"); // audio format XXX + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // reserved + mOwner->writeInt16(0); // data ref index + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(2); // channel count + mOwner->writeInt16(16); // sample size + mOwner->writeInt16(0); // predefined + mOwner->writeInt16(0); // reserved + + int32_t samplerate; + bool success = mMeta->findInt32(kKeySampleRate, &samplerate); + assert(success); + + mOwner->writeInt32(samplerate << 16); + mOwner->endBox(); + } else { + if (!strcasecmp("video/mp4v-es", mime)) { + mOwner->beginBox("mp4v"); + } else if (!strcasecmp("video/3gpp", mime)) { + mOwner->beginBox("s263"); + } else { + assert(!"should not be here, unknown mime type."); + } + + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // reserved + mOwner->writeInt16(0); // data ref index + mOwner->writeInt16(0); // predefined + mOwner->writeInt16(0); // reserved + mOwner->writeInt32(0); // predefined + mOwner->writeInt32(0); // predefined + mOwner->writeInt32(0); // predefined + + int32_t width, height; + bool success = mMeta->findInt32(kKeyWidth, &width); + success = success && mMeta->findInt32(kKeyHeight, &height); + assert(success); + + mOwner->writeInt16(width); + mOwner->writeInt16(height); + mOwner->writeInt32(0x480000); // horiz resolution + mOwner->writeInt32(0x480000); // vert resolution + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(1); // frame count + mOwner->write(" ", 32); + mOwner->writeInt16(0x18); // depth + mOwner->writeInt16(-1); // predefined + + assert(23 + mCodecSpecificDataSize < 128); + + if (!strcasecmp("video/mp4v-es", mime)) { + mOwner->beginBox("esds"); + + mOwner->writeInt32(0); // version=0, flags=0 + + mOwner->writeInt8(0x03); // ES_DescrTag + mOwner->writeInt8(23 + mCodecSpecificDataSize); + mOwner->writeInt16(0x0000); // ES_ID + mOwner->writeInt8(0x1f); + + mOwner->writeInt8(0x04); // DecoderConfigDescrTag + mOwner->writeInt8(15 + mCodecSpecificDataSize); + mOwner->writeInt8(0x20); // objectTypeIndication ISO/IEC 14492-2 + mOwner->writeInt8(0x11); // streamType VisualStream + + static const uint8_t kData[] = { + 0x01, 0x77, 0x00, + 0x00, 0x03, 0xe8, 0x00, + 0x00, 0x03, 0xe8, 0x00 + }; + mOwner->write(kData, sizeof(kData)); + + mOwner->writeInt8(0x05); // DecoderSpecificInfoTag + + mOwner->writeInt8(mCodecSpecificDataSize); + mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); + + static const uint8_t kData2[] = { + 0x06, // SLConfigDescriptorTag + 0x01, + 0x02 + }; + mOwner->write(kData2, sizeof(kData2)); + + mOwner->endBox(); // esds + } else if (!strcasecmp("video/3gpp", mime)) { + mOwner->beginBox("d263"); + + mOwner->writeInt32(0); // vendor + mOwner->writeInt8(0); // decoder version + mOwner->writeInt8(10); // level: 10 + mOwner->writeInt8(0); // profile: 0 + + mOwner->endBox(); // d263 + } + mOwner->endBox(); // mp4v or s263 + } + mOwner->endBox(); // stsd + + mOwner->beginBox("stts"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size() - 1); + + List<SampleInfo>::iterator it = mSampleInfos.begin(); + int64_t last = (*it).timestamp; + ++it; + while (it != mSampleInfos.end()) { + mOwner->writeInt32(1); + mOwner->writeInt32((*it).timestamp - last); + + last = (*it).timestamp; + + ++it; + } + mOwner->endBox(); // stts + + mOwner->beginBox("stsz"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(0); // default sample size + mOwner->writeInt32(mSampleInfos.size()); + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it) { + mOwner->writeInt32((*it).size); + } + mOwner->endBox(); // stsz + + mOwner->beginBox("stsc"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size()); + int32_t n = 1; + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it, ++n) { + mOwner->writeInt32(n); + mOwner->writeInt32(1); + mOwner->writeInt32(1); + } + mOwner->endBox(); // stsc + + mOwner->beginBox("co64"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size()); + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it, ++n) { + mOwner->writeInt64((*it).offset); + } + mOwner->endBox(); // co64 + + mOwner->endBox(); // stbl + mOwner->endBox(); // mdia + mOwner->endBox(); // trak +} + +} // namespace android diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp new file mode 100644 index 000000000000..cd78dbd7c0bc --- /dev/null +++ b/media/libstagefright/MediaBuffer.cpp @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#define LOG_TAG "MediaBuffer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +// XXX make this truly atomic. +static int atomic_add(int *value, int delta) { + int prev_value = *value; + *value += delta; + + return prev_value; +} + +MediaBuffer::MediaBuffer(void *data, size_t size) + : mObserver(NULL), + mNextBuffer(NULL), + mRefCount(0), + mData(data), + mSize(size), + mRangeOffset(0), + mRangeLength(size), + mOwnsData(false), + mMetaData(new MetaData), + mOriginal(NULL) { +} + +MediaBuffer::MediaBuffer(size_t size) + : mObserver(NULL), + mNextBuffer(NULL), + mRefCount(0), + mData(malloc(size)), + mSize(size), + mRangeOffset(0), + mRangeLength(size), + mOwnsData(true), + mMetaData(new MetaData), + mOriginal(NULL) { +} + +void MediaBuffer::release() { + if (mObserver == NULL) { + assert(mRefCount == 0); + delete this; + return; + } + + int prevCount = atomic_add(&mRefCount, -1); + if (prevCount == 1) { + if (mObserver == NULL) { + delete this; + return; + } + + mObserver->signalBufferReturned(this); + } + assert(prevCount > 0); +} + +void MediaBuffer::claim() { + assert(mObserver != NULL); + assert(mRefCount == 1); + + mRefCount = 0; +} + +void MediaBuffer::add_ref() { + atomic_add(&mRefCount, 1); +} + +void *MediaBuffer::data() const { + return mData; +} + +size_t MediaBuffer::size() const { + return mSize; +} + +size_t MediaBuffer::range_offset() const { + return mRangeOffset; +} + +size_t MediaBuffer::range_length() const { + return mRangeLength; +} + +void MediaBuffer::set_range(size_t offset, size_t length) { + if (offset < 0 || offset + length > mSize) { + LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize); + } + assert(offset >= 0 && offset + length <= mSize); + + mRangeOffset = offset; + mRangeLength = length; +} + +sp<MetaData> MediaBuffer::meta_data() { + return mMetaData; +} + +void MediaBuffer::reset() { + mMetaData->clear(); + set_range(0, mSize); +} + +MediaBuffer::~MediaBuffer() { + assert(mObserver == NULL); + + if (mOwnsData && mData != NULL) { + free(mData); + mData = NULL; + } + + if (mOriginal != NULL) { + mOriginal->release(); + mOriginal = NULL; + } +} + +void MediaBuffer::setObserver(MediaBufferObserver *observer) { + assert(observer == NULL || mObserver == NULL); + mObserver = observer; +} + +void MediaBuffer::setNextBuffer(MediaBuffer *buffer) { + mNextBuffer = buffer; +} + +MediaBuffer *MediaBuffer::nextBuffer() { + return mNextBuffer; +} + +int MediaBuffer::refcount() const { + return mRefCount; +} + +MediaBuffer *MediaBuffer::clone() { + MediaBuffer *buffer = new MediaBuffer(mData, mSize); + buffer->set_range(mRangeOffset, mRangeLength); + buffer->mMetaData = new MetaData(*mMetaData.get()); + + add_ref(); + buffer->mOriginal = this; + + return buffer; +} + +} // namespace android + diff --git a/media/libstagefright/MediaBufferGroup.cpp b/media/libstagefright/MediaBufferGroup.cpp new file mode 100644 index 000000000000..aec7722df6a0 --- /dev/null +++ b/media/libstagefright/MediaBufferGroup.cpp @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#define LOG_TAG "MediaBufferGroup" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> + +namespace android { + +MediaBufferGroup::MediaBufferGroup() + : mFirstBuffer(NULL), + mLastBuffer(NULL) { +} + +MediaBufferGroup::~MediaBufferGroup() { + MediaBuffer *next; + for (MediaBuffer *buffer = mFirstBuffer; buffer != NULL; + buffer = next) { + next = buffer->nextBuffer(); + + assert(buffer->refcount() == 0); + + buffer->setObserver(NULL); + buffer->release(); + } +} + +void MediaBufferGroup::add_buffer(MediaBuffer *buffer) { + Mutex::Autolock autoLock(mLock); + + buffer->setObserver(this); + + if (mLastBuffer) { + mLastBuffer->setNextBuffer(buffer); + } else { + mFirstBuffer = buffer; + } + + mLastBuffer = buffer; +} + +status_t MediaBufferGroup::acquire_buffer(MediaBuffer **out) { + Mutex::Autolock autoLock(mLock); + + for (;;) { + for (MediaBuffer *buffer = mFirstBuffer; + buffer != NULL; buffer = buffer->nextBuffer()) { + if (buffer->refcount() == 0) { + buffer->add_ref(); + buffer->reset(); + + *out = buffer; + goto exit; + } + } + + // All buffers are in use. Block until one of them is returned to us. + mCondition.wait(mLock); + } + +exit: + return OK; +} + +void MediaBufferGroup::signalBufferReturned(MediaBuffer *) { + Mutex::Autolock autoLock(mLock); + mCondition.signal(); +} + +} // namespace android diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp new file mode 100644 index 000000000000..bc667946ee5c --- /dev/null +++ b/media/libstagefright/MediaExtractor.cpp @@ -0,0 +1,54 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaExtractor" +#include <utils/Log.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaExtractor.h> +#include <utils/String8.h> + +namespace android { + +// static +MediaExtractor *MediaExtractor::Create(DataSource *source, const char *mime) { + String8 tmp; + if (mime == NULL) { + float confidence; + if (!source->sniff(&tmp, &confidence)) { + LOGE("FAILED to autodetect media content."); + + return NULL; + } + + mime = tmp.string(); + LOGI("Autodetected media content as '%s' with confidence %.2f", + mime, confidence); + } + + if (!strcasecmp(mime, "video/mp4") || !strcasecmp(mime, "audio/mp4")) { + return new MPEG4Extractor(source); + } else if (!strcasecmp(mime, "audio/mpeg")) { + return new MP3Extractor(source); + } + + return NULL; +} + +} // namespace android diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp new file mode 100644 index 000000000000..78fcdee16a8f --- /dev/null +++ b/media/libstagefright/MediaPlayerImpl.cpp @@ -0,0 +1,693 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaPlayerImpl" +#include "utils/Log.h" + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <unistd.h> + +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/CachingDataSource.h> +// #include <media/stagefright/CameraSource.h> +#include <media/stagefright/HTTPDataSource.h> +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaPlayerImpl.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXDecoder.h> +#include <media/stagefright/QComHardwareRenderer.h> +#include <media/stagefright/ShoutcastSource.h> +#include <media/stagefright/SoftwareRenderer.h> +#include <media/stagefright/SurfaceRenderer.h> +#include <media/stagefright/TimeSource.h> +#include <ui/PixelFormat.h> +#include <ui/Surface.h> + +namespace android { + +MediaPlayerImpl::MediaPlayerImpl(const char *uri) + : mInitCheck(NO_INIT), + mExtractor(NULL), + mTimeSource(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mAudioPlayer(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0), + mVideoPosition(0), + mDuration(0), + mPlaying(false), + mPaused(false), + mRenderer(NULL), + mSeeking(false), + mFrameSize(0), + mUseSoftwareColorConversion(false) { + LOGI("MediaPlayerImpl(%s)", uri); + DataSource::RegisterDefaultSniffers(); + + status_t err = mClient.connect(); + if (err != OK) { + LOGE("Failed to connect to OMXClient."); + return; + } + + if (!strncasecmp("shoutcast://", uri, 12)) { + setAudioSource(makeShoutcastSource(uri)); +#if 0 + } else if (!strncasecmp("camera:", uri, 7)) { + mVideoWidth = 480; + mVideoHeight = 320; + mVideoDecoder = CameraSource::Create(); +#endif + } else { + DataSource *source = NULL; + if (!strncasecmp("file://", uri, 7)) { + source = new MmapSource(uri + 7); + } else if (!strncasecmp("http://", uri, 7)) { + source = new HTTPDataSource(uri); + source = new CachingDataSource(source, 64 * 1024, 10); + } else { + // Assume it's a filename. + source = new MmapSource(uri); + } + + mExtractor = MediaExtractor::Create(source); + + if (mExtractor == NULL) { + return; + } + } + + init(); + + mInitCheck = OK; +} + +MediaPlayerImpl::MediaPlayerImpl(int fd, int64_t offset, int64_t length) + : mInitCheck(NO_INIT), + mExtractor(NULL), + mTimeSource(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mAudioPlayer(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0), + mVideoPosition(0), + mDuration(0), + mPlaying(false), + mPaused(false), + mRenderer(NULL), + mSeeking(false), + mFrameSize(0), + mUseSoftwareColorConversion(false) { + LOGI("MediaPlayerImpl(%d, %lld, %lld)", fd, offset, length); + DataSource::RegisterDefaultSniffers(); + + status_t err = mClient.connect(); + if (err != OK) { + LOGE("Failed to connect to OMXClient."); + return; + } + + mExtractor = MediaExtractor::Create( + new MmapSource(fd, offset, length)); + + if (mExtractor == NULL) { + return; + } + + init(); + + mInitCheck = OK; +} + +status_t MediaPlayerImpl::initCheck() const { + return mInitCheck; +} + +MediaPlayerImpl::~MediaPlayerImpl() { + stop(); + setSurface(NULL); + + LOGV("Shutting down audio."); + delete mAudioDecoder; + mAudioDecoder = NULL; + + delete mAudioSource; + mAudioSource = NULL; + + LOGV("Shutting down video."); + delete mVideoDecoder; + mVideoDecoder = NULL; + + delete mVideoSource; + mVideoSource = NULL; + + delete mExtractor; + mExtractor = NULL; + + if (mInitCheck == OK) { + mClient.disconnect(); + } + + LOGV("~MediaPlayerImpl done."); +} + +void MediaPlayerImpl::play() { + LOGI("play"); + + if (mPlaying) { + if (mPaused) { + if (mAudioSource != NULL) { + mAudioPlayer->resume(); + } + mPaused = false; + } + return; + } + + mPlaying = true; + + if (mAudioSource != NULL) { + mAudioPlayer = new AudioPlayer(mAudioSink); + mAudioPlayer->setSource(mAudioDecoder); + mAudioPlayer->start(); + mTimeSource = mAudioPlayer; + } else { + mTimeSource = new SystemTimeSource; + } + + if (mVideoDecoder != NULL) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&mVideoThread, &attr, VideoWrapper, this); + + pthread_attr_destroy(&attr); + } +} + +void MediaPlayerImpl::pause() { + if (!mPlaying || mPaused) { + return; + } + + if (mAudioSource != NULL) { + mAudioPlayer->pause(); + } + + mPaused = true; +} + +void MediaPlayerImpl::stop() { + if (!mPlaying) { + return; + } + + mPlaying = false; + + if (mVideoDecoder != NULL) { + void *dummy; + pthread_join(mVideoThread, &dummy); + } + + if (mAudioSource != NULL) { + mAudioPlayer->stop(); + + delete mAudioPlayer; + mAudioPlayer = NULL; + } else { + delete mTimeSource; + } + + mTimeSource = NULL; +} + +// static +void *MediaPlayerImpl::VideoWrapper(void *me) { + ((MediaPlayerImpl *)me)->videoEntry(); + + return NULL; +} + +void MediaPlayerImpl::videoEntry() { + bool firstFrame = true; + bool eof = false; + + status_t err = mVideoDecoder->start(); + assert(err == OK); + + while (mPlaying) { + MediaBuffer *buffer; + + MediaSource::ReadOptions options; + bool seeking = false; + + { + Mutex::Autolock autoLock(mLock); + if (mSeeking) { + LOGI("seek-options to %lld", mSeekTimeUs); + options.setSeekTo(mSeekTimeUs); + + mSeeking = false; + seeking = true; + eof = false; + } + } + + if (eof || mPaused) { + usleep(100000); + continue; + } + + status_t err = mVideoDecoder->read(&buffer, &options); + assert((err == OK && buffer != NULL) || (err != OK && buffer == NULL)); + + if (err == ERROR_END_OF_STREAM || err != OK) { + eof = true; + continue; + } + + if (buffer->range_length() == 0) { + // The final buffer is empty. + buffer->release(); + continue; + } + + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + int64_t pts_us = (int64_t)units * 1000000 / scale; + { + Mutex::Autolock autoLock(mLock); + mVideoPosition = pts_us; + } + + if (seeking && mAudioPlayer != NULL) { + // Now that we know where exactly video seeked (taking sync-samples + // into account), we will seek the audio track to the same time. + mAudioPlayer->seekTo(pts_us); + } + + if (firstFrame || seeking) { + mTimeSourceDeltaUs = mTimeSource->getRealTimeUs() - pts_us; + firstFrame = false; + } + + displayOrDiscardFrame(buffer, pts_us); + } + + mVideoDecoder->stop(); +} + +void MediaPlayerImpl::displayOrDiscardFrame( + MediaBuffer *buffer, int64_t pts_us) { + for (;;) { + if (!mPlaying || mPaused) { + buffer->release(); + buffer = NULL; + + return; + } + + int64_t realtime_us, mediatime_us; + if (mAudioPlayer != NULL + && mAudioPlayer->getMediaTimeMapping(&realtime_us, &mediatime_us)) { + mTimeSourceDeltaUs = realtime_us - mediatime_us; + } + + int64_t now_us = mTimeSource->getRealTimeUs(); + now_us -= mTimeSourceDeltaUs; + + int64_t delay_us = pts_us - now_us; + + if (delay_us < -15000) { + // We're late. + + LOGI("we're late by %lld ms, dropping a frame\n", + -delay_us / 1000); + + buffer->release(); + buffer = NULL; + return; + } else if (delay_us > 100000) { + LOGI("we're much too early (by %lld ms)\n", + delay_us / 1000); + usleep(100000); + continue; + } else if (delay_us > 0) { + usleep(delay_us); + } + + break; + } + + { + Mutex::Autolock autoLock(mLock); + if (mRenderer != NULL) { + sendFrameToISurface(buffer); + } + } + + buffer->release(); + buffer = NULL; +} + +void MediaPlayerImpl::init() { + if (mExtractor != NULL) { + int num_tracks; + assert(mExtractor->countTracks(&num_tracks) == OK); + + mDuration = 0; + + for (int i = 0; i < num_tracks; ++i) { + const sp<MetaData> meta = mExtractor->getTrackMetaData(i); + assert(meta != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + bool is_audio = false; + bool is_acceptable = false; + if (!strncasecmp(mime, "audio/", 6)) { + is_audio = true; + is_acceptable = (mAudioSource == NULL); + } else if (!strncasecmp(mime, "video/", 6)) { + is_acceptable = (mVideoSource == NULL); + } + + if (!is_acceptable) { + continue; + } + + MediaSource *source; + if (mExtractor->getTrack(i, &source) != OK) { + continue; + } + + int32_t units, scale; + if (meta->findInt32(kKeyDuration, &units) + && meta->findInt32(kKeyTimeScale, &scale)) { + int64_t duration_us = (int64_t)units * 1000000 / scale; + if (duration_us > mDuration) { + mDuration = duration_us; + } + } + + if (is_audio) { + setAudioSource(source); + } else { + setVideoSource(source); + } + } + } +} + +void MediaPlayerImpl::setAudioSource(MediaSource *source) { + mAudioSource = source; + + sp<MetaData> meta = source->getFormat(); + + mAudioDecoder = OMXDecoder::Create(&mClient, meta); + mAudioDecoder->setSource(source); +} + +void MediaPlayerImpl::setVideoSource(MediaSource *source) { + LOGI("setVideoSource"); + mVideoSource = source; + + sp<MetaData> meta = source->getFormat(); + + bool success = meta->findInt32(kKeyWidth, &mVideoWidth); + assert(success); + + success = meta->findInt32(kKeyHeight, &mVideoHeight); + assert(success); + + mVideoDecoder = OMXDecoder::Create(&mClient, meta); + ((OMXDecoder *)mVideoDecoder)->setSource(source); + + if (mISurface.get() != NULL || mSurface.get() != NULL) { + depopulateISurface(); + populateISurface(); + } +} + +void MediaPlayerImpl::setSurface(const sp<Surface> &surface) { + LOGI("setSurface %p", surface.get()); + Mutex::Autolock autoLock(mLock); + + depopulateISurface(); + + mSurface = surface; + mISurface = NULL; + + if (mSurface.get() != NULL) { + populateISurface(); + } +} + +void MediaPlayerImpl::setISurface(const sp<ISurface> &isurface) { + LOGI("setISurface %p", isurface.get()); + Mutex::Autolock autoLock(mLock); + + depopulateISurface(); + + mSurface = NULL; + mISurface = isurface; + + if (mISurface.get() != NULL) { + populateISurface(); + } +} + +MediaSource *MediaPlayerImpl::makeShoutcastSource(const char *uri) { + if (strncasecmp(uri, "shoutcast://", 12)) { + return NULL; + } + + string host; + string path; + int port; + + char *slash = strchr(uri + 12, '/'); + if (slash == NULL) { + host = uri + 12; + path = "/"; + } else { + host = string(uri + 12, slash - (uri + 12)); + path = slash; + } + + char *colon = strchr(host.c_str(), ':'); + if (colon == NULL) { + port = 80; + } else { + char *end; + long tmp = strtol(colon + 1, &end, 10); + assert(end > colon + 1); + assert(tmp > 0 && tmp < 65536); + port = tmp; + + host = string(host, 0, colon - host.c_str()); + } + + LOGI("Connecting to host '%s', port %d, path '%s'", + host.c_str(), port, path.c_str()); + + HTTPStream *http = new HTTPStream; + int http_status; + + for (;;) { + status_t err = http->connect(host.c_str(), port); + assert(err == OK); + + err = http->send("GET "); + err = http->send(path.c_str()); + err = http->send(" HTTP/1.1\r\n"); + err = http->send("Host: "); + err = http->send(host.c_str()); + err = http->send("\r\n"); + err = http->send("Icy-MetaData: 1\r\n\r\n"); + + assert(OK == http->receive_header(&http_status)); + + if (http_status == 301 || http_status == 302) { + string location; + assert(http->find_header_value("Location", &location)); + + assert(string(location, 0, 7) == "http://"); + location.erase(0, 7); + string::size_type slashPos = location.find('/'); + if (slashPos == string::npos) { + slashPos = location.size(); + location += '/'; + } + + http->disconnect(); + + LOGI("Redirecting to %s\n", location.c_str()); + + host = string(location, 0, slashPos); + + string::size_type colonPos = host.find(':'); + if (colonPos != string::npos) { + const char *start = host.c_str() + colonPos + 1; + char *end; + long tmp = strtol(start, &end, 10); + assert(end > start && *end == '\0'); + + port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80; + } else { + port = 80; + } + + path = string(location, slashPos); + + continue; + } + + break; + } + + if (http_status != 200) { + LOGE("Connection failed: http_status = %d", http_status); + return NULL; + } + + MediaSource *source = new ShoutcastSource(http); + + return source; +} + +bool MediaPlayerImpl::isPlaying() const { + return mPlaying && !mPaused; +} + +int64_t MediaPlayerImpl::getDuration() { + return mDuration; +} + +int64_t MediaPlayerImpl::getPosition() { + int64_t position = 0; + if (mVideoSource != NULL) { + Mutex::Autolock autoLock(mLock); + position = mVideoPosition; + } else if (mAudioPlayer != NULL) { + position = mAudioPlayer->getMediaTimeUs(); + } + + return position; +} + +status_t MediaPlayerImpl::seekTo(int64_t time) { + LOGI("seekTo %lld", time); + + if (mPaused) { + return UNKNOWN_ERROR; + } + + if (mVideoSource == NULL && mAudioPlayer != NULL) { + mAudioPlayer->seekTo(time); + } else { + Mutex::Autolock autoLock(mLock); + mSeekTimeUs = time; + mSeeking = true; + } + + return OK; +} + +void MediaPlayerImpl::populateISurface() { + if (mVideoSource == NULL) { + return; + } + + sp<MetaData> meta = mVideoDecoder->getFormat(); + + int32_t format; + const char *component; + int32_t decodedWidth, decodedHeight; + bool success = meta->findInt32(kKeyColorFormat, &format); + success = success && meta->findCString(kKeyDecoderComponent, &component); + success = success && meta->findInt32(kKeyWidth, &decodedWidth); + success = success && meta->findInt32(kKeyHeight, &decodedHeight); + assert(success); + + if (mSurface.get() != NULL) { + mRenderer = + new SurfaceRenderer( + mSurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } else if (format == OMX_COLOR_FormatYUV420Planar + && !strncasecmp(component, "OMX.qcom.video.decoder.", 23)) { + mRenderer = + new QComHardwareRenderer( + mISurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } else { + LOGW("Using software renderer."); + mRenderer = new SoftwareRenderer( + mISurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } +} + +void MediaPlayerImpl::depopulateISurface() { + delete mRenderer; + mRenderer = NULL; +} + +void MediaPlayerImpl::sendFrameToISurface(MediaBuffer *buffer) { + void *platformPrivate; + if (!buffer->meta_data()->findPointer( + kKeyPlatformPrivate, &platformPrivate)) { + platformPrivate = NULL; + } + + mRenderer->render( + (const uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length(), + platformPrivate); +} + +void MediaPlayerImpl::setAudioSink( + const sp<MediaPlayerBase::AudioSink> &audioSink) { + LOGI("setAudioSink %p", audioSink.get()); + mAudioSink = audioSink; +} + +} // namespace android + diff --git a/media/libstagefright/MediaSource.cpp b/media/libstagefright/MediaSource.cpp new file mode 100644 index 000000000000..ec89b7442597 --- /dev/null +++ b/media/libstagefright/MediaSource.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/MediaSource.h> + +namespace android { + +MediaSource::MediaSource() {} + +MediaSource::~MediaSource() {} + +//////////////////////////////////////////////////////////////////////////////// + +MediaSource::ReadOptions::ReadOptions() { + reset(); +} + +void MediaSource::ReadOptions::reset() { + mOptions = 0; + mSeekTimeUs = 0; + mLatenessUs = 0; +} + +void MediaSource::ReadOptions::setSeekTo(int64_t time_us) { + mOptions |= kSeekTo_Option; + mSeekTimeUs = time_us; +} + +void MediaSource::ReadOptions::clearSeekTo() { + mOptions &= ~kSeekTo_Option; + mSeekTimeUs = 0; +} + +bool MediaSource::ReadOptions::getSeekTo(int64_t *time_us) const { + *time_us = mSeekTimeUs; + return (mOptions & kSeekTo_Option) != 0; +} + +void MediaSource::ReadOptions::setLateBy(int64_t lateness_us) { + mLatenessUs = lateness_us; +} + +int64_t MediaSource::ReadOptions::getLateBy() const { + return mLatenessUs; +} + +} // namespace android diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp new file mode 100644 index 000000000000..5d23732be80e --- /dev/null +++ b/media/libstagefright/MetaData.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <media/stagefright/MetaData.h> + +namespace android { + +MetaData::MetaData() { +} + +MetaData::MetaData(const MetaData &from) + : RefBase(), + mItems(from.mItems) { +} + +MetaData::~MetaData() { + clear(); +} + +void MetaData::clear() { + mItems.clear(); +} + +bool MetaData::remove(uint32_t key) { + ssize_t i = mItems.indexOfKey(key); + + if (i < 0) { + return false; + } + + mItems.removeItemsAt(i); + + return true; +} + +bool MetaData::setCString(uint32_t key, const char *value) { + return setData(key, TYPE_C_STRING, value, strlen(value) + 1); +} + +bool MetaData::setInt32(uint32_t key, int32_t value) { + return setData(key, TYPE_INT32, &value, sizeof(value)); +} + +bool MetaData::setFloat(uint32_t key, float value) { + return setData(key, TYPE_FLOAT, &value, sizeof(value)); +} + +bool MetaData::setPointer(uint32_t key, void *value) { + return setData(key, TYPE_POINTER, &value, sizeof(value)); +} + +bool MetaData::findCString(uint32_t key, const char **value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_C_STRING) { + return false; + } + + *value = (const char *)data; + + return true; +} + +bool MetaData::findInt32(uint32_t key, int32_t *value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_INT32) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(int32_t *)data; + + return true; +} + +bool MetaData::findFloat(uint32_t key, float *value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_FLOAT) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(float *)data; + + return true; +} + +bool MetaData::findPointer(uint32_t key, void **value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_POINTER) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(void **)data; + + return true; +} + +bool MetaData::setData( + uint32_t key, uint32_t type, const void *data, size_t size) { + bool overwrote_existing = true; + + ssize_t i = mItems.indexOfKey(key); + if (i < 0) { + typed_data item; + i = mItems.add(key, item); + + overwrote_existing = false; + } + + typed_data &item = mItems.editValueAt(i); + + item.setData(type, data, size); + + return overwrote_existing; +} + +bool MetaData::findData(uint32_t key, uint32_t *type, + const void **data, size_t *size) const { + ssize_t i = mItems.indexOfKey(key); + + if (i < 0) { + return false; + } + + const typed_data &item = mItems.valueAt(i); + + item.getData(type, data, size); + + return true; +} + +MetaData::typed_data::typed_data() + : mType(0), + mSize(0) { +} + +MetaData::typed_data::~typed_data() { + clear(); +} + +MetaData::typed_data::typed_data(const typed_data &from) + : mType(from.mType), + mSize(0) { + allocateStorage(from.mSize); + memcpy(storage(), from.storage(), mSize); +} + +MetaData::typed_data &MetaData::typed_data::operator=( + const MetaData::typed_data &from) { + if (this != &from) { + clear(); + mType = from.mType; + allocateStorage(from.mSize); + memcpy(storage(), from.storage(), mSize); + } + + return *this; +} + +void MetaData::typed_data::clear() { + freeStorage(); + + mType = 0; +} + +void MetaData::typed_data::setData( + uint32_t type, const void *data, size_t size) { + clear(); + + mType = type; + allocateStorage(size); + memcpy(storage(), data, size); +} + +void MetaData::typed_data::getData( + uint32_t *type, const void **data, size_t *size) const { + *type = mType; + *size = mSize; + *data = storage(); +} + +void MetaData::typed_data::allocateStorage(size_t size) { + mSize = size; + + if (usesReservoir()) { + return; + } + + u.ext_data = malloc(mSize); +} + +void MetaData::typed_data::freeStorage() { + if (!usesReservoir()) { + if (u.ext_data) { + free(u.ext_data); + } + } + + mSize = 0; +} + +} // namespace android + diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp new file mode 100644 index 000000000000..7cb861cde932 --- /dev/null +++ b/media/libstagefright/MmapSource.cpp @@ -0,0 +1,112 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MmapSource" +#include <utils/Log.h> + +#include <sys/mman.h> + +#undef NDEBUG +#include <assert.h> + +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include <media/stagefright/MmapSource.h> + +namespace android { + +MmapSource::MmapSource(const char *filename) + : mFd(open(filename, O_RDONLY)), + mBase(NULL), + mSize(0) { + LOGV("MmapSource '%s'", filename); + assert(mFd >= 0); + + off_t size = lseek(mFd, 0, SEEK_END); + mSize = (size_t)size; + + mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, 0); + + if (mBase == (void *)-1) { + mBase = NULL; + + close(mFd); + mFd = -1; + } +} + +MmapSource::MmapSource(int fd, int64_t offset, int64_t length) + : mFd(fd), + mBase(NULL), + mSize(length) { + LOGV("MmapSource fd:%d offset:%lld length:%lld", fd, offset, length); + assert(fd >= 0); + + mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, offset); + + if (mBase == (void *)-1) { + mBase = NULL; + + close(mFd); + mFd = -1; + } + +} + +MmapSource::~MmapSource() { + if (mFd != -1) { + munmap(mBase, mSize); + mBase = NULL; + mSize = 0; + + close(mFd); + mFd = -1; + } +} + +status_t MmapSource::InitCheck() const { + return mFd == -1 ? NO_INIT : OK; +} + +ssize_t MmapSource::read_at(off_t offset, void *data, size_t size) { + LOGV("read_at offset:%ld data:%p size:%d", offset, data, size); + assert(offset >= 0); + + size_t avail = 0; + if (offset >= 0 && offset < (off_t)mSize) { + avail = mSize - offset; + } + + if (size > avail) { + size = avail; + } + + memcpy(data, (const uint8_t *)mBase + offset, size); + + return (ssize_t)size; +} + +status_t MmapSource::getSize(off_t *size) { + *size = mSize; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp new file mode 100644 index 000000000000..1bc8a442201b --- /dev/null +++ b/media/libstagefright/OMXClient.cpp @@ -0,0 +1,369 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "OMXClient" +#include <utils/Log.h> + +#include <sys/socket.h> + +#undef NDEBUG +#include <assert.h> + +#include <binder/IServiceManager.h> +#include <media/IMediaPlayerService.h> +#include <media/IOMX.h> +#include <media/stagefright/OMXClient.h> + +namespace android { + +OMXClient::OMXClient() + : mSock(-1) { +} + +OMXClient::~OMXClient() { + disconnect(); +} + +status_t OMXClient::connect() { + Mutex::Autolock autoLock(mLock); + + if (mSock >= 0) { + return UNKNOWN_ERROR; + } + + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + + assert(service.get() != NULL); + + mOMX = service->createOMX(); + assert(mOMX.get() != NULL); + +#if IOMX_USES_SOCKETS + status_t result = mOMX->connect(&mSock); + if (result != OK) { + mSock = -1; + + mOMX = NULL; + return result; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +#else + mReflector = new OMXClientReflector(this); +#endif + + return OK; +} + +void OMXClient::disconnect() { + { + Mutex::Autolock autoLock(mLock); + + if (mSock < 0) { + return; + } + + assert(mObservers.isEmpty()); + } + +#if IOMX_USES_SOCKETS + omx_message msg; + msg.type = omx_message::DISCONNECT; + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); + + void *dummy; + pthread_join(mThread, &dummy); +#else + mReflector->reset(); + mReflector.clear(); +#endif +} + +#if IOMX_USES_SOCKETS +// static +void *OMXClient::ThreadWrapper(void *me) { + ((OMXClient *)me)->threadEntry(); + + return NULL; +} + +void OMXClient::threadEntry() { + bool done = false; + while (!done) { + omx_message msg; + ssize_t n = recv(mSock, &msg, sizeof(msg), 0); + + if (n <= 0) { + break; + } + + done = onOMXMessage(msg); + } + + Mutex::Autolock autoLock(mLock); + close(mSock); + mSock = -1; +} +#endif + +status_t OMXClient::fillBuffer(IOMX::node_id node, IOMX::buffer_id buffer) { +#if !IOMX_USES_SOCKETS + mOMX->fill_buffer(node, buffer); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + omx_message msg; + msg.type = omx_message::FILL_BUFFER; + msg.u.buffer_data.node = node; + msg.u.buffer_data.buffer = buffer; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::emptyBuffer( + IOMX::node_id node, IOMX::buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { +#if !IOMX_USES_SOCKETS + mOMX->empty_buffer( + node, buffer, range_offset, range_length, flags, timestamp); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + // XXX I don't like all this copying... + + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER; + msg.u.extended_buffer_data.node = node; + msg.u.extended_buffer_data.buffer = buffer; + msg.u.extended_buffer_data.range_offset = range_offset; + msg.u.extended_buffer_data.range_length = range_length; + msg.u.extended_buffer_data.flags = flags; + msg.u.extended_buffer_data.timestamp = timestamp; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::send_command( + IOMX::node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { +#if !IOMX_USES_SOCKETS + return mOMX->send_command(node, cmd, param); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + omx_message msg; + msg.type = omx_message::SEND_COMMAND; + msg.u.send_command_data.node = node; + msg.u.send_command_data.cmd = cmd; + msg.u.send_command_data.param = param; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::registerObserver( + IOMX::node_id node, OMXObserver *observer) { + Mutex::Autolock autoLock(&mLock); + + ssize_t index = mObservers.indexOfKey(node); + if (index >= 0) { + return UNKNOWN_ERROR; + } + + mObservers.add(node, observer); + observer->start(); + +#if !IOMX_USES_SOCKETS + mOMX->observe_node(node, mReflector); +#endif + + return OK; +} + +void OMXClient::unregisterObserver(IOMX::node_id node) { + Mutex::Autolock autoLock(mLock); + + ssize_t index = mObservers.indexOfKey(node); + assert(index >= 0); + + if (index < 0) { + return; + } + + OMXObserver *observer = mObservers.valueAt(index); + observer->stop(); + mObservers.removeItemsAt(index); +} + +bool OMXClient::onOMXMessage(const omx_message &msg) { + bool done = false; + + switch (msg.type) { + case omx_message::EVENT: + { + LOGV("OnEvent node:%p event:%d data1:%ld data2:%ld", + msg.u.event_data.node, + msg.u.event_data.event, + msg.u.event_data.data1, + msg.u.event_data.data2); + + break; + } + + case omx_message::FILL_BUFFER_DONE: + { + LOGV("FillBufferDone %p", msg.u.extended_buffer_data.buffer); + break; + } + + case omx_message::EMPTY_BUFFER_DONE: + { + LOGV("EmptyBufferDone %p", msg.u.buffer_data.buffer); + break; + } + +#if IOMX_USES_SOCKETS + case omx_message::DISCONNECTED: + { + LOGV("Disconnected"); + done = true; + break; + } +#endif + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } + + Mutex::Autolock autoLock(mLock); + ssize_t index = mObservers.indexOfKey(msg.u.buffer_data.node); + + if (index >= 0) { + mObservers.editValueAt(index)->postMessage(msg); + } + + return done; +} + +//////////////////////////////////////////////////////////////////////////////// + +OMXObserver::OMXObserver() { +} + +OMXObserver::~OMXObserver() { +} + +void OMXObserver::start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +} + +void OMXObserver::stop() { + omx_message msg; + msg.type = omx_message::QUIT_OBSERVER; + postMessage(msg); + + void *dummy; + pthread_join(mThread, &dummy); +} + +void OMXObserver::postMessage(const omx_message &msg) { + Mutex::Autolock autoLock(mLock); + mQueue.push_back(msg); + mQueueNotEmpty.signal(); +} + +// static +void *OMXObserver::ThreadWrapper(void *me) { + static_cast<OMXObserver *>(me)->threadEntry(); + + return NULL; +} + +void OMXObserver::threadEntry() { + for (;;) { + omx_message msg; + + { + Mutex::Autolock autoLock(mLock); + while (mQueue.empty()) { + mQueueNotEmpty.wait(mLock); + } + + msg = *mQueue.begin(); + mQueue.erase(mQueue.begin()); + } + + if (msg.type == omx_message::QUIT_OBSERVER) { + break; + } + + onOMXMessage(msg); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +OMXClientReflector::OMXClientReflector(OMXClient *client) + : mClient(client) { +} + +void OMXClientReflector::on_message(const omx_message &msg) { + if (mClient != NULL) { + mClient->onOMXMessage(msg); + } +} + +void OMXClientReflector::reset() { + mClient = NULL; +} + +} // namespace android diff --git a/media/libstagefright/OMXDecoder.cpp b/media/libstagefright/OMXDecoder.cpp new file mode 100644 index 000000000000..c059a9df624a --- /dev/null +++ b/media/libstagefright/OMXDecoder.cpp @@ -0,0 +1,1329 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "OMXDecoder" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <media/stagefright/ESDS.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXDecoder.h> + +namespace android { + +class OMXMediaBuffer : public MediaBuffer { +public: + OMXMediaBuffer(IOMX::buffer_id buffer_id, const sp<IMemory> &mem) + : MediaBuffer(mem->pointer(), + mem->size()), + mBufferID(buffer_id), + mMem(mem) { + } + + IOMX::buffer_id buffer_id() const { return mBufferID; } + +private: + IOMX::buffer_id mBufferID; + sp<IMemory> mMem; + + OMXMediaBuffer(const OMXMediaBuffer &); + OMXMediaBuffer &operator=(const OMXMediaBuffer &); +}; + +struct CodecInfo { + const char *mime; + const char *codec; +}; + +static const CodecInfo kDecoderInfo[] = { + { "audio/mpeg", "OMX.PV.mp3dec" }, + { "audio/3gpp", "OMX.PV.amrdec" }, + { "audio/mp4a-latm", "OMX.PV.aacdec" }, + { "video/mp4v-es", "OMX.qcom.video.decoder.mpeg4" }, + { "video/mp4v-es", "OMX.PV.mpeg4dec" }, + { "video/3gpp", "OMX.qcom.video.decoder.h263" }, + { "video/3gpp", "OMX.PV.h263dec" }, + { "video/avc", "OMX.qcom.video.decoder.avc" }, + { "video/avc", "OMX.PV.avcdec" }, +}; + +static const CodecInfo kEncoderInfo[] = { + { "audio/3gpp", "OMX.PV.amrencnb" }, + { "audio/mp4a-latm", "OMX.PV.aacenc" }, + { "video/mp4v-es", "OMX.qcom.video.encoder.mpeg4" }, + { "video/mp4v-es", "OMX.PV.mpeg4enc" }, + { "video/3gpp", "OMX.qcom.video.encoder.h263" }, + { "video/3gpp", "OMX.PV.h263enc" }, + { "video/avc", "OMX.PV.avcenc" }, +}; + +static const char *GetCodec(const CodecInfo *info, size_t numInfos, + const char *mime, int index) { + assert(index >= 0); + for(size_t i = 0; i < numInfos; ++i) { + if (!strcasecmp(mime, info[i].mime)) { + if (index == 0) { + return info[i].codec; + } + + --index; + } + } + + return NULL; +} + +// static +OMXDecoder *OMXDecoder::Create(OMXClient *client, const sp<MetaData> &meta) { + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + sp<IOMX> omx = client->interface(); + + const char *codec = NULL; + IOMX::node_id node = 0; + for (int index = 0;; ++index) { + codec = GetCodec( + kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]), + mime, index); + + if (!codec) { + return NULL; + } + + LOGI("Attempting to allocate OMX node '%s'", codec); + + status_t err = omx->allocate_node(codec, &node); + if (err == OK) { + break; + } + } + + OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec); + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + assert(esds.InitCheck() == OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + printf("found codec specific data of size %d\n", + codec_specific_data_size); + + decoder->addCodecSpecificData( + codec_specific_data, codec_specific_data_size); + } else if (meta->findData(kKeyAVCC, &type, &data, &size)) { + printf("found avcc of size %d\n", size); + + const uint8_t *ptr = (const uint8_t *)data + 6; + size -= 6; + while (size >= 2) { + size_t length = ptr[0] << 8 | ptr[1]; + + ptr += 2; + size -= 2; + + // printf("length = %d, size = %d\n", length, size); + + assert(size >= length); + + decoder->addCodecSpecificData(ptr, length); + + ptr += length; + size -= length; + + if (size <= 1) { + break; + } + + ptr++; // XXX skip trailing 0x01 byte??? + --size; + } + } + + return decoder; +} + +// static +OMXDecoder *OMXDecoder::CreateEncoder( + OMXClient *client, const sp<MetaData> &meta) { + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + sp<IOMX> omx = client->interface(); + + const char *codec = NULL; + IOMX::node_id node = 0; + for (int index = 0;; ++index) { + codec = GetCodec( + kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), + mime, index); + + if (!codec) { + return NULL; + } + + LOGI("Attempting to allocate OMX node '%s'", codec); + + status_t err = omx->allocate_node(codec, &node); + if (err == OK) { + break; + } + } + + OMXDecoder *encoder = new OMXDecoder(client, node, mime, codec); + + return encoder; +} + +OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node, + const char *mime, const char *codec) + : mClient(client), + mOMX(mClient->interface()), + mNode(node), + mComponentName(strdup(codec)), + mIsMP3(!strcasecmp(mime, "audio/mpeg")), + mSource(NULL), + mCodecSpecificDataIterator(mCodecSpecificData.begin()), + mState(OMX_StateLoaded), + mPortStatusMask(kPortStatusActive << 2 | kPortStatusActive), + mShutdownInitiated(false), + mDealer(new MemoryDealer(3 * 1024 * 1024)), + mSeeking(false), + mStarted(false), + mErrorCondition(OK), + mReachedEndOfInput(false) { + mClient->registerObserver(mNode, this); + + mBuffers.push(); // input buffers + mBuffers.push(); // output buffers +} + +OMXDecoder::~OMXDecoder() { + if (mStarted) { + stop(); + } + + for (List<CodecSpecificData>::iterator it = mCodecSpecificData.begin(); + it != mCodecSpecificData.end(); ++it) { + free((*it).data); + } + mCodecSpecificData.clear(); + + mClient->unregisterObserver(mNode); + + status_t err = mOMX->free_node(mNode); + assert(err == OK); + mNode = 0; + + free(mComponentName); + mComponentName = NULL; +} + +void OMXDecoder::setSource(MediaSource *source) { + Mutex::Autolock autoLock(mLock); + + assert(mSource == NULL); + + mSource = source; + setup(); +} + +status_t OMXDecoder::start(MetaData *) { + assert(!mStarted); + + // mDealer->dump("Decoder Dealer"); + + sp<MetaData> params = new MetaData; + if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) { + params->setInt32(kKeyNeedsNALFraming, true); + } + + status_t err = mSource->start(params.get()); + + if (err != OK) { + return err; + } + + postStart(); + + mStarted = true; + + return OK; +} + +status_t OMXDecoder::stop() { + assert(mStarted); + + LOGI("Initiating OMX Node shutdown, busy polling."); + initiateShutdown(); + + // Important: initiateShutdown must be called first, _then_ release + // buffers we're holding onto. + while (!mOutputBuffers.empty()) { + MediaBuffer *buffer = *mOutputBuffers.begin(); + mOutputBuffers.erase(mOutputBuffers.begin()); + + LOGV("releasing buffer %p.", buffer->data()); + + buffer->release(); + buffer = NULL; + } + + int attempt = 1; + while (mState != OMX_StateLoaded && attempt < 10) { + usleep(100000); + + ++attempt; + } + + if (mState != OMX_StateLoaded) { + LOGE("!!! OMX Node '%s' did NOT shutdown cleanly !!!", mComponentName); + } else { + LOGI("OMX Node '%s' has shutdown cleanly.", mComponentName); + } + + mSource->stop(); + + mCodecSpecificDataIterator = mCodecSpecificData.begin(); + mShutdownInitiated = false; + mSeeking = false; + mStarted = false; + mErrorCondition = OK; + mReachedEndOfInput = false; + + return OK; +} + +sp<MetaData> OMXDecoder::getFormat() { + return mOutputFormat; +} + +status_t OMXDecoder::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + Mutex::Autolock autoLock(mLock); + + if (mErrorCondition != OK && mErrorCondition != ERROR_END_OF_STREAM) { + // Errors are sticky. + return mErrorCondition; + } + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + LOGI("[%s] seeking to %lld us", mComponentName, seekTimeUs); + + mErrorCondition = OK; + mReachedEndOfInput = false; + + setPortStatus(kPortIndexInput, kPortStatusFlushing); + setPortStatus(kPortIndexOutput, kPortStatusFlushing); + + mSeeking = true; + mSeekTimeUs = seekTimeUs; + + while (!mOutputBuffers.empty()) { + OMXMediaBuffer *buffer = + static_cast<OMXMediaBuffer *>(*mOutputBuffers.begin()); + + // We could have used buffer->release() instead, but we're + // holding the lock and signalBufferReturned attempts to acquire + // the lock. + buffer->claim(); + mBuffers.editItemAt( + kPortIndexOutput).push_back(buffer->buffer_id()); + buffer = NULL; + + mOutputBuffers.erase(mOutputBuffers.begin()); + } + + status_t err = mOMX->send_command(mNode, OMX_CommandFlush, -1); + assert(err == OK); + + // Once flushing is completed buffers will again be scheduled to be + // filled/emptied. + } + + while (mErrorCondition == OK && mOutputBuffers.empty()) { + mOutputBufferAvailable.wait(mLock); + } + + if (!mOutputBuffers.empty()) { + MediaBuffer *buffer = *mOutputBuffers.begin(); + mOutputBuffers.erase(mOutputBuffers.begin()); + + *out = buffer; + + return OK; + } else { + assert(mErrorCondition != OK); + return mErrorCondition; + } +} + +void OMXDecoder::addCodecSpecificData(const void *data, size_t size) { + CodecSpecificData specific; + specific.data = malloc(size); + memcpy(specific.data, data, size); + specific.size = size; + + mCodecSpecificData.push_back(specific); + mCodecSpecificDataIterator = mCodecSpecificData.begin(); +} + +void OMXDecoder::onOMXMessage(const omx_message &msg) { + Mutex::Autolock autoLock(mLock); + + switch (msg.type) { + case omx_message::START: + { + onStart(); + break; + } + + case omx_message::EVENT: + { + onEvent(msg.u.event_data.event, msg.u.event_data.data1, + msg.u.event_data.data2); + break; + } + + case omx_message::EMPTY_BUFFER_DONE: + { + onEmptyBufferDone(msg.u.buffer_data.buffer); + break; + } + + case omx_message::FILL_BUFFER_DONE: + case omx_message::INITIAL_FILL_BUFFER: + { + onFillBufferDone(msg); + break; + } + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } +} + +void OMXDecoder::setAMRFormat() { + OMX_AUDIO_PARAM_AMRTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexInput; + + status_t err = + mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + + assert(err == NO_ERROR); + + def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + def.eAMRBandMode = OMX_AUDIO_AMRBandModeNB0; + + err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setAACFormat() { + OMX_AUDIO_PARAM_AACPROFILETYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexInput; + + status_t err = + mOMX->get_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def)); + assert(err == NO_ERROR); + + def.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; + + err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setVideoOutputFormat(OMX_U32 width, OMX_U32 height) { + LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height); + + OMX_PARAM_PORTDEFINITIONTYPE def; + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + bool is_encoder = strstr(mComponentName, ".encoder.") != NULL; // XXX + + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = is_encoder ? kPortIndexOutput : kPortIndexInput; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + assert(err == NO_ERROR); + +#if 1 + // XXX Need a (much) better heuristic to compute input buffer sizes. + const size_t X = 64 * 1024; + if (def.nBufferSize < X) { + def.nBufferSize = X; + } +#endif + + assert(def.eDomain == OMX_PortDomainVideo); + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + // video_def.eCompressionFormat = OMX_VIDEO_CodingAVC; + video_def->eColorFormat = OMX_COLOR_FormatUnused; + + err = mOMX->set_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + //////////////////////////////////////////////////////////////////////////// + + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = is_encoder ? kPortIndexInput : kPortIndexOutput; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + assert(def.eDomain == OMX_PortDomainVideo); + + def.nBufferSize = + (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2; // YUV420 + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + video_def->nStride = width; + // video_def->nSliceHeight = height; + video_def->eCompressionFormat = OMX_VIDEO_CodingUnused; +// video_def->eColorFormat = OMX_COLOR_FormatYUV420Planar; + + err = mOMX->set_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setup() { + const sp<MetaData> &meta = mSource->getFormat(); + + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + if (!strcasecmp(mime, "audio/3gpp")) { + setAMRFormat(); + } else if (!strcasecmp(mime, "audio/mp4a-latm")) { + setAACFormat(); + } else if (!strncasecmp(mime, "video/", 6)) { + int32_t width, height; + bool success = meta->findInt32(kKeyWidth, &width); + success = success && meta->findInt32(kKeyHeight, &height); + assert(success); + + setVideoOutputFormat(width, height); + } + + // dumpPortDefinition(0); + // dumpPortDefinition(1); + + mOutputFormat = new MetaData; + mOutputFormat->setCString(kKeyDecoderComponent, mComponentName); + + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + switch (def.eDomain) { + case OMX_PortDomainAudio: + { + OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio; + + assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM); + + OMX_AUDIO_PARAM_PCMMODETYPE params; + params.nSize = sizeof(params); + params.nVersion.s.nVersionMajor = 1; + params.nVersion.s.nVersionMinor = 1; + params.nPortIndex = kPortIndexOutput; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); + assert(err == OK); + + assert(params.eNumData == OMX_NumericalDataSigned); + assert(params.nBitPerSample == 16); + assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear); + + int32_t numChannels, sampleRate; + meta->findInt32(kKeyChannelCount, &numChannels); + meta->findInt32(kKeySampleRate, &sampleRate); + + mOutputFormat->setCString(kKeyMIMEType, "audio/raw"); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + break; + } + + case OMX_PortDomainVideo: + { + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + if (video_def->eCompressionFormat == OMX_VIDEO_CodingUnused) { + mOutputFormat->setCString(kKeyMIMEType, "video/raw"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingMPEG4) { + mOutputFormat->setCString(kKeyMIMEType, "video/mp4v-es"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingH263) { + mOutputFormat->setCString(kKeyMIMEType, "video/3gpp"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingAVC) { + mOutputFormat->setCString(kKeyMIMEType, "video/avc"); + } else { + assert(!"Unknown compression format."); + } + + if (!strcmp(mComponentName, "OMX.PV.avcdec")) { + // This component appears to be lying to me. + mOutputFormat->setInt32( + kKeyWidth, (video_def->nFrameWidth + 15) & -16); + mOutputFormat->setInt32( + kKeyHeight, (video_def->nFrameHeight + 15) & -16); + } else { + mOutputFormat->setInt32(kKeyWidth, video_def->nFrameWidth); + mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight); + } + + mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat); + break; + } + + default: + { + assert(!"should not be here, neither audio nor video."); + break; + } + } +} + +void OMXDecoder::onStart() { + bool needs_qcom_hack = + !strncmp(mComponentName, "OMX.qcom.video.", 15); + + if (!needs_qcom_hack) { + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + } + + allocateBuffers(kPortIndexInput); + allocateBuffers(kPortIndexOutput); + + if (needs_qcom_hack) { + // XXX this should happen before AllocateBuffers, but qcom's + // h264 vdec disagrees. + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + } +} + +void OMXDecoder::allocateBuffers(OMX_U32 port_index) { + assert(mBuffers[port_index].empty()); + + OMX_U32 num_buffers; + OMX_U32 buffer_size; + + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nVersion.s.nRevision = 0; + def.nVersion.s.nStep = 0; + def.nPortIndex = port_index; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + num_buffers = def.nBufferCountActual; + buffer_size = def.nBufferSize; + + LOGV("[%s] port %ld: allocating %ld buffers of size %ld each\n", + mComponentName, port_index, num_buffers, buffer_size); + + for (OMX_U32 i = 0; i < num_buffers; ++i) { + sp<IMemory> mem = mDealer->allocate(buffer_size); + assert(mem.get() != NULL); + + IOMX::buffer_id buffer; + status_t err; + + if (port_index == kPortIndexInput + && !strncmp(mComponentName, "OMX.qcom.video.encoder.", 23)) { + // qcom's H.263 encoder appears to want to allocate its own input + // buffers. + err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer); + if (err != OK) { + LOGE("[%s] allocate_buffer_with_backup failed with error %d", + mComponentName, err); + } + } else if (port_index == kPortIndexOutput + && !strncmp(mComponentName, "OMX.qcom.video.decoder.", 23)) { +#if 1 + err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer); +#else + // XXX This is fine as long as we are either running the player + // inside the media server process or we are using the + // QComHardwareRenderer to output the frames. + err = mOMX->allocate_buffer(mNode, port_index, buffer_size, &buffer); +#endif + if (err != OK) { + LOGE("[%s] allocate_buffer_with_backup failed with error %d", + mComponentName, err); + } + } else { + err = mOMX->use_buffer(mNode, port_index, mem, &buffer); + if (err != OK) { + LOGE("[%s] use_buffer failed with error %d", + mComponentName, err); + } + } + assert(err == OK); + + LOGV("allocated %s buffer %p.", + port_index == kPortIndexInput ? "INPUT" : "OUTPUT", + buffer); + + mBuffers.editItemAt(port_index).push_back(buffer); + mBufferMap.add(buffer, mem); + + if (port_index == kPortIndexOutput) { + OMXMediaBuffer *media_buffer = new OMXMediaBuffer(buffer, mem); + media_buffer->setObserver(this); + + mMediaBufferMap.add(buffer, media_buffer); + } + } + + LOGV("allocate %s buffers done.", + port_index == kPortIndexInput ? "INPUT" : "OUTPUT"); +} + +void OMXDecoder::onEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + LOGV("[%s] onEvent event=%d, data1=%ld, data2=%ld", + mComponentName, event, data1, data2); + + switch (event) { + case OMX_EventCmdComplete: { + onEventCmdComplete( + static_cast<OMX_COMMANDTYPE>(data1), data2); + + break; + } + + case OMX_EventPortSettingsChanged: { + onEventPortSettingsChanged(data1); + break; + } + + case OMX_EventBufferFlag: { + // initiateShutdown(); + break; + } + + default: + break; + } +} + +void OMXDecoder::onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data) { + switch (type) { + case OMX_CommandStateSet: { + OMX_STATETYPE state = static_cast<OMX_STATETYPE>(data); + onStateChanged(state); + break; + } + + case OMX_CommandPortDisable: { + OMX_U32 port_index = data; + assert(getPortStatus(port_index) == kPortStatusDisabled); + + status_t err = + mOMX->send_command(mNode, OMX_CommandPortEnable, port_index); + + allocateBuffers(port_index); + + break; + } + + case OMX_CommandPortEnable: { + OMX_U32 port_index = data; + assert(getPortStatus(port_index) ==kPortStatusDisabled); + setPortStatus(port_index, kPortStatusActive); + + assert(port_index == kPortIndexOutput); + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + while (!obuffers->empty()) { + IOMX::buffer_id buffer = *obuffers->begin(); + obuffers->erase(obuffers->begin()); + + status_t err = mClient->fillBuffer(mNode, buffer); + assert(err == NO_ERROR); + } + + break; + } + + case OMX_CommandFlush: { + OMX_U32 port_index = data; + LOGV("Port %ld flush complete.", port_index); + assert(getPortStatus(port_index) == kPortStatusFlushing); + + setPortStatus(port_index, kPortStatusActive); + BufferList *buffers = &mBuffers.editItemAt(port_index); + while (!buffers->empty()) { + IOMX::buffer_id buffer = *buffers->begin(); + buffers->erase(buffers->begin()); + + if (port_index == kPortIndexInput) { + postEmptyBufferDone(buffer); + } else { + postInitialFillBuffer(buffer); + } + } + break; + } + + default: + break; + } +} + +void OMXDecoder::onEventPortSettingsChanged(OMX_U32 port_index) { + assert(getPortStatus(port_index) == kPortStatusActive); + setPortStatus(port_index, kPortStatusDisabled); + + status_t err = + mOMX->send_command(mNode, OMX_CommandPortDisable, port_index); + assert(err == NO_ERROR); +} + +void OMXDecoder::onStateChanged(OMX_STATETYPE to) { + if (mState == OMX_StateLoaded) { + assert(to == OMX_StateIdle); + + mState = to; + + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateExecuting); + assert(err == NO_ERROR); + } else if (mState == OMX_StateIdle) { + if (to == OMX_StateExecuting) { + mState = to; + + BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput); + while (!ibuffers->empty()) { + IOMX::buffer_id buffer = *ibuffers->begin(); + ibuffers->erase(ibuffers->begin()); + + postEmptyBufferDone(buffer); + } + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + while (!obuffers->empty()) { + IOMX::buffer_id buffer = *obuffers->begin(); + obuffers->erase(obuffers->begin()); + + postInitialFillBuffer(buffer); + } + } else { + assert(to == OMX_StateLoaded); + + mState = to; + + setPortStatus(kPortIndexInput, kPortStatusActive); + setPortStatus(kPortIndexOutput, kPortStatusActive); + } + } else if (mState == OMX_StateExecuting) { + assert(to == OMX_StateIdle); + + mState = to; + + LOGV("Executing->Idle complete, initiating Idle->Loaded"); + status_t err = + mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateLoaded); + assert(err == NO_ERROR); + + BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput); + for (BufferList::iterator it = ibuffers->begin(); + it != ibuffers->end(); ++it) { + freeInputBuffer(*it); + } + ibuffers->clear(); + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + for (BufferList::iterator it = obuffers->begin(); + it != obuffers->end(); ++it) { + freeOutputBuffer(*it); + } + obuffers->clear(); + } +} + +void OMXDecoder::initiateShutdown() { + Mutex::Autolock autoLock(mLock); + + if (mShutdownInitiated) { + return; + } + + if (mState == OMX_StateLoaded) { + return; + } + + assert(mState == OMX_StateExecuting); + + mShutdownInitiated = true; + + status_t err = + mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + + setPortStatus(kPortIndexInput, kPortStatusShutdown); + setPortStatus(kPortIndexOutput, kPortStatusShutdown); +} + +void OMXDecoder::setPortStatus(OMX_U32 port_index, PortStatus status) { + int shift = 2 * port_index; + + mPortStatusMask &= ~(3 << shift); + mPortStatusMask |= status << shift; +} + +OMXDecoder::PortStatus OMXDecoder::getPortStatus( + OMX_U32 port_index) const { + int shift = 2 * port_index; + + return static_cast<PortStatus>((mPortStatusMask >> shift) & 3); +} + +void OMXDecoder::onEmptyBufferDone(IOMX::buffer_id buffer) { + LOGV("[%s] onEmptyBufferDone (%p)", mComponentName, buffer); + + status_t err; + switch (getPortStatus(kPortIndexInput)) { + case kPortStatusDisabled: + freeInputBuffer(buffer); + err = NO_ERROR; + break; + + case kPortStatusShutdown: + LOGV("We're shutting down, enqueue INPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + err = NO_ERROR; + break; + + case kPortStatusFlushing: + LOGV("We're currently flushing, enqueue INPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + err = NO_ERROR; + break; + + default: + onRealEmptyBufferDone(buffer); + err = NO_ERROR; + break; + } + assert(err == NO_ERROR); +} + +void OMXDecoder::onFillBufferDone(const omx_message &msg) { + IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer; + + LOGV("[%s] onFillBufferDone (%p)", mComponentName, buffer); + + status_t err; + switch (getPortStatus(kPortIndexOutput)) { + case kPortStatusDisabled: + freeOutputBuffer(buffer); + err = NO_ERROR; + break; + case kPortStatusShutdown: + LOGV("We're shutting down, enqueue OUTPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + err = NO_ERROR; + break; + + case kPortStatusFlushing: + LOGV("We're currently flushing, enqueue OUTPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + err = NO_ERROR; + break; + + default: + { + if (msg.type == omx_message::INITIAL_FILL_BUFFER) { + err = mClient->fillBuffer(mNode, buffer); + } else { + LOGV("[%s] Filled OUTPUT buffer %p, flags=0x%08lx.", + mComponentName, buffer, msg.u.extended_buffer_data.flags); + + onRealFillBufferDone(msg); + err = NO_ERROR; + } + break; + } + } + assert(err == NO_ERROR); +} + +void OMXDecoder::onRealEmptyBufferDone(IOMX::buffer_id buffer) { + if (mReachedEndOfInput) { + // We already sent the EOS notification. + + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + return; + } + + const sp<IMemory> mem = mBufferMap.valueFor(buffer); + assert(mem.get() != NULL); + + static const uint8_t kNALStartCode[4] = { 0x00, 0x00, 0x00, 0x01 }; + + if (mCodecSpecificDataIterator != mCodecSpecificData.end()) { + List<CodecSpecificData>::iterator it = mCodecSpecificDataIterator; + + size_t range_length = 0; + + if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) { + assert((*mCodecSpecificDataIterator).size + 4 <= mem->size()); + + memcpy(mem->pointer(), kNALStartCode, 4); + + memcpy((uint8_t *)mem->pointer() + 4, (*it).data, (*it).size); + range_length = (*it).size + 4; + } else { + assert((*mCodecSpecificDataIterator).size <= mem->size()); + + memcpy((uint8_t *)mem->pointer(), (*it).data, (*it).size); + range_length = (*it).size; + } + + ++mCodecSpecificDataIterator; + + status_t err = mClient->emptyBuffer( + mNode, buffer, 0, range_length, + OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, + 0); + + assert(err == NO_ERROR); + + return; + } + + LOGV("[%s] waiting for input data", mComponentName); + + MediaBuffer *input_buffer; + for (;;) { + status_t err; + + if (mSeeking) { + MediaSource::ReadOptions options; + options.setSeekTo(mSeekTimeUs); + + mSeeking = false; + + err = mSource->read(&input_buffer, &options); + } else { + err = mSource->read(&input_buffer); + } + assert((err == OK && input_buffer != NULL) + || (err != OK && input_buffer == NULL)); + + if (err == ERROR_END_OF_STREAM) { + LOGE("[%s] Reached end of stream.", mComponentName); + mReachedEndOfInput = true; + } else { + LOGV("[%s] got input data", mComponentName); + } + + if (err != OK) { + status_t err2 = mClient->emptyBuffer( + mNode, buffer, 0, 0, OMX_BUFFERFLAG_EOS, 0); + + assert(err2 == NO_ERROR); + return; + } + + if (mSeeking) { + input_buffer->release(); + input_buffer = NULL; + + continue; + } + + break; + } + + const uint8_t *src_data = + (const uint8_t *)input_buffer->data() + input_buffer->range_offset(); + + size_t src_length = input_buffer->range_length(); + if (src_length == 195840) { + // When feeding the output of the AVC decoder into the H263 encoder, + // buffer sizes mismatch if width % 16 != 0 || height % 16 != 0. + src_length = 194400; // XXX HACK + } else if (src_length == 115200) { + src_length = 114240; // XXX HACK + } + + if (src_length > mem->size()) { + LOGE("src_length=%d > mem->size() = %d\n", + src_length, mem->size()); + } + + assert(src_length <= mem->size()); + memcpy(mem->pointer(), src_data, src_length); + + OMX_U32 flags = 0; + if (!mIsMP3) { + // Only mp3 audio data may be streamed, all other data is assumed + // to be fed into the decoder at frame boundaries. + flags |= OMX_BUFFERFLAG_ENDOFFRAME; + } + + int32_t units, scale; + bool success = + input_buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + + success = success && + input_buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + + OMX_TICKS timestamp = 0; + + if (success) { + // XXX units should be microseconds but PV treats them as milliseconds. + timestamp = ((OMX_S64)units * 1000) / scale; + } + + input_buffer->release(); + input_buffer = NULL; + + LOGV("[%s] Calling EmptyBuffer on buffer %p", + mComponentName, buffer); + + status_t err2 = mClient->emptyBuffer( + mNode, buffer, 0, src_length, flags, timestamp); + assert(err2 == OK); +} + +void OMXDecoder::onRealFillBufferDone(const omx_message &msg) { + OMXMediaBuffer *media_buffer = + mMediaBufferMap.valueFor(msg.u.extended_buffer_data.buffer); + + media_buffer->set_range( + msg.u.extended_buffer_data.range_offset, + msg.u.extended_buffer_data.range_length); + + media_buffer->add_ref(); + + media_buffer->meta_data()->clear(); + + media_buffer->meta_data()->setInt32( + kKeyTimeUnits, msg.u.extended_buffer_data.timestamp); + media_buffer->meta_data()->setInt32(kKeyTimeScale, 1000); + + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) { + media_buffer->meta_data()->setInt32(kKeyIsSyncFrame, true); + } + + media_buffer->meta_data()->setPointer( + kKeyPlatformPrivate, + msg.u.extended_buffer_data.platform_private); + + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) { + mErrorCondition = ERROR_END_OF_STREAM; + } + + mOutputBuffers.push_back(media_buffer); + mOutputBufferAvailable.signal(); +} + +void OMXDecoder::signalBufferReturned(MediaBuffer *_buffer) { + Mutex::Autolock autoLock(mLock); + + OMXMediaBuffer *media_buffer = static_cast<OMXMediaBuffer *>(_buffer); + + IOMX::buffer_id buffer = media_buffer->buffer_id(); + + PortStatus outputStatus = getPortStatus(kPortIndexOutput); + if (outputStatus == kPortStatusShutdown + || outputStatus == kPortStatusFlushing) { + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + } else { + LOGV("[%s] Calling FillBuffer on buffer %p.", mComponentName, buffer); + + status_t err = mClient->fillBuffer(mNode, buffer); + assert(err == NO_ERROR); + } +} + +void OMXDecoder::freeInputBuffer(IOMX::buffer_id buffer) { + LOGV("freeInputBuffer %p", buffer); + + status_t err = mOMX->free_buffer(mNode, kPortIndexInput, buffer); + assert(err == NO_ERROR); + mBufferMap.removeItem(buffer); + + LOGV("freeInputBuffer %p done", buffer); +} + +void OMXDecoder::freeOutputBuffer(IOMX::buffer_id buffer) { + LOGV("freeOutputBuffer %p", buffer); + + status_t err = mOMX->free_buffer(mNode, kPortIndexOutput, buffer); + assert(err == NO_ERROR); + mBufferMap.removeItem(buffer); + + ssize_t index = mMediaBufferMap.indexOfKey(buffer); + assert(index >= 0); + MediaBuffer *mbuffer = mMediaBufferMap.editValueAt(index); + mMediaBufferMap.removeItemsAt(index); + mbuffer->setObserver(NULL); + mbuffer->release(); + mbuffer = NULL; + + LOGV("freeOutputBuffer %p done", buffer); +} + +void OMXDecoder::dumpPortDefinition(OMX_U32 port_index) { + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = port_index; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + LOGI("DumpPortDefinition on port %ld", port_index); + LOGI("nBufferCountActual = %ld, nBufferCountMin = %ld, nBufferSize = %ld", + def.nBufferCountActual, def.nBufferCountMin, def.nBufferSize); + switch (def.eDomain) { + case OMX_PortDomainAudio: + { + LOGI("eDomain = AUDIO"); + + if (port_index == kPortIndexOutput) { + OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio; + assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM); + + OMX_AUDIO_PARAM_PCMMODETYPE params; + params.nSize = sizeof(params); + params.nVersion.s.nVersionMajor = 1; + params.nVersion.s.nVersionMinor = 1; + params.nPortIndex = port_index; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); + assert(err == OK); + + assert(params.nChannels == 1 || params.bInterleaved); + assert(params.eNumData == OMX_NumericalDataSigned); + assert(params.nBitPerSample == 16); + assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear); + + LOGI("nChannels = %ld, nSamplingRate = %ld", + params.nChannels, params.nSamplingRate); + } + + break; + } + + case OMX_PortDomainVideo: + { + LOGI("eDomain = VIDEO"); + + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + LOGI("nFrameWidth = %ld, nFrameHeight = %ld, nStride = %ld, " + "nSliceHeight = %ld", + video_def->nFrameWidth, video_def->nFrameHeight, + video_def->nStride, video_def->nSliceHeight); + LOGI("nBitrate = %ld, xFrameRate = %.2f", + video_def->nBitrate, video_def->xFramerate / 65536.0f); + LOGI("eCompressionFormat = %d, eColorFormat = %d", + video_def->eCompressionFormat, video_def->eColorFormat); + + break; + } + + default: + LOGI("eDomain = UNKNOWN"); + break; + } +} + +void OMXDecoder::postStart() { + omx_message msg; + msg.type = omx_message::START; + postMessage(msg); +} + +void OMXDecoder::postEmptyBufferDone(IOMX::buffer_id buffer) { + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER_DONE; + msg.u.buffer_data.node = mNode; + msg.u.buffer_data.buffer = buffer; + postMessage(msg); +} + +void OMXDecoder::postInitialFillBuffer(IOMX::buffer_id buffer) { + omx_message msg; + msg.type = omx_message::INITIAL_FILL_BUFFER; + msg.u.buffer_data.node = mNode; + msg.u.buffer_data.buffer = buffer; + postMessage(msg); +} + +} // namespace android diff --git a/media/libstagefright/QComHardwareRenderer.cpp b/media/libstagefright/QComHardwareRenderer.cpp new file mode 100644 index 000000000000..5a2379272cee --- /dev/null +++ b/media/libstagefright/QComHardwareRenderer.cpp @@ -0,0 +1,139 @@ +/* + * 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <binder/MemoryHeapBase.h> +#include <binder/MemoryHeapPmem.h> +#include <media/stagefright/QComHardwareRenderer.h> +#include <ui/ISurface.h> + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +typedef struct PLATFORM_PRIVATE_ENTRY +{ + /* Entry type */ + uint32_t type; + + /* Pointer to platform specific entry */ + void *entry; + +} PLATFORM_PRIVATE_ENTRY; + +typedef struct PLATFORM_PRIVATE_LIST +{ + /* Number of entries */ + uint32_t nEntries; + + /* Pointer to array of platform specific entries * + * Contiguous block of PLATFORM_PRIVATE_ENTRY elements */ + PLATFORM_PRIVATE_ENTRY *entryList; + +} PLATFORM_PRIVATE_LIST; + +// data structures for tunneling buffers +typedef struct PLATFORM_PRIVATE_PMEM_INFO +{ + /* pmem file descriptor */ + uint32_t pmem_fd; + uint32_t offset; + +} PLATFORM_PRIVATE_PMEM_INFO; + +#define PLATFORM_PRIVATE_PMEM 1 + +QComHardwareRenderer::QComHardwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mISurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight), + mFrameSize((mDecodedWidth * mDecodedHeight * 3) / 2) { + assert(mISurface.get() != NULL); + assert(mDecodedWidth > 0); + assert(mDecodedHeight > 0); +} + +QComHardwareRenderer::~QComHardwareRenderer() { + mISurface->unregisterBuffers(); +} + +void QComHardwareRenderer::render( + const void *data, size_t size, void *platformPrivate) { + size_t offset; + if (!getOffset(platformPrivate, &offset)) { + return; + } + + mISurface->postBuffer(offset); +} + +bool QComHardwareRenderer::getOffset(void *platformPrivate, size_t *offset) { + *offset = 0; + + PLATFORM_PRIVATE_LIST *list = (PLATFORM_PRIVATE_LIST *)platformPrivate; + for (uint32_t i = 0; i < list->nEntries; ++i) { + if (list->entryList[i].type != PLATFORM_PRIVATE_PMEM) { + continue; + } + + PLATFORM_PRIVATE_PMEM_INFO *info = + (PLATFORM_PRIVATE_PMEM_INFO *)list->entryList[i].entry; + + if (info != NULL) { + if (mMemoryHeap.get() == NULL) { + publishBuffers(info->pmem_fd); + } + + if (mMemoryHeap.get() == NULL) { + return false; + } + + *offset = info->offset; + + return true; + } + } + + return false; +} + +void QComHardwareRenderer::publishBuffers(uint32_t pmem_fd) { + sp<MemoryHeapBase> master = + reinterpret_cast<MemoryHeapBase *>(pmem_fd); + + master->setDevice("/dev/pmem"); + + mMemoryHeap = new MemoryHeapPmem(master, 0); + mMemoryHeap->slap(); + + ISurface::BufferHeap bufferHeap( + mDisplayWidth, mDisplayHeight, + mDecodedWidth, mDecodedHeight, + PIXEL_FORMAT_YCbCr_420_SP, + mMemoryHeap); + + status_t err = mISurface->registerBuffers(bufferHeap); + assert(err == OK); +} + +} // namespace android diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp new file mode 100644 index 000000000000..8f1fa67e7781 --- /dev/null +++ b/media/libstagefright/SampleTable.cpp @@ -0,0 +1,598 @@ +/* + * 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. + */ + +#define LOG_TAG "SampleTable" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <assert.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/SampleTable.h> +#include <media/stagefright/Utils.h> + +namespace android { + +static const uint32_t kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o'); +static const uint32_t kChunkOffsetType64 = FOURCC('c', 'o', '6', '4'); +static const uint32_t kSampleSizeType32 = FOURCC('s', 't', 's', 'z'); +static const uint32_t kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2'); + +SampleTable::SampleTable(DataSource *source) + : mDataSource(source), + mChunkOffsetOffset(-1), + mChunkOffsetType(0), + mNumChunkOffsets(0), + mSampleToChunkOffset(-1), + mNumSampleToChunkOffsets(0), + mSampleSizeOffset(-1), + mSampleSizeFieldSize(0), + mDefaultSampleSize(0), + mNumSampleSizes(0), + mTimeToSampleCount(0), + mTimeToSample(NULL), + mSyncSampleOffset(-1), + mNumSyncSamples(0) { +} + +SampleTable::~SampleTable() { + delete[] mTimeToSample; + mTimeToSample = NULL; +} + +status_t SampleTable::setChunkOffsetParams( + uint32_t type, off_t data_offset, off_t data_size) { + if (mChunkOffsetOffset >= 0) { + return ERROR_MALFORMED; + } + + assert(type == kChunkOffsetType32 || type == kChunkOffsetType64); + + mChunkOffsetOffset = data_offset; + mChunkOffsetType = type; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumChunkOffsets = U32_AT(&header[4]); + + if (mChunkOffsetType == kChunkOffsetType32) { + if (data_size < 8 + mNumChunkOffsets * 4) { + return ERROR_MALFORMED; + } + } else { + if (data_size < 8 + mNumChunkOffsets * 8) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setSampleToChunkParams( + off_t data_offset, off_t data_size) { + if (mSampleToChunkOffset >= 0) { + return ERROR_MALFORMED; + } + + mSampleToChunkOffset = data_offset; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumSampleToChunkOffsets = U32_AT(&header[4]); + + if (data_size < 8 + mNumSampleToChunkOffsets * 12) { + return ERROR_MALFORMED; + } + + return OK; +} + +status_t SampleTable::setSampleSizeParams( + uint32_t type, off_t data_offset, off_t data_size) { + if (mSampleSizeOffset >= 0) { + return ERROR_MALFORMED; + } + + assert(type == kSampleSizeType32 || type == kSampleSizeTypeCompact); + + mSampleSizeOffset = data_offset; + + if (data_size < 12) { + return ERROR_MALFORMED; + } + + uint8_t header[12]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mDefaultSampleSize = U32_AT(&header[4]); + mNumSampleSizes = U32_AT(&header[8]); + + if (type == kSampleSizeType32) { + mSampleSizeFieldSize = 32; + + if (mDefaultSampleSize != 0) { + return OK; + } + + if (data_size < 12 + mNumSampleSizes * 4) { + return ERROR_MALFORMED; + } + } else { + if ((mDefaultSampleSize & 0xffffff00) != 0) { + // The high 24 bits are reserved and must be 0. + return ERROR_MALFORMED; + } + + mSampleSizeFieldSize = mDefaultSampleSize & 0xf; + mDefaultSampleSize = 0; + + if (mSampleSizeFieldSize != 4 && mSampleSizeFieldSize != 8 + && mSampleSizeFieldSize != 16) { + return ERROR_MALFORMED; + } + + if (data_size < 12 + (mNumSampleSizes * mSampleSizeFieldSize + 4) / 8) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setTimeToSampleParams( + off_t data_offset, off_t data_size) { + if (mTimeToSample != NULL || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mTimeToSampleCount = U32_AT(&header[4]); + mTimeToSample = new uint32_t[mTimeToSampleCount * 2]; + + size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2; + if (mDataSource->read_at( + data_offset + 8, mTimeToSample, size) < (ssize_t)size) { + return ERROR_IO; + } + + for (uint32_t i = 0; i < mTimeToSampleCount * 2; ++i) { + mTimeToSample[i] = ntohl(mTimeToSample[i]); + } + + return OK; +} + +status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) { + if (mSyncSampleOffset >= 0 || data_size < 8) { + return ERROR_MALFORMED; + } + + mSyncSampleOffset = data_offset; + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumSyncSamples = U32_AT(&header[4]); + + if (mNumSyncSamples < 2) { + LOGW("Table of sync samples is empty or has only a single entry!"); + } + return OK; +} + +uint32_t SampleTable::countChunkOffsets() const { + return mNumChunkOffsets; +} + +status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { + *offset = 0; + + if (mChunkOffsetOffset < 0) { + return ERROR_MALFORMED; + } + + if (chunk_index >= mNumChunkOffsets) { + return ERROR_OUT_OF_RANGE; + } + + if (mChunkOffsetType == kChunkOffsetType32) { + uint32_t offset32; + + if (mDataSource->read_at( + mChunkOffsetOffset + 8 + 4 * chunk_index, + &offset32, + sizeof(offset32)) < (ssize_t)sizeof(offset32)) { + return ERROR_IO; + } + + *offset = ntohl(offset32); + } else { + assert(mChunkOffsetOffset == kChunkOffsetType64); + + uint64_t offset64; + if (mDataSource->read_at( + mChunkOffsetOffset + 8 + 8 * chunk_index, + &offset64, + sizeof(offset64)) < (ssize_t)sizeof(offset64)) { + return ERROR_IO; + } + + *offset = ntoh64(offset64); + } + + return OK; +} + +status_t SampleTable::getChunkForSample( + uint32_t sample_index, + uint32_t *chunk_index, + uint32_t *chunk_relative_sample_index, + uint32_t *desc_index) { + *chunk_index = 0; + *chunk_relative_sample_index = 0; + *desc_index = 0; + + if (mSampleToChunkOffset < 0) { + return ERROR_MALFORMED; + } + + if (sample_index >= countSamples()) { + return ERROR_END_OF_STREAM; + } + + uint32_t first_chunk = 0; + uint32_t samples_per_chunk = 0; + uint32_t chunk_desc_index = 0; + + uint32_t index = 0; + while (index < mNumSampleToChunkOffsets) { + uint8_t buffer[12]; + if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12, + buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint32_t stop_chunk = U32_AT(buffer); + if (sample_index < (stop_chunk - first_chunk) * samples_per_chunk) { + break; + } + + sample_index -= (stop_chunk - first_chunk) * samples_per_chunk; + first_chunk = stop_chunk; + samples_per_chunk = U32_AT(&buffer[4]); + chunk_desc_index = U32_AT(&buffer[8]); + + ++index; + } + + *chunk_index = sample_index / samples_per_chunk + first_chunk - 1; + *chunk_relative_sample_index = sample_index % samples_per_chunk; + *desc_index = chunk_desc_index; + + return OK; +} + +uint32_t SampleTable::countSamples() const { + return mNumSampleSizes; +} + +status_t SampleTable::getSampleSize( + uint32_t sample_index, size_t *sample_size) { + *sample_size = 0; + + if (mSampleSizeOffset < 0) { + return ERROR_MALFORMED; + } + + if (sample_index >= mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + if (mDefaultSampleSize > 0) { + *sample_size = mDefaultSampleSize; + return OK; + } + + switch (mSampleSizeFieldSize) { + case 32: + { + if (mDataSource->read_at( + mSampleSizeOffset + 12 + 4 * sample_index, + sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) { + return ERROR_IO; + } + + *sample_size = ntohl(*sample_size); + break; + } + + case 16: + { + uint16_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + 2 * sample_index, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = ntohs(x); + break; + } + + case 8: + { + uint8_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + sample_index, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = x; + break; + } + + default: + { + assert(mSampleSizeFieldSize == 4); + + uint8_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + sample_index / 2, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = (sample_index & 1) ? x & 0x0f : x >> 4; + break; + } + } + + return OK; +} + +status_t SampleTable::getSampleOffsetAndSize( + uint32_t sample_index, off_t *offset, size_t *size) { + Mutex::Autolock autoLock(mLock); + + *offset = 0; + *size = 0; + + uint32_t chunk_index; + uint32_t chunk_relative_sample_index; + uint32_t desc_index; + status_t err = getChunkForSample( + sample_index, &chunk_index, &chunk_relative_sample_index, + &desc_index); + + if (err != OK) { + return err; + } + + err = getChunkOffset(chunk_index, offset); + + if (err != OK) { + return err; + } + + for (uint32_t j = 0; j < chunk_relative_sample_index; ++j) { + size_t sample_size; + err = getSampleSize(sample_index - j - 1, &sample_size); + + if (err != OK) { + return err; + } + + *offset += sample_size; + } + + err = getSampleSize(sample_index, size); + + if (err != OK) { + return err; + } + + return OK; +} + +status_t SampleTable::getMaxSampleSize(size_t *max_size) { + Mutex::Autolock autoLock(mLock); + + *max_size = 0; + + for (uint32_t i = 0; i < mNumSampleSizes; ++i) { + size_t sample_size; + status_t err = getSampleSize(i, &sample_size); + + if (err != OK) { + return err; + } + + if (sample_size > *max_size) { + *max_size = sample_size; + } + } + + return OK; +} + +status_t SampleTable::getDecodingTime(uint32_t sample_index, uint32_t *time) { + // XXX FIXME idiotic (for the common use-case) O(n) algorithm below... + + Mutex::Autolock autoLock(mLock); + + if (sample_index >= mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + uint32_t cur_sample = 0; + *time = 0; + for (uint32_t i = 0; i < mTimeToSampleCount; ++i) { + uint32_t n = mTimeToSample[2 * i]; + uint32_t delta = mTimeToSample[2 * i + 1]; + + if (sample_index < cur_sample + n) { + *time += delta * (sample_index - cur_sample); + + return OK; + } + + *time += delta * n; + cur_sample += n; + } + + return ERROR_OUT_OF_RANGE; +} + +status_t SampleTable::findClosestSample( + uint32_t req_time, uint32_t *sample_index, uint32_t flags) { + Mutex::Autolock autoLock(mLock); + + uint32_t cur_sample = 0; + uint32_t time = 0; + for (uint32_t i = 0; i < mTimeToSampleCount; ++i) { + uint32_t n = mTimeToSample[2 * i]; + uint32_t delta = mTimeToSample[2 * i + 1]; + + if (req_time < time + n * delta) { + int j = (req_time - time) / delta; + + *sample_index = cur_sample + j; + + if (flags & kSyncSample_Flag) { + return findClosestSyncSample(*sample_index, sample_index); + } + + return OK; + } + + time += delta * n; + cur_sample += n; + } + + return ERROR_OUT_OF_RANGE; +} + +status_t SampleTable::findClosestSyncSample( + uint32_t start_sample_index, uint32_t *sample_index) { + *sample_index = 0; + + if (mSyncSampleOffset < 0) { + // All samples are sync-samples. + *sample_index = start_sample_index; + return OK; + } + + uint32_t x; + uint32_t left = 0; + uint32_t right = mNumSyncSamples; + while (left < right) { + uint32_t mid = (left + right) / 2; + if (mDataSource->read_at( + mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) { + return ERROR_IO; + } + + x = ntohl(x); + + if (x < (start_sample_index + 1)) { + left = mid + 1; + } else if (x > (start_sample_index + 1)) { + right = mid; + } else { + break; + } + } + +#if 1 + // Make sure we return a sample at or _after_ the requested one. + // Seeking to a particular time in a media source containing audio and + // video will most likely be able to sync fairly close to the requested + // time in the audio track but may only be able to seek a fair distance + // from the requested time in the video track. + // If we seek the video track to a time earlier than the audio track, + // we'll cause the video track to be late for quite a while, the decoder + // trying to catch up. + // If we seek the video track to a time later than the audio track, + // audio will start playing fine while no video will be output for a + // while, the video decoder will not stress the system. + if (mDataSource->read_at( + mSyncSampleOffset + 8 + (left - 1) * 4, &x, 4) != 4) { + return ERROR_IO; + } + x = ntohl(x); + assert((x - 1) >= start_sample_index); +#endif + + *sample_index = x - 1; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp new file mode 100644 index 000000000000..17b626e8bea9 --- /dev/null +++ b/media/libstagefright/ShoutcastSource.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/ShoutcastSource.h> +#include <media/stagefright/string.h> + +namespace android { + +ShoutcastSource::ShoutcastSource(HTTPStream *http) + : mHttp(http), + mMetaDataOffset(0), + mBytesUntilMetaData(0), + mGroup(NULL), + mStarted(false) { + string metaint; + if (mHttp->find_header_value("icy-metaint", &metaint)) { + char *end; + const char *start = metaint.c_str(); + mMetaDataOffset = strtol(start, &end, 10); + assert(end > start && *end == '\0'); + assert(mMetaDataOffset > 0); + + mBytesUntilMetaData = mMetaDataOffset; + } +} + +ShoutcastSource::~ShoutcastSource() { + if (mStarted) { + stop(); + } + + delete mHttp; + mHttp = NULL; +} + +status_t ShoutcastSource::start(MetaData *) { + assert(!mStarted); + + mGroup = new MediaBufferGroup; + mGroup->add_buffer(new MediaBuffer(4096)); // XXX + + mStarted = true; + + return OK; +} + +status_t ShoutcastSource::stop() { + assert(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +sp<MetaData> ShoutcastSource::getFormat() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, "audio/mpeg"); + meta->setInt32(kKeySampleRate, 44100); + meta->setInt32(kKeyChannelCount, 2); // XXX + + return meta; +} + +status_t ShoutcastSource::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + return ERROR_UNSUPPORTED; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + *out = buffer; + + size_t num_bytes = buffer->size(); + if (mMetaDataOffset > 0 && num_bytes > mBytesUntilMetaData) { + num_bytes = mBytesUntilMetaData; + } + + ssize_t n = mHttp->receive(buffer->data(), num_bytes); + + if (n <= 0) { + return (status_t)n; + } + + buffer->set_range(0, n); + + mBytesUntilMetaData -= (size_t)n; + + if (mBytesUntilMetaData == 0) { + unsigned char num_16_byte_blocks = 0; + n = mHttp->receive((char *)&num_16_byte_blocks, 1); + assert(n == 1); + + char meta[255 * 16]; + size_t meta_size = num_16_byte_blocks * 16; + size_t meta_length = 0; + while (meta_length < meta_size) { + n = mHttp->receive(&meta[meta_length], meta_size - meta_length); + if (n <= 0) { + return (status_t)n; + } + + meta_length += (size_t) n; + } + + while (meta_length > 0 && meta[meta_length - 1] == '\0') { + --meta_length; + } + + if (meta_length > 0) { + // Technically we should probably attach this meta data to the + // next buffer. XXX + buffer->meta_data()->setData('shou', 'shou', meta, meta_length); + } + + mBytesUntilMetaData = mMetaDataOffset; + } + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/SoftwareRenderer.cpp b/media/libstagefright/SoftwareRenderer.cpp new file mode 100644 index 000000000000..66b6b07a6be2 --- /dev/null +++ b/media/libstagefright/SoftwareRenderer.cpp @@ -0,0 +1,173 @@ +/* + * 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. + */ + +#define LOG_TAG "SoftwareRenderer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <binder/MemoryHeapBase.h> +#include <media/stagefright/SoftwareRenderer.h> +#include <ui/ISurface.h> + +namespace android { + +#define QCOM_YUV 0 + +SoftwareRenderer::SoftwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mISurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight), + mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565 + mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)), + mIndex(0) { + assert(mISurface.get() != NULL); + assert(mDecodedWidth > 0); + assert(mDecodedHeight > 0); + assert(mMemoryHeap->heapID() >= 0); + + ISurface::BufferHeap bufferHeap( + mDisplayWidth, mDisplayHeight, + mDecodedWidth, mDecodedHeight, + PIXEL_FORMAT_RGB_565, + mMemoryHeap); + + status_t err = mISurface->registerBuffers(bufferHeap); + assert(err == OK); +} + +SoftwareRenderer::~SoftwareRenderer() { + mISurface->unregisterBuffers(); +} + +void SoftwareRenderer::render( + const void *data, size_t size, void *platformPrivate) { + assert(size >= (mDecodedWidth * mDecodedHeight * 3) / 2); + + static const signed kClipMin = -278; + static const signed kClipMax = 535; + static uint8_t kClip[kClipMax - kClipMin + 1]; + static uint8_t *kAdjustedClip = &kClip[-kClipMin]; + + static bool clipInitialized = false; + + if (!clipInitialized) { + for (signed i = kClipMin; i <= kClipMax; ++i) { + kClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i; + } + clipInitialized = true; + } + + size_t offset = mIndex * mFrameSize; + + void *dst = (uint8_t *)mMemoryHeap->getBase() + offset; + + uint32_t *dst_ptr = (uint32_t *)dst; + + const uint8_t *src_y = (const uint8_t *)data; + + const uint8_t *src_u = + (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight; + +#if !QCOM_YUV + const uint8_t *src_v = + (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2); +#endif + + for (size_t y = 0; y < mDecodedHeight; ++y) { + for (size_t x = 0; x < mDecodedWidth; x += 2) { + // B = 1.164 * (Y - 16) + 2.018 * (U - 128) + // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128) + // R = 1.164 * (Y - 16) + 1.596 * (V - 128) + + // B = 298/256 * (Y - 16) + 517/256 * (U - 128) + // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128) + // R = .................. + 409/256 * (V - 128) + + // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277 + // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172 + // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223 + + // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534 + // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432 + // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481 + + // clip range -278 .. 535 + + signed y1 = (signed)src_y[x] - 16; + signed y2 = (signed)src_y[x + 1] - 16; + +#if QCOM_YUV + signed u = (signed)src_u[x & ~1] - 128; + signed v = (signed)src_u[(x & ~1) + 1] - 128; +#else + signed u = (signed)src_u[x / 2] - 128; + signed v = (signed)src_v[x / 2] - 128; +#endif + + signed u_b = u * 517; + signed u_g = -u * 100; + signed v_g = -v * 208; + signed v_r = v * 409; + + signed tmp1 = y1 * 298; + signed b1 = (tmp1 + u_b) / 256; + signed g1 = (tmp1 + v_g + u_g) / 256; + signed r1 = (tmp1 + v_r) / 256; + + signed tmp2 = y2 * 298; + signed b2 = (tmp2 + u_b) / 256; + signed g2 = (tmp2 + v_g + u_g) / 256; + signed r2 = (tmp2 + v_r) / 256; + + uint32_t rgb1 = + ((kAdjustedClip[r1] >> 3) << 11) + | ((kAdjustedClip[g1] >> 2) << 5) + | (kAdjustedClip[b1] >> 3); + + uint32_t rgb2 = + ((kAdjustedClip[r2] >> 3) << 11) + | ((kAdjustedClip[g2] >> 2) << 5) + | (kAdjustedClip[b2] >> 3); + + dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + } + + src_y += mDecodedWidth; + + if (y & 1) { +#if QCOM_YUV + src_u += mDecodedWidth; +#else + src_u += mDecodedWidth / 2; + src_v += mDecodedWidth / 2; +#endif + } + + dst_ptr += mDecodedWidth / 2; + } + + mISurface->postBuffer(offset); + mIndex = 1 - mIndex; +} + +} // namespace android diff --git a/media/libstagefright/SurfaceRenderer.cpp b/media/libstagefright/SurfaceRenderer.cpp new file mode 100644 index 000000000000..e54288d97ab3 --- /dev/null +++ b/media/libstagefright/SurfaceRenderer.cpp @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceRenderer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/SurfaceRenderer.h> +#include <ui/Surface.h> + +namespace android { + +SurfaceRenderer::SurfaceRenderer( + const sp<Surface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mSurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight) { +} + +SurfaceRenderer::~SurfaceRenderer() { +} + +void SurfaceRenderer::render( + const void *data, size_t size, void *platformPrivate) { + Surface::SurfaceInfo info; + status_t err = mSurface->lock(&info); + if (err != OK) { + return; + } + + const uint8_t *src = (const uint8_t *)data; + uint8_t *dst = (uint8_t *)info.bits; + + for (size_t i = 0; i < mDisplayHeight; ++i) { + memcpy(dst, src, mDisplayWidth); + src += mDecodedWidth; + dst += mDisplayWidth; + } + src += (mDecodedHeight - mDisplayHeight) * mDecodedWidth; + + for (size_t i = 0; i < (mDisplayHeight + 1) / 2; ++i) { + memcpy(dst, src, (mDisplayWidth + 1) & ~1); + src += (mDecodedWidth + 1) & ~1; + dst += (mDisplayWidth + 1) & ~1; + } + + mSurface->unlockAndPost(); +} + +} // namespace android diff --git a/media/libstagefright/TimeSource.cpp b/media/libstagefright/TimeSource.cpp new file mode 100644 index 000000000000..d987fbf1c731 --- /dev/null +++ b/media/libstagefright/TimeSource.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stddef.h> +#include <sys/time.h> + +#include <media/stagefright/TimeSource.h> + +namespace android { + +SystemTimeSource::SystemTimeSource() + : mStartTimeUs(GetSystemTimeUs()) { +} + +int64_t SystemTimeSource::getRealTimeUs() { + return GetSystemTimeUs() - mStartTimeUs; +} + +// static +int64_t SystemTimeSource::GetSystemTimeUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; +} + +} // namespace android + diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp new file mode 100644 index 000000000000..2f8a19f86dd4 --- /dev/null +++ b/media/libstagefright/TimedEventQueue.cpp @@ -0,0 +1,207 @@ +/* + * 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. + */ + +#undef __STRICT_ANSI__ +#define __STDINT_LIMITS +#define __STDC_LIMIT_MACROS +#include <stdint.h> + +#define LOG_TAG "TimedEventQueue" +#include <utils/Log.h> + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/TimedEventQueue.h> + +namespace android { + +TimedEventQueue::TimedEventQueue() + : mRunning(false), + mStopped(false) { +} + +TimedEventQueue::~TimedEventQueue() { + stop(); +} + +void TimedEventQueue::start() { + if (mRunning) { + return; + } + + mStopped = false; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&mThread, &attr, ThreadWrapper, this); + + pthread_attr_destroy(&attr); + + mRunning = true; +} + +void TimedEventQueue::stop(bool flush) { + if (!mRunning) { + return; + } + + if (flush) { + postEventToBack(new StopEvent); + } else { + postTimedEvent(new StopEvent, INT64_MIN); + } + + void *dummy; + pthread_join(mThread, &dummy); + + mQueue.clear(); + + mRunning = false; +} + +void TimedEventQueue::postEvent(const sp<Event> &event) { + // Reserve an earlier timeslot an INT64_MIN to be able to post + // the StopEvent to the absolute head of the queue. + postTimedEvent(event, INT64_MIN + 1); +} + +void TimedEventQueue::postEventToBack(const sp<Event> &event) { + postTimedEvent(event, INT64_MAX); +} + +void TimedEventQueue::postEventWithDelay( + const sp<Event> &event, int64_t delay_us) { + assert(delay_us >= 0); + postTimedEvent(event, getRealTimeUs() + delay_us); +} + +void TimedEventQueue::postTimedEvent( + const sp<Event> &event, int64_t realtime_us) { + Mutex::Autolock autoLock(mLock); + + List<QueueItem>::iterator it = mQueue.begin(); + while (it != mQueue.end() && realtime_us >= (*it).realtime_us) { + ++it; + } + + QueueItem item; + item.event = event; + item.realtime_us = realtime_us; + + if (it == mQueue.begin()) { + mQueueHeadChangedCondition.signal(); + } + + mQueue.insert(it, item); + + mQueueNotEmptyCondition.signal(); +} + +bool TimedEventQueue::cancelEvent(const sp<Event> &event) { + Mutex::Autolock autoLock(mLock); + + List<QueueItem>::iterator it = mQueue.begin(); + while (it != mQueue.end() && (*it).event != event) { + ++it; + } + + if (it == mQueue.end()) { + return false; + } + + if (it == mQueue.begin()) { + mQueueHeadChangedCondition.signal(); + } + + mQueue.erase(it); + + return true; +} + +// static +int64_t TimedEventQueue::getRealTimeUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; +} + +// static +void *TimedEventQueue::ThreadWrapper(void *me) { + static_cast<TimedEventQueue *>(me)->threadEntry(); + + return NULL; +} + +void TimedEventQueue::threadEntry() { + for (;;) { + int64_t now_us; + sp<Event> event; + + { + Mutex::Autolock autoLock(mLock); + + if (mStopped) { + break; + } + + while (mQueue.empty()) { + mQueueNotEmptyCondition.wait(mLock); + } + + List<QueueItem>::iterator it; + for (;;) { + it = mQueue.begin(); + + now_us = getRealTimeUs(); + int64_t when_us = (*it).realtime_us; + + int64_t delay_us; + if (when_us < 0 || when_us == INT64_MAX) { + delay_us = 0; + } else { + delay_us = when_us - now_us; + } + + if (delay_us <= 0) { + break; + } + + status_t err = mQueueHeadChangedCondition.waitRelative( + mLock, delay_us * 1000); + + if (err == -ETIMEDOUT) { + now_us = getRealTimeUs(); + break; + } + } + + event = (*it).event; + mQueue.erase(it); + } + + // Fire event with the lock NOT held. + event->fire(this, now_us); + } +} + +} // namespace android + diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp new file mode 100644 index 000000000000..2720f93e14e0 --- /dev/null +++ b/media/libstagefright/Utils.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <arpa/inet.h> + +#include <media/stagefright/Utils.h> + +namespace android { + +uint16_t U16_AT(const uint8_t *ptr) { + return ptr[0] << 8 | ptr[1]; +} + +uint32_t U32_AT(const uint8_t *ptr) { + return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; +} + +uint64_t U64_AT(const uint8_t *ptr) { + return ((uint64_t)U32_AT(ptr)) << 32 | U32_AT(ptr + 4); +} + +// XXX warning: these won't work on big-endian host. +uint64_t ntoh64(uint64_t x) { + return ((uint64_t)ntohl(x & 0xffffffff) << 32) | ntohl(x >> 32); +} + +uint64_t hton64(uint64_t x) { + return ((uint64_t)htonl(x & 0xffffffff) << 32) | htonl(x >> 32); +} + +} // namespace android + diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk new file mode 100644 index 000000000000..9c6d475bbf50 --- /dev/null +++ b/media/libstagefright/omx/Android.mk @@ -0,0 +1,27 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# Set up the OpenCore variables. +include external/opencore/Config.mk +LOCAL_C_INCLUDES := $(PV_INCLUDES) +LOCAL_CFLAGS := $(PV_CFLAGS_MINUS_VISIBILITY) + +LOCAL_SRC_FILES:= \ + OMX.cpp + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libmedia \ + libutils \ + libui \ + libopencore_common + +LOCAL_PRELINK_MODULE:= false + +LOCAL_MODULE:= libstagefright_omx + +include $(BUILD_SHARED_LIBRARY) + +endif diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp new file mode 100644 index 000000000000..daaa741ed26c --- /dev/null +++ b/media/libstagefright/omx/OMX.cpp @@ -0,0 +1,623 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "OMX" +#include <utils/Log.h> + +#include <sys/socket.h> + +#undef NDEBUG +#include <assert.h> + +#include "OMX.h" +#include "pv_omxcore.h" + +#include <binder/IMemory.h> + +#include <OMX_Component.h> + +namespace android { + +class NodeMeta { +public: + NodeMeta(OMX *owner) + : mOwner(owner), + mHandle(NULL) { + } + + OMX *owner() const { + return mOwner; + } + + void setHandle(OMX_HANDLETYPE handle) { + assert(mHandle == NULL); + mHandle = handle; + } + + OMX_HANDLETYPE handle() const { + return mHandle; + } + + void setObserver(const sp<IOMXObserver> &observer) { + mObserver = observer; + } + + sp<IOMXObserver> observer() { + return mObserver; + } + +private: + OMX *mOwner; + OMX_HANDLETYPE mHandle; + sp<IOMXObserver> mObserver; + + NodeMeta(const NodeMeta &); + NodeMeta &operator=(const NodeMeta &); +}; + +class BufferMeta { +public: + BufferMeta(OMX *owner, const sp<IMemory> &mem, bool is_backup = false) + : mOwner(owner), + mMem(mem), + mIsBackup(is_backup) { + } + + BufferMeta(OMX *owner, size_t size) + : mOwner(owner), + mSize(size), + mIsBackup(false) { + } + + void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy((OMX_U8 *)mMem->pointer() + header->nOffset, + header->pBuffer + header->nOffset, + header->nFilledLen); + } + + void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy(header->pBuffer + header->nOffset, + (const OMX_U8 *)mMem->pointer() + header->nOffset, + header->nFilledLen); + } + +private: + OMX *mOwner; + sp<IMemory> mMem; + size_t mSize; + bool mIsBackup; + + BufferMeta(const BufferMeta &); + BufferMeta &operator=(const BufferMeta &); +}; + +// static +OMX_CALLBACKTYPE OMX::kCallbacks = { + &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone +}; + +// static +OMX_ERRORTYPE OMX::OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnEvent(meta, eEvent, nData1, nData2, pEventData); +} + +// static +OMX_ERRORTYPE OMX::OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnEmptyBufferDone(meta, pBuffer); +} + +// static +OMX_ERRORTYPE OMX::OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnFillBufferDone(meta, pBuffer); +} + +OMX::OMX() +#if IOMX_USES_SOCKETS + : mSock(-1) +#endif +{ +} + +OMX::~OMX() { +#if IOMX_USES_SOCKETS + assert(mSock < 0); +#endif +} + +#if IOMX_USES_SOCKETS +status_t OMX::connect(int *sd) { + Mutex::Autolock autoLock(mLock); + + if (mSock >= 0) { + return UNKNOWN_ERROR; + } + + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { + return UNKNOWN_ERROR; + } + + mSock = sockets[0]; + *sd = sockets[1]; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); + + return OK; +} + +// static +void *OMX::ThreadWrapper(void *me) { + ((OMX *)me)->threadEntry(); + + return NULL; +} + +void OMX::threadEntry() { + bool done = false; + while (!done) { + omx_message msg; + ssize_t n = recv(mSock, &msg, sizeof(msg), 0); + + if (n <= 0) { + break; + } + + Mutex::Autolock autoLock(mLock); + + switch (msg.type) { + case omx_message::FILL_BUFFER: + { + OMX_BUFFERHEADERTYPE *header = + static_cast<OMX_BUFFERHEADERTYPE *>( + msg.u.buffer_data.buffer); + + header->nFilledLen = 0; + header->nOffset = 0; + header->hMarkTargetComponent = NULL; + header->nFlags = 0; + + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.buffer_data.node); + + LOGV("FillThisBuffer buffer=%p", header); + + OMX_ERRORTYPE err = + OMX_FillThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::EMPTY_BUFFER: + { + OMX_BUFFERHEADERTYPE *header = + static_cast<OMX_BUFFERHEADERTYPE *>( + msg.u.extended_buffer_data.buffer); + + header->nFilledLen = msg.u.extended_buffer_data.range_length; + header->nOffset = msg.u.extended_buffer_data.range_offset; + header->hMarkTargetComponent = NULL; + header->nFlags = msg.u.extended_buffer_data.flags; + header->nTimeStamp = msg.u.extended_buffer_data.timestamp; + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(header->pAppPrivate); + buffer_meta->CopyToOMX(header); + + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.extended_buffer_data.node); + + LOGV("EmptyThisBuffer buffer=%p", header); + + OMX_ERRORTYPE err = + OMX_EmptyThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::SEND_COMMAND: + { + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.send_command_data.node); + + OMX_ERRORTYPE err = + OMX_SendCommand( + node_meta->handle(), msg.u.send_command_data.cmd, + msg.u.send_command_data.param, NULL); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::DISCONNECT: + { + omx_message msg; + msg.type = omx_message::DISCONNECTED; + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); + done = true; + break; + } + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } + } + + Mutex::Autolock autoLock(mLock); + close(mSock); + mSock = -1; +} +#endif + +status_t OMX::list_nodes(List<String8> *list) { + OMX_MasterInit(); // XXX Put this somewhere else. + + list->clear(); + + OMX_U32 index = 0; + char componentName[256]; + while (OMX_MasterComponentNameEnum(componentName, sizeof(componentName), index) + == OMX_ErrorNone) { + list->push_back(String8(componentName)); + + ++index; + } + + return OK; +} + +status_t OMX::allocate_node(const char *name, node_id *node) { + Mutex::Autolock autoLock(mLock); + + *node = 0; + + OMX_MasterInit(); // XXX Put this somewhere else. + + NodeMeta *meta = new NodeMeta(this); + + OMX_HANDLETYPE handle; + OMX_ERRORTYPE err = OMX_MasterGetHandle( + &handle, const_cast<char *>(name), meta, &kCallbacks); + + if (err != OMX_ErrorNone) { + LOGE("FAILED to allocate omx component '%s'", name); + + delete meta; + meta = NULL; + + return UNKNOWN_ERROR; + } + + meta->setHandle(handle); + + *node = meta; + + return OK; +} + +status_t OMX::free_node(node_id node) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = OMX_MasterFreeHandle(meta->handle()); + + delete meta; + meta = NULL; + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { + Mutex::Autolock autoLock(mLock); + +#if IOMX_USES_SOCKETS + if (mSock < 0) { + return UNKNOWN_ERROR; + } +#endif + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = OMX_SendCommand(meta->handle(), cmd, param, NULL); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = OMX_GetParameter(meta->handle(), index, params); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_SetParameter(meta->handle(), index, const_cast<void *>(params)); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, params); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_UseBuffer(node_meta->handle(), &header, port_index, buffer_meta, + params->size(), static_cast<OMX_U8 *>(params->pointer())); + + if (err != OMX_ErrorNone) { + LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err); + + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, size); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_AllocateBuffer(node_meta->handle(), &header, port_index, + buffer_meta, size); + + if (err != OMX_ErrorNone) { + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, params, true); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_AllocateBuffer( + node_meta->handle(), &header, port_index, buffer_meta, + params->size()); + + if (err != OMX_ErrorNone) { + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::free_buffer(node_id node, OMX_U32 port_index, buffer_id buffer) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate); + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_FreeBuffer(node_meta->handle(), port_index, header); + + delete buffer_meta; + buffer_meta = NULL; + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +OMX_ERRORTYPE OMX::OnEvent( + NodeMeta *meta, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData) { + LOGV("OnEvent(%d, %ld, %ld)", eEvent, nData1, nData2); + + omx_message msg; + msg.type = omx_message::EVENT; + msg.u.event_data.node = meta; + msg.u.event_data.event = eEvent; + msg.u.event_data.data1 = nData1; + msg.u.event_data.data2 = nData2; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::OnEmptyBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + LOGV("OnEmptyBufferDone buffer=%p", pBuffer); + + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER_DONE; + msg.u.buffer_data.node = meta; + msg.u.buffer_data.buffer = pBuffer; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::OnFillBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + LOGV("OnFillBufferDone buffer=%p", pBuffer); + BufferMeta *buffer_meta = static_cast<BufferMeta *>(pBuffer->pAppPrivate); + buffer_meta->CopyFromOMX(pBuffer); + + omx_message msg; + msg.type = omx_message::FILL_BUFFER_DONE; + msg.u.extended_buffer_data.node = meta; + msg.u.extended_buffer_data.buffer = pBuffer; + msg.u.extended_buffer_data.range_offset = pBuffer->nOffset; + msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen; + msg.u.extended_buffer_data.flags = pBuffer->nFlags; + msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp; + msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +#if !IOMX_USES_SOCKETS +status_t OMX::observe_node( + node_id node, const sp<IOMXObserver> &observer) { + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + node_meta->setObserver(observer); + + return OK; +} + +void OMX::fill_buffer(node_id node, buffer_id buffer) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = 0; + header->nOffset = 0; + header->nFlags = 0; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = + OMX_FillThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); +} + +void OMX::empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = range_length; + header->nOffset = range_offset; + header->nFlags = flags; + header->nTimeStamp = timestamp; + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(header->pAppPrivate); + buffer_meta->CopyToOMX(header); + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = + OMX_EmptyThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); +} +#endif + +} // namespace android + diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/omx/OMX.h new file mode 100644 index 000000000000..ed4e5dd3238c --- /dev/null +++ b/media/libstagefright/omx/OMX.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_OMX_H_ +#define ANDROID_OMX_H_ + +#include <pthread.h> + +#include <media/IOMX.h> +#include <utils/threads.h> + +namespace android { + +class NodeMeta; + +class OMX : public BnOMX { +public: + OMX(); + virtual ~OMX(); + +#if IOMX_USES_SOCKETS + virtual status_t connect(int *sd); +#endif + + virtual status_t list_nodes(List<String8> *list); + + virtual status_t allocate_node(const char *name, node_id *node); + virtual status_t free_node(node_id node); + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size); + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size); + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer); + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer); + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer); + + virtual void fill_buffer(node_id node, buffer_id buffer); + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp); +#endif + +private: + static OMX_CALLBACKTYPE kCallbacks; + +#if IOMX_USES_SOCKETS + int mSock; + pthread_t mThread; + + static void *ThreadWrapper(void *me); + void threadEntry(); +#endif + + Mutex mLock; + + static OMX_ERRORTYPE OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData); + + static OMX_ERRORTYPE OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); + + static OMX_ERRORTYPE OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); + + OMX_ERRORTYPE OnEvent( + NodeMeta *meta, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData); + + OMX_ERRORTYPE OnEmptyBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + OMX_ERRORTYPE OnFillBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + OMX(const OMX &); + OMX &operator=(const OMX &); +}; + +} // namespace android + +#endif // ANDROID_OMX_H_ diff --git a/media/libstagefright/string.cpp b/media/libstagefright/string.cpp new file mode 100644 index 000000000000..5b1678403f8e --- /dev/null +++ b/media/libstagefright/string.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <media/stagefright/string.h> + +namespace android { + +// static +string::size_type string::npos = (string::size_type)-1; + +string::string() { +} + +string::string(const char *s, size_t length) + : mString(s, length) { +} + +string::string(const string &from, size_type start, size_type length) + : mString(from.c_str() + start, length) { +} + +string::string(const char *s) + : mString(s) { +} + +const char *string::c_str() const { + return mString.string(); +} + +string::size_type string::size() const { + return mString.length(); +} + +void string::clear() { + mString = String8(); +} + +string::size_type string::find(char c) const { + char s[2]; + s[0] = c; + s[1] = '\0'; + + ssize_t index = mString.find(s); + + return index < 0 ? npos : (size_type)index; +} + +bool string::operator<(const string &other) const { + return mString < other.mString; +} + +bool string::operator==(const string &other) const { + return mString == other.mString; +} + +string &string::operator+=(char c) { + mString.append(&c, 1); + + return *this; +} + +void string::erase(size_t from, size_t length) { + String8 s(mString.string(), from); + s.append(mString.string() + from + length); + + mString = s; +} + +} // namespace android + diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp index 6f0cdfb2c525..fe1187897126 100644 --- a/media/sdutils/sdutil.cpp +++ b/media/sdutils/sdutil.cpp @@ -88,7 +88,7 @@ static int mount(const char* path) { String16 string(path); gMountService->mountMedia(string); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 60; i++) { if (isMounted(path)) { return 0; } @@ -103,7 +103,7 @@ static int unmount(const char* path) { String16 string(path); gMountService->unmountMedia(string); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 20; i++) { if (!isMounted(path)) { return 0; } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java index 6edc2cc0d61b..2a4e9a04af62 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java @@ -23,6 +23,7 @@ import com.android.mediaframeworktest.functional.MediaMimeTest; import com.android.mediaframeworktest.functional.MediaPlayerApiTest; import com.android.mediaframeworktest.functional.MediaRecorderTest; import com.android.mediaframeworktest.functional.SimTonesTest; +import com.android.mediaframeworktest.functional.MediaPlayerInvokeTest; import junit.framework.TestSuite; @@ -32,7 +33,7 @@ import android.test.InstrumentationTestSuite; /** * Instrumentation Test Runner for all MediaPlayer tests. - * + * * Running all tests: * * adb shell am instrument \ @@ -52,6 +53,7 @@ public class MediaFrameworkTestRunner extends InstrumentationTestRunner { suite.addTestSuite(MediaRecorderTest.class); suite.addTestSuite(MediaAudioTrackTest.class); suite.addTestSuite(MediaMimeTest.class); + suite.addTestSuite(MediaPlayerInvokeTest.class); return suite; } @@ -60,5 +62,3 @@ public class MediaFrameworkTestRunner extends InstrumentationTestRunner { return MediaFrameworkTestRunner.class.getClassLoader(); } } - - diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java index 81d59da5c492..a203adc71c31 100755 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java @@ -24,16 +24,16 @@ import junit.framework.TestSuite; /** * Instrumentation Test Runner for all media framework unit tests. - * + * * Make sure that MediaFrameworkUnitTestRunner has been added to * AndroidManifest.xml file, and then "make -j4 mediaframeworktest; adb sync" * to build and upload mediaframeworktest to the phone or emulator. - * + * * Example on running all unit tests for a single class: * adb shell am instrument -e class \ - * com.android.mediaframeworktest.unit.MediaMetadataRetrieverUnitTest \ + * com.android.mediaframeworktest.unit.MediaMetadataRetrieverUnitTest \ * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner - * + * * Example on running all unit tests for the media framework: * adb shell am instrument \ * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner @@ -54,12 +54,12 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { public ClassLoader getLoader() { return MediaFrameworkUnitTestRunner.class.getClassLoader(); } - + // Running all unit tests checking the state machine may be time-consuming. private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { suite.addTestSuite(MediaMetadataRetrieverTest.class); } - + // Running all unit tests checking the state machine may be time-consuming. private void addMediaRecorderStateUnitTests(TestSuite suite) { suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class); @@ -87,5 +87,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { suite.addTestSuite(MediaPlayerSetLoopingStateUnitTest.class); suite.addTestSuite(MediaPlayerSetAudioStreamTypeStateUnitTest.class); suite.addTestSuite(MediaPlayerSetVolumeStateUnitTest.class); + suite.addTestSuite(MediaPlayerMetadataParserTest.class); } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java new file mode 100644 index 000000000000..ab8b31155972 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerInvokeTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.functional; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaNames; + +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; + +import android.media.MediaPlayer; +import android.os.Parcel; + +import java.util.Calendar; +import java.util.Random; + +// Tests for the invoke method in the MediaPlayer. +public class MediaPlayerInvokeTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private static final String TAG = "MediaPlayerInvokeTest"; + private MediaPlayer mPlayer; + private Random rnd; + + public MediaPlayerInvokeTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + rnd = new Random(Calendar.getInstance().getTimeInMillis()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mPlayer = new MediaPlayer(); + } + + @Override + protected void tearDown() throws Exception { + mPlayer.release(); + super.tearDown(); + } + + // Generate a random number, sends it to the ping test player. + @MediumTest + public void testPing() throws Exception { + mPlayer.setDataSource("test:invoke_mock_media_player.so?url=ping"); + + Parcel request = mPlayer.newRequest(); + Parcel reply = Parcel.obtain(); + + int val = rnd.nextInt(); + request.writeInt(val); + assertEquals(0, mPlayer.invoke(request, reply)); + assertEquals(val, reply.readInt()); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java new file mode 100644 index 000000000000..38f598a6abc9 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.unit; +import android.media.Metadata; +import android.os.Parcel; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import java.util.Calendar; +import java.util.Date; + +/* + * Check the Java layer that parses serialized metadata in Parcel + * works as expected. + * + */ + +public class MediaPlayerMetadataParserTest extends AndroidTestCase { + private static final String TAG = "MediaPlayerMetadataTest"; + private static final int kMarker = 0x4d455441; // 'M' 'E' 'T' 'A' + private static final int kHeaderSize = 8; + + private Metadata mMetadata = null; + private Parcel mParcel = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mMetadata = new Metadata(); + mParcel = Parcel.obtain(); + + resetParcel(); + } + + // Check parsing of the parcel fails. Make sure the parser rewind + // the parcel properly. + private void assertParseFail() throws Exception { + mParcel.setDataPosition(0); + assertFalse(mMetadata.parse(mParcel)); + assertEquals(0, mParcel.dataPosition()); + } + + // Check parsing of the parcel is successful. + private void assertParse() throws Exception { + mParcel.setDataPosition(0); + assertTrue(mMetadata.parse(mParcel)); + } + + // Write the number of bytes from the start of the parcel to the + // current position at the beginning of the parcel (offset 0). + private void adjustSize() { + adjustSize(0); + } + + // Write the number of bytes from the offset to the current + // position at position pointed by offset. + private void adjustSize(int offset) { + final int pos = mParcel.dataPosition(); + + mParcel.setDataPosition(offset); + mParcel.writeInt(pos - offset); + mParcel.setDataPosition(pos); + } + + // Rewind the parcel and insert the header. + private void resetParcel() { + mParcel.setDataPosition(0); + // Most tests will use a properly formed parcel with a size + // and the meta marker so we add them by default. + mParcel.writeInt(-1); // Placeholder for the size + mParcel.writeInt(kMarker); + } + + // ---------------------------------------------------------------------- + // START OF THE TESTS + + + // There should be at least 8 bytes in the parcel, 4 for the size + // and 4 for the 'M' 'E' 'T' 'A' marker. + @SmallTest + public void testMissingSizeAndMarker() throws Exception { + for (int i = 0; i < kHeaderSize; ++i) { + mParcel.setDataPosition(0); + mParcel.setDataSize(i); + + assertEquals(i, mParcel.dataAvail()); + assertParseFail(); + } + } + + // There should be at least 'size' bytes in the parcel. + @SmallTest + public void testMissingData() throws Exception { + final int size = 20; + + mParcel.writeInt(size); + mParcel.setDataSize(size - 1); + assertParseFail(); + } + + // Empty parcel is fine + @SmallTest + public void testEmptyIsOk() throws Exception { + adjustSize(); + assertParse(); + } + + // ---------------------------------------------------------------------- + // RECORDS + // ---------------------------------------------------------------------- + + // A record header should be at least 12 bytes long + @SmallTest + public void testRecordMissingId() throws Exception { + mParcel.writeInt(13); // record length + // misses metadata id and metadata type. + adjustSize(); + assertParseFail(); + } + + @SmallTest + public void testRecordMissingType() throws Exception { + mParcel.writeInt(13); // record length lies + mParcel.writeInt(Metadata.TITLE); + // misses metadata type + adjustSize(); + assertParseFail(); + } + + @SmallTest + public void testRecordWithZeroPayload() throws Exception { + mParcel.writeInt(0); + adjustSize(); + assertParseFail(); + } + + // A record cannot be empty. + @SmallTest + public void testRecordMissingPayload() throws Exception { + mParcel.writeInt(12); + mParcel.writeInt(Metadata.TITLE); + mParcel.writeInt(Metadata.STRING_VAL); + // misses payload + adjustSize(); + assertParseFail(); + } + + // Check records can be found. + @SmallTest + public void testRecordsFound() throws Exception { + writeStringRecord(Metadata.TITLE, "a title"); + writeStringRecord(Metadata.GENRE, "comedy"); + writeStringRecord(Metadata.firstCustomId(), "custom"); + adjustSize(); + assertParse(); + assertTrue(mMetadata.has(Metadata.TITLE)); + assertTrue(mMetadata.has(Metadata.GENRE)); + assertTrue(mMetadata.has(Metadata.firstCustomId())); + assertFalse(mMetadata.has(Metadata.DRM_CRIPPLED)); + assertEquals(3, mMetadata.keySet().size()); + } + + // Detects bad metadata type + @SmallTest + public void testBadMetadataType() throws Exception { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(Metadata.TITLE); + mParcel.writeInt(0); // Invalid type. + mParcel.writeString("dummy"); + adjustSize(start); + + adjustSize(); + assertParseFail(); + } + + // Check a Metadata instance can be reused, i.e the parse method + // wipes out the existing states/keys. + @SmallTest + public void testParseClearState() throws Exception { + writeStringRecord(Metadata.TITLE, "a title"); + writeStringRecord(Metadata.GENRE, "comedy"); + writeStringRecord(Metadata.firstCustomId(), "custom"); + adjustSize(); + assertParse(); + + resetParcel(); + writeStringRecord(Metadata.MIME_TYPE, "audio/mpg"); + adjustSize(); + assertParse(); + + // Only the mime type metadata should be present. + assertEquals(1, mMetadata.keySet().size()); + assertTrue(mMetadata.has(Metadata.MIME_TYPE)); + + assertFalse(mMetadata.has(Metadata.TITLE)); + assertFalse(mMetadata.has(Metadata.GENRE)); + assertFalse(mMetadata.has(Metadata.firstCustomId())); + } + + // ---------------------------------------------------------------------- + // GETTERS + // ---------------------------------------------------------------------- + + // getString + @SmallTest + public void testGetString() throws Exception { + writeStringRecord(Metadata.TITLE, "a title"); + writeStringRecord(Metadata.GENRE, "comedy"); + adjustSize(); + assertParse(); + + assertEquals("a title", mMetadata.getString(Metadata.TITLE)); + assertEquals("comedy", mMetadata.getString(Metadata.GENRE)); + } + + // get an empty string. + @SmallTest + public void testGetEmptyString() throws Exception { + writeStringRecord(Metadata.TITLE, ""); + adjustSize(); + assertParse(); + + assertEquals("", mMetadata.getString(Metadata.TITLE)); + } + + // get a string when a NULL value was in the parcel + @SmallTest + public void testGetNullString() throws Exception { + writeStringRecord(Metadata.TITLE, null); + adjustSize(); + assertParse(); + + assertEquals(null, mMetadata.getString(Metadata.TITLE)); + } + + // get a string when an integer is actually present + @SmallTest + public void testWrongType() throws Exception { + writeIntRecord(Metadata.DURATION, 5); + adjustSize(); + assertParse(); + + try { + mMetadata.getString(Metadata.DURATION); + } catch (IllegalStateException ise) { + return; + } + fail("Exception was not thrown"); + } + + // getInt + @SmallTest + public void testGetInt() throws Exception { + writeIntRecord(Metadata.CD_TRACK_NUM, 1); + adjustSize(); + assertParse(); + + assertEquals(1, mMetadata.getInt(Metadata.CD_TRACK_NUM)); + } + + // getBoolean + @SmallTest + public void testGetBoolean() throws Exception { + writeBooleanRecord(Metadata.DRM_CRIPPLED, true); + adjustSize(); + assertParse(); + + assertEquals(true, mMetadata.getBoolean(Metadata.DRM_CRIPPLED)); + } + + // getLong + @SmallTest + public void testGetLong() throws Exception { + writeLongRecord(Metadata.DURATION, 1L); + adjustSize(); + assertParse(); + + assertEquals(1L, mMetadata.getLong(Metadata.DURATION)); + } + + // getDouble + @SmallTest + public void testGetDouble() throws Exception { + writeDoubleRecord(Metadata.VIDEO_FRAME_RATE, 29.97); + adjustSize(); + assertParse(); + + assertEquals(29.97, mMetadata.getDouble(Metadata.VIDEO_FRAME_RATE)); + } + + // getByteArray + @SmallTest + public void testGetByteArray() throws Exception { + byte data[] = new byte[]{1,2,3,4,5}; + + writeByteArrayRecord(Metadata.ALBUM_ART, data); + adjustSize(); + assertParse(); + + byte res[] = mMetadata.getByteArray(Metadata.ALBUM_ART); + for (int i = 0; i < data.length; ++i) { + assertEquals(data[i], res[i]); + } + } + + // getDate + @SmallTest + public void testGetDate() throws Exception { + writeDateRecord(Metadata.DATE, 0, "PST"); + adjustSize(); + assertParse(); + + assertEquals(new Date(0), mMetadata.getDate(Metadata.DATE)); + } + + // getTimedText + @SmallTest + public void testGetTimedText() throws Exception { + Date now = Calendar.getInstance().getTime(); + writeTimedTextRecord(Metadata.CAPTION, now.getTime(), + 10, "Some caption"); + adjustSize(); + assertParse(); + + Metadata.TimedText caption = mMetadata.getTimedText(Metadata.CAPTION); + assertEquals("" + now + "-" + 10 + ":Some caption", caption.toString()); + } + + // ---------------------------------------------------------------------- + // HELPERS TO APPEND RECORDS + // ---------------------------------------------------------------------- + + // Insert a string record at the current position. + private void writeStringRecord(int metadataId, String val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.STRING_VAL); + mParcel.writeString(val); + adjustSize(start); + } + + // Insert an int record at the current position. + private void writeIntRecord(int metadataId, int val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.INTEGER_VAL); + mParcel.writeInt(val); + adjustSize(start); + } + + // Insert a boolean record at the current position. + private void writeBooleanRecord(int metadataId, boolean val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.BOOLEAN_VAL); + mParcel.writeInt(val ? 1 : 0); + adjustSize(start); + } + + // Insert a Long record at the current position. + private void writeLongRecord(int metadataId, long val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.LONG_VAL); + mParcel.writeLong(val); + adjustSize(start); + } + + // Insert a Double record at the current position. + private void writeDoubleRecord(int metadataId, double val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.DOUBLE_VAL); + mParcel.writeDouble(val); + adjustSize(start); + } + + // Insert a ByteArray record at the current position. + private void writeByteArrayRecord(int metadataId, byte[] val) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.BYTE_ARRAY_VAL); + mParcel.writeByteArray(val); + adjustSize(start); + } + + // Insert a Date record at the current position. + private void writeDateRecord(int metadataId, long time, String tz) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.DATE_VAL); + mParcel.writeLong(time); + mParcel.writeString(tz); + adjustSize(start); + } + + // Insert a TimedText record at the current position. + private void writeTimedTextRecord(int metadataId, long begin, + int duration, String text) { + final int start = mParcel.dataPosition(); + mParcel.writeInt(-1); // Placeholder for the length + mParcel.writeInt(metadataId); + mParcel.writeInt(Metadata.TIMED_TEXT_VAL); + mParcel.writeLong(begin); + mParcel.writeInt(duration); + mParcel.writeString(text); + adjustSize(start); + } +} diff --git a/media/tests/players/Android.mk b/media/tests/players/Android.mk new file mode 100644 index 000000000000..eb50a514cba9 --- /dev/null +++ b/media/tests/players/Android.mk @@ -0,0 +1,29 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= invoke_mock_media_player.cpp + +LOCAL_SHARED_LIBRARIES:= \ + libbinder \ + libutils + +LOCAL_MODULE:= invoke_mock_media_player +LOCAL_MODULE_TAGS := test eng +LOCAL_PRELINK_MODULE:= false + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/tests/players/README b/media/tests/players/README new file mode 100644 index 000000000000..edf9bd634286 --- /dev/null +++ b/media/tests/players/README @@ -0,0 +1,8 @@ +Native test players for system tests. + +For functional/system/performance tests, a native test player can be used. +This directory contains the sources of such players. +The class TestPlayerStub uses the dynamic loader to load any of them. + + + diff --git a/media/tests/players/invoke_mock_media_player.cpp b/media/tests/players/invoke_mock_media_player.cpp new file mode 100644 index 000000000000..8d575a35b29c --- /dev/null +++ b/media/tests/players/invoke_mock_media_player.cpp @@ -0,0 +1,124 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "TestPlayerStub" +#include "utils/Log.h" + +#include <string.h> + +#include <binder/Parcel.h> +#include <media/MediaPlayerInterface.h> +#include <utils/Errors.h> + +using android::INVALID_OPERATION; +using android::ISurface; +using android::MediaPlayerBase; +using android::MetadataType; +using android::OK; +using android::Parcel; +using android::SortedVector; +using android::TEST_PLAYER; +using android::UNKNOWN_ERROR; +using android::player_type; +using android::sp; +using android::status_t; + +// This file contains a test player that is loaded via the +// TestPlayerStub class. The player contains various implementation +// of the invoke method that java tests can use. + +namespace { +const char *kPing = "ping"; + +class Player: public MediaPlayerBase +{ + public: + enum TestType {TEST_UNKNOWN, PING}; + Player() {} + virtual ~Player() {} + + virtual status_t initCheck() {return OK;} + virtual bool hardwareOutput() {return true;} + + virtual status_t setDataSource(const char *url) { + LOGV("setDataSource %s", url); + mTest = TEST_UNKNOWN; + if (strncmp(url, kPing, strlen(kPing)) == 0) { + mTest = PING; + } + return OK; + } + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length) {return OK;} + virtual status_t setVideoSurface(const sp<ISurface>& surface) {return OK;} + virtual status_t prepare() {return OK;} + virtual status_t prepareAsync() {return OK;} + virtual status_t start() {return OK;} + virtual status_t stop() {return OK;} + virtual status_t pause() {return OK;} + virtual bool isPlaying() {return true;} + virtual status_t seekTo(int msec) {return OK;} + virtual status_t getCurrentPosition(int *msec) {return OK;} + virtual status_t getDuration(int *msec) {return OK;} + virtual status_t reset() {return OK;} + virtual status_t setLooping(int loop) {return OK;} + virtual player_type playerType() {return TEST_PLAYER;} + virtual status_t invoke(const Parcel& request, Parcel *reply); + virtual status_t getMetadata(const SortedVector<MetadataType>& ids, + Parcel *records) {return INVALID_OPERATION;} + + private: + // Take a request, copy it to the reply. + void ping(const Parcel& request, Parcel *reply); + + status_t mStatus; + TestType mTest; +}; + +status_t Player::invoke(const Parcel& request, Parcel *reply) +{ + switch (mTest) { + case PING: + ping(request, reply); + break; + default: mStatus = UNKNOWN_ERROR; + } + return mStatus; +} + +void Player::ping(const Parcel& request, Parcel *reply) +{ + const size_t len = request.dataAvail(); + + reply->setData(static_cast<const uint8_t*>(request.readInplace(len)), len); + mStatus = OK; +} + +} + +extern "C" android::MediaPlayerBase* newPlayer() +{ + LOGD("New invoke test player"); + return new Player(); +} + +extern "C" android::status_t deletePlayer(android::MediaPlayerBase *player) +{ + LOGD("Delete invoke test player"); + delete player; + return OK; +} diff --git a/obex/javax/obex/Authenticator.java b/obex/javax/obex/Authenticator.java index 7246e9166b8f..ec226fb7ada3 100644 --- a/obex/javax/obex/Authenticator.java +++ b/obex/javax/obex/Authenticator.java @@ -34,53 +34,50 @@ package javax.obex; /** * This interface provides a way to respond to authentication challenge and - * authentication response headers. When a client or server receives an + * authentication response headers. When a client or server receives an * authentication challenge or authentication response header, the * <code>onAuthenticationChallenge()</code> or - * <code>onAuthenticationResponse()</code> will be called, respectively, by - * the implementation. + * <code>onAuthenticationResponse()</code> will be called, respectively, by the + * implementation. * <P> * For more information on how the authentication procedure works in OBEX, - * please review the IrOBEX specification at - * <A HREF="http://www.irda.org">http://www.irda.org</A>. + * please review the IrOBEX specification at <A + * HREF="http://www.irda.org">http://www.irda.org</A>. * <P> * <STRONG>Authentication Challenges</STRONG> * <P> * When a client or server receives an authentication challenge header, the - * <code>onAuthenticationChallenge()</code> method will be invoked by the - * OBEX API implementation. The application will then return the user name - * (if needed) and password via a <code>PasswordAuthentication</code> object. - * The password in this object is not sent in the authentication response. - * Instead, the 16-byte challenge received in the authentication challenge is - * combined with the password returned from the - * <code>onAuthenticationChallenge()</code> method and passed through the MD5 - * hash algorithm. The resulting value is sent in the authentication response - * along with the user name if it was provided. + * <code>onAuthenticationChallenge()</code> method will be invoked by the OBEX + * API implementation. The application will then return the user name (if + * needed) and password via a <code>PasswordAuthentication</code> object. The + * password in this object is not sent in the authentication response. Instead, + * the 16-byte challenge received in the authentication challenge is combined + * with the password returned from the <code>onAuthenticationChallenge()</code> + * method and passed through the MD5 hash algorithm. The resulting value is sent + * in the authentication response along with the user name if it was provided. * <P> * <STRONG>Authentication Responses</STRONG> * <P> * When a client or server receives an authentication response header, the * <code>onAuthenticationResponse()</code> method is invoked by the API * implementation with the user name received in the authentication response - * header. (The user name will be <code>null</code> if no user name was - * provided in the authentication response header.) The application must - * determine the correct password. This value should be returned from the - * <code>onAuthenticationResponse()</code> method. If the authentication - * request should fail without the implementation checking the password, - * <code>null</code> should - * be returned by the application. (This is needed for reasons like not - * recognizing the user name, etc.) If the returned value is not - * <code>null</code>, the OBEX API implementation will combine the password + * header. (The user name will be <code>null</code> if no user name was provided + * in the authentication response header.) The application must determine the + * correct password. This value should be returned from the + * <code>onAuthenticationResponse()</code> method. If the authentication request + * should fail without the implementation checking the password, + * <code>null</code> should be returned by the application. (This is needed for + * reasons like not recognizing the user name, etc.) If the returned value is + * not <code>null</code>, the OBEX API implementation will combine the password * returned from the <code>onAuthenticationResponse()</code> method and * challenge sent via the authentication challenge, apply the MD5 hash * algorithm, and compare the result to the response hash received in the - * authentication response header. If the values are not equal, an - * <code>IOException</code> will be thrown if the client requested authentication. - * If the server requested authentication, the + * authentication response header. If the values are not equal, an + * <code>IOException</code> will be thrown if the client requested + * authentication. If the server requested authentication, the * <code>onAuthenticationFailure()</code> method will be called on the - * <code>ServerRequestHandler</code> that failed authentication. The - * connection is <B>not</B> closed if authentication failed. - * + * <code>ServerRequestHandler</code> that failed authentication. The connection + * is <B>not</B> closed if authentication failed. * @hide */ public interface Authenticator { @@ -90,35 +87,29 @@ public interface Authenticator { * header. It should respond to the challenge with a * <code>PasswordAuthentication</code> that contains the correct user name * and password for the challenge. - * - * @param description the description of which user name and password - * should be used; if no description is provided in the authentication - * challenge or the description is encoded in an encoding scheme that is - * not supported, an empty string will be provided - * + * @param description the description of which user name and password should + * be used; if no description is provided in the authentication + * challenge or the description is encoded in an encoding scheme that + * is not supported, an empty string will be provided * @param isUserIdRequired <code>true</code> if the user ID is required; - * <code>false</code> if the user ID is not required - * - * @param isFullAccess <code>true</code> if full access to the server - * will be granted; <code>false</code> if read only access will be - * granted - * - * @return a <code>PasswordAuthentication</code> object containing the - * user name and password used for authentication + * <code>false</code> if the user ID is not required + * @param isFullAccess <code>true</code> if full access to the server will + * be granted; <code>false</code> if read only access will be granted + * @return a <code>PasswordAuthentication</code> object containing the user + * name and password used for authentication */ PasswordAuthentication onAuthenticationChallenge(String description, boolean isUserIdRequired, boolean isFullAccess); /** * Called when a client or server receives an authentication response - * header. This method will provide the user name and expect the correct + * header. This method will provide the user name and expect the correct * password to be returned. - * - * @param userName the user name provided in the authentication response; - * may be <code>null</code> - * + * @param userName the user name provided in the authentication response; may + * be <code>null</code> * @return the correct password for the user name provided; if - * <code>null</code> is returned then the authentication request failed + * <code>null</code> is returned then the authentication request + * failed */ byte[] onAuthenticationResponse(byte[] userName); } diff --git a/obex/javax/obex/BaseStream.java b/obex/javax/obex/BaseStream.java index c32717fad272..022ad4fb4a34 100644 --- a/obex/javax/obex/BaseStream.java +++ b/obex/javax/obex/BaseStream.java @@ -37,46 +37,39 @@ import java.io.IOException; /** * This interface defines the methods needed by a parent that uses the * PrivateInputStream and PrivateOutputStream objects defined in this package. - * * @hide */ public interface BaseStream { /** * Verifies that this object is still open. - * * @throws IOException if the object is closed */ void ensureOpen() throws IOException; /** - * Verifies that additional information may be sent. In other words, the + * Verifies that additional information may be sent. In other words, the * operation is not done. - * * @throws IOException if the operation is completed */ void ensureNotDone() throws IOException; /** * Continues the operation since there is no data to read. - * - * @param sendEmpty <code>true</code> if the operation should send an - * empty packet or not send anything if there is no data to send - * @param inStream <code>true</code> if the stream is input stream or - * is output stream + * @param sendEmpty <code>true</code> if the operation should send an empty + * packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or is + * output stream * @return <code>true</code> if the operation was completed; - * <code>false</code> if no operation took place - * + * <code>false</code> if no operation took place * @throws IOException if an IO error occurs */ boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException; /** * Called when the output or input stream is closed. - * * @param inStream <code>true</code> if the input stream is closed; - * <code>false</code> if the output stream is closed - * + * <code>false</code> if the output stream is closed * @throws IOException if an IO error occurs */ void streamClosed(boolean inStream) throws IOException; diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index b3807afe0705..65663b1a5bb9 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -40,9 +40,8 @@ import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; /** - * This class implements the <code>Operation</code> interface. It will read - * and write data via puts and gets. - * + * This class implements the <code>Operation</code> interface. It will read and + * write data via puts and gets. * @hide */ public final class ClientOperation implements Operation, BaseStream { @@ -73,15 +72,14 @@ public final class ClientOperation implements Operation, BaseStream { private boolean mEndOfBodySent; - /** + /** * Creates new OperationImpl to read and write data to a server * @param maxSize the maximum packet size * @param p the parent to this object * @param type <code>true</code> if this is a get request; - * <code>false</code. if this is a put request - * @param headers the headers to set in the initial request - * - * @throws IOExcpetion if the an IO error occured + * <code>false</code. if this is a put request + * @param header the header to set in the initial request + * @throws IOException if the an IO error occurred */ public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type) throws IOException { @@ -126,20 +124,14 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * Sends an ABORT message to the server. By calling this method, the + * Sends an ABORT message to the server. By calling this method, the * corresponding input and output streams will be closed along with this * object. - * - * @throws IOException if the transaction has already ended or if an - * OBEX server called this method + * @throws IOException if the transaction has already ended or if an OBEX + * server called this method */ public synchronized void abort() throws IOException { ensureOpen(); - // need check again . - // if(isDone) { - // throw new IOException("Operation has already ended"); - // } - //no compatible with sun-ri if ((mOperationDone) && (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE)) { throw new IOException("Operation has already ended"); @@ -165,15 +157,14 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * Retrieves the response code retrieved from the server. Response codes - * are defined in the <code>ResponseCodes</code> interface. - * + * Retrieves the response code retrieved from the server. Response codes are + * defined in the <code>ResponseCodes</code> interface. * @return the response code retrieved from the server - * * @throws IOException if an error occurred in the transport layer during - * the transaction; if this method is called on a <code>HeaderSet</code> - * object created by calling <code>createHeaderSet</code> in a - * <code>ClientSession</code> object + * the transaction; if this method is called on a + * <code>HeaderSet</code> object created by calling + * <code>createHeaderSet</code> in a <code>ClientSession</code> + * object */ public synchronized int getResponseCode() throws IOException { //avoid dup validateConnection @@ -187,7 +178,6 @@ public final class ClientOperation implements Operation, BaseStream { /** * This method will always return <code>null</code> - * * @return <code>null</code> */ public String getEncoding() { @@ -198,9 +188,8 @@ public final class ClientOperation implements Operation, BaseStream { * Returns the type of content that the resource connected to is providing. * E.g. if the connection is via HTTP, then the value of the content-type * header field is returned. - * * @return the content type of the resource that the URL references, or - * <code>null</code> if not known + * <code>null</code> if not known */ public String getType() { try { @@ -212,11 +201,10 @@ public final class ClientOperation implements Operation, BaseStream { /** * Returns the length of the content which is being provided. E.g. if the - * connection is via HTTP, then the value of the content-length header - * field is returned. - * + * connection is via HTTP, then the value of the content-length header field + * is returned. * @return the content length of the resource that this connection's URL - * references, or -1 if the content length is not known + * references, or -1 if the content length is not known */ public long getLength() { try { @@ -234,9 +222,7 @@ public final class ClientOperation implements Operation, BaseStream { /** * Open and return an input stream for a connection. - * * @return an input stream - * * @throws IOException if an I/O error occurs */ public InputStream openInputStream() throws IOException { @@ -259,11 +245,9 @@ public final class ClientOperation implements Operation, BaseStream { return mPrivateInput; } - /**8 + /** * Open and return a data input stream for a connection. - * * @return an input stream - * * @throws IOException if an I/O error occurs */ public DataInputStream openDataInputStream() throws IOException { @@ -272,9 +256,7 @@ public final class ClientOperation implements Operation, BaseStream { /** * Open and return an output stream for a connection. - * * @return an output stream - * * @throws IOException if an I/O error occurs */ public OutputStream openOutputStream() throws IOException { @@ -301,9 +283,7 @@ public final class ClientOperation implements Operation, BaseStream { /** * Open and return a data output stream for a connection. - * * @return an output stream - * * @throws IOException if an I/O error occurs */ public DataOutputStream openDataOutputStream() throws IOException { @@ -312,7 +292,6 @@ public final class ClientOperation implements Operation, BaseStream { /** * Closes the connection and ends the transaction - * * @throws IOException if the operation has already ended or is closed */ public void close() throws IOException { @@ -324,11 +303,9 @@ public final class ClientOperation implements Operation, BaseStream { /** * Returns the headers that have been received during the operation. - * Modifying the object returned has no effect on the headers that are - * sent or retrieved. - * + * Modifying the object returned has no effect on the headers that are sent + * or retrieved. * @return the headers received during this <code>Operation</code> - * * @throws IOException if this <code>Operation</code> has been closed */ public HeaderSet getReceivedHeader() throws IOException { @@ -340,15 +317,11 @@ public final class ClientOperation implements Operation, BaseStream { /** * Specifies the headers that should be sent in the next OBEX message that * is sent. - * * @param headers the headers to send in the next message - * - * @throws IOException if this <code>Operation</code> has been closed - * or the transaction has ended and no further messages will be exchanged - * + * @throws IOException if this <code>Operation</code> has been closed or the + * transaction has ended and no further messages will be exchanged * @throws IllegalArgumentException if <code>headers</code> was not created - * by a call to <code>ServerRequestHandler.createHeaderSet()</code> - * + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> * @throws NullPointerException if <code>headers</code> is <code>null</code> */ public void sendHeaders(HeaderSet headers) throws IOException { @@ -370,62 +343,8 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * Reads a response from the server. It will populate the appropriate body - * and headers. - * - * @return <code>true</code> if the transaction should end; - * <code>false</code> if the transaction should not end - * - * @throws IOException if an IO error occurred - */ - /* - private boolean readResponse() throws IOException { - mReplyHeader.responseCode = mInput.read(); - int packetLength = mInput.read(); - packetLength = (packetLength << 8) + mInput.read(); - - if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { - if (mExceptionMessage != null) { - abort(); - } - throw new IOException("Received a packet that was too big"); - } - - if (packetLength > ObexHelper.BASE_PACKET_LENGTH) { - int dataLength = packetLength - ObexHelper.BASE_PACKET_LENGTH; - byte[] data = new byte[dataLength]; - int readLength = mInput.read(data); - if (readLength != dataLength) { - throw new IOException("Received a packet without data as decalred length"); - } - byte[] body = ObexHelper.updateHeaderSet(mReplyHeader, data); - - if (body != null) { - mPrivateInput.writeBytes(body, 1); - - /* - * Determine if a body (0x48) header or an end of body (0x49) - * was received. If we received an end of body and - * a response code of OBEX_HTTP_OK, then the operation should - * end. - * - if ((body[0] == 0x49) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_OK)) { - return false; - } - } - } - - if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - return true; - } else { - return false; - } - } - */ - /** - * Verifies that additional information may be sent. In other words, the + * Verifies that additional information may be sent. In other words, the * operation is not done. - * * @throws IOException if the operation is completed */ public void ensureNotDone() throws IOException { @@ -436,7 +355,6 @@ public final class ClientOperation implements Operation, BaseStream { /** * Verifies that the connection is open and no exceptions should be thrown. - * * @throws IOException if an exception needs to be thrown */ public void ensureOpen() throws IOException { @@ -452,7 +370,6 @@ public final class ClientOperation implements Operation, BaseStream { /** * Verifies that the connection is open and the proper data has been read. - * * @throws IOException if an IO error occurs */ private void validateConnection() throws IOException { @@ -466,15 +383,12 @@ public final class ClientOperation implements Operation, BaseStream { /** * Sends a request to the client of the specified type - * - * @param response the response code to send back to the client - * + * @param opCode the request code to send to the client * @return <code>true</code> if there is more data to send; - * <code>false</code> if there is no more data to send - * + * <code>false</code> if there is no more data to send * @throws IOException if an IO error occurs */ - private boolean sendRequest(int type) throws IOException { + private boolean sendRequest(int opCode) throws IOException { boolean returnValue = false; ByteArrayOutputStream out = new ByteArrayOutputStream(); int bodyLength = -1; @@ -519,7 +433,7 @@ public final class ClientOperation implements Operation, BaseStream { byte[] sendHeader = new byte[end - start]; System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); - if (!mParent.sendRequest(type, sendHeader, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) { return false; } @@ -559,7 +473,7 @@ public final class ClientOperation implements Operation, BaseStream { * (End of Body) otherwise, we need to send 0x48 (Body) */ if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent) - && ((type & 0x80) != 0)) { + && ((opCode & 0x80) != 0)) { out.write(0x49); mEndOfBodySent = true; } else { @@ -577,7 +491,7 @@ public final class ClientOperation implements Operation, BaseStream { if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) { // only 0x82 or 0x83 can send 0x49 - if ((type & 0x80) == 0) { + if ((opCode & 0x80) == 0) { out.write(0x48); } else { out.write(0x49); @@ -591,13 +505,13 @@ public final class ClientOperation implements Operation, BaseStream { } if (out.size() == 0) { - if (!mParent.sendRequest(type, null, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) { return false; } return returnValue; } if ((out.size() > 0) - && (!mParent.sendRequest(type, out.toByteArray(), mReplyHeader, mPrivateInput))) { + && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) { return false; } @@ -610,10 +524,9 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * This method starts the processing thread results. It will send the - * initial request. If the response takes more then one packet, a thread + * This method starts the processing thread results. It will send the + * initial request. If the response takes more then one packet, a thread * will be started to handle additional requests - * * @throws IOException if an IO error occurs */ private synchronized void startProcessing() throws IOException { @@ -659,11 +572,10 @@ public final class ClientOperation implements Operation, BaseStream { /** * Continues the operation since there is no data to read. - * - * @param sendEmpty <code>true</code> if the operation should send an - * empty packet or not send anything if there is no data to send - * @param inStream <code>true</code> if the stream is input stream or - * is output stream + * @param sendEmpty <code>true</code> if the operation should send an empty + * packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or is + * output stream * @throws IOException if an IO error occurs */ public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) @@ -717,10 +629,8 @@ public final class ClientOperation implements Operation, BaseStream { /** * Called when the output or input stream is closed. - * * @param inStream <code>true</code> if the input stream is closed; - * <code>false</code> if the output stream is closed - * + * <code>false</code> if the output stream is closed * @throws IOException if an IO error occurs */ public void streamClosed(boolean inStream) throws IOException { @@ -804,7 +714,6 @@ public final class ClientOperation implements Operation, BaseStream { if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; } - } } } diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java index d5549222edde..093538330f5d 100644 --- a/obex/javax/obex/ClientSession.java +++ b/obex/javax/obex/ClientSession.java @@ -39,7 +39,6 @@ import java.io.OutputStream; /** * This class in an implementation of the OBEX ClientSession. - * * @hide */ public final class ClientSession extends ObexSession { @@ -163,7 +162,7 @@ public final class ClientSession extends ObexSession { } /** - * 0xCB Connection Id an identifier used for OBEX connection multiplexing + * 0xCB Connection Id an identifier used for OBEX connection multiplexing */ public void setConnectionID(long id) { if ((id < 0) || (id > 0xFFFFFFFFL)) { @@ -365,7 +364,6 @@ public final class ClientSession extends ObexSession { /** * Verifies that the connection is open. - * * @throws IOException if the connection is closed */ public synchronized void ensureOpen() throws IOException { @@ -375,9 +373,8 @@ public final class ClientSession extends ObexSession { } /** - * Set request inactive. - * Allows Put and get operation objects to tell this object when they are - * done. + * Set request inactive. Allows Put and get operation objects to tell this + * object when they are done. */ /*package*/synchronized void setRequestInactive() { mRequestActive = false; @@ -395,27 +392,17 @@ public final class ClientSession extends ObexSession { } /** - * Sends a standard request to the client. It will then wait for the reply - * and update the header set object provided. If any authentication - * headers (i.e. authentication challenge or authentication response) are - * received, they will be processed. - * + * Sends a standard request to the client. It will then wait for the reply + * and update the header set object provided. If any authentication headers + * (i.e. authentication challenge or authentication response) are received, + * they will be processed. * @param opCode the type of request to send to the client - * - * @param head the headers to send to the server - * - * @param challenge the nonce that was sent in the authentication - * challenge header located in <code>head</code>; <code>null</code> - * if no authentication header is included in <code>head</code> - * + * @param head the headers to send to the client * @param header the header object to update with the response - * - * @param input the input stream used by the Operation object; null if this - * is called on a CONNECT, SETPATH or DISCONNECT - * - * return <code>true</code> if the operation completed successfully; - * <code>false</code> if an authentication response failed to pass - * + * @param privateInput the input stream used by the Operation object; null + * if this is called on a CONNECT, SETPATH or DISCONNECT return + * <code>true</code> if the operation completed successfully; + * <code>false</code> if an authentication response failed to pass * @throws IOException if an IO error occurs */ public boolean sendRequest(int opCode, byte[] head, HeaderSet header, diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java index f777da65cd93..8b457f66ece2 100644 --- a/obex/javax/obex/HeaderSet.java +++ b/obex/javax/obex/HeaderSet.java @@ -40,28 +40,27 @@ import java.util.Random; /** * This class implements the javax.obex.HeaderSet interface for OBEX over * RFCOMM. - * * @hide */ public final class HeaderSet { /** - * Represents the OBEX Count header. This allows the connection statement - * to tell the server how many objects it plans to send or retrieve. + * Represents the OBEX Count header. This allows the connection statement to + * tell the server how many objects it plans to send or retrieve. * <P> * The value of <code>COUNT</code> is 0xC0 (192). */ public static final int COUNT = 0xC0; /** - * Represents the OBEX Name header. This specifies the name of the object. + * Represents the OBEX Name header. This specifies the name of the object. * <P> * The value of <code>NAME</code> is 0x01 (1). */ public static final int NAME = 0x01; /** - * Represents the OBEX Type header. This allows a request to specify the + * Represents the OBEX Type header. This allows a request to specify the * type of the object (e.g. text, html, binary, etc.). * <P> * The value of <code>TYPE</code> is 0x42 (66). @@ -69,7 +68,7 @@ public final class HeaderSet { public static final int TYPE = 0x42; /** - * Represents the OBEX Length header. This is the length of the object in + * Represents the OBEX Length header. This is the length of the object in * bytes. * <P> * The value of <code>LENGTH</code> is 0xC3 (195). @@ -77,32 +76,32 @@ public final class HeaderSet { public static final int LENGTH = 0xC3; /** - * Represents the OBEX Time header using the ISO 8601 standards. This is - * the preferred time header. + * Represents the OBEX Time header using the ISO 8601 standards. This is the + * preferred time header. * <P> * The value of <code>TIME_ISO_8601</code> is 0x44 (68). */ public static final int TIME_ISO_8601 = 0x44; /** - * Represents the OBEX Time header using the 4 byte representation. This - * is only included for backwards compatibility. It represents the number - * of seconds since January 1, 1970. + * Represents the OBEX Time header using the 4 byte representation. This is + * only included for backwards compatibility. It represents the number of + * seconds since January 1, 1970. * <P> * The value of <code>TIME_4_BYTE</code> is 0xC4 (196). */ public static final int TIME_4_BYTE = 0xC4; /** - * Represents the OBEX Description header. This is a text description of - * the object. + * Represents the OBEX Description header. This is a text description of the + * object. * <P> * The value of <code>DESCRIPTION</code> is 0x05 (5). */ public static final int DESCRIPTION = 0x05; /** - * Represents the OBEX Target header. This is the name of the service an + * Represents the OBEX Target header. This is the name of the service an * operation is targeted to. * <P> * The value of <code>TARGET</code> is 0x46 (70). @@ -110,7 +109,7 @@ public final class HeaderSet { public static final int TARGET = 0x46; /** - * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be + * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be * included in a request or reply. * <P> * The value of <code>HTTP</code> is 0x47 (71). @@ -132,7 +131,7 @@ public final class HeaderSet { public static final int END_OF_BODY = 0x49; /** - * Represents the OBEX Who header. Identifies the OBEX application to + * Represents the OBEX Who header. Identifies the OBEX application to * determine if the two peers are talking to each other. * <P> * The value of <code>WHO</code> is 0x4A (74). @@ -149,7 +148,7 @@ public final class HeaderSet { public static final int CONNECTION_ID = 0xCB; /** - * Represents the OBEX Application Parameter header. This header specifies + * Represents the OBEX Application Parameter header. This header specifies * additional application request and response information. * <P> * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76). @@ -171,8 +170,8 @@ public final class HeaderSet { public static final int AUTH_RESPONSE = 0x4E; /** - * Represents the OBEX Object Class header. This header specifies the - * OBEX object class of the object. + * Represents the OBEX Object Class header. This header specifies the OBEX + * object class of the object. * <P> * The value of <code>OBJECT_CLASS</code> is 0x4F (79). */ @@ -200,12 +199,6 @@ public final class HeaderSet { private byte[] mAppParam; // byte sequence of the form tag length value - public byte[] mAuthChall; // The authentication challenge header - - public byte[] mAuthResp; // The authentication response header - - public byte[] mConnectionID; // THe connection ID - private byte[] mObjectClass; // byte sequence private String[] mUnicodeUserDefined; //null terminated unicode string @@ -216,15 +209,20 @@ public final class HeaderSet { private Long[] mIntegerUserDefined; // 4 byte unsigned integer - /*package*/int responseCode; + private final Random mRandom; - /*package*/byte[] nonce; + /*package*/ byte[] nonce; - private final Random mRandom; + public byte[] mAuthChall; // The authentication challenge header + + public byte[] mAuthResp; // The authentication response header + + public byte[] mConnectionID; // THe connection ID + + public int responseCode; /** * Creates new <code>HeaderSet</code> object. - * * @param size the max packet size for this connection */ public HeaderSet() { @@ -237,20 +235,17 @@ public final class HeaderSet { } /** - * Sets the value of the header identifier to the value provided. The type + * Sets the value of the header identifier to the value provided. The type * of object must correspond to the Java type defined in the description of - * this interface. If <code>null</code> is passed as the + * this interface. If <code>null</code> is passed as the * <code>headerValue</code> then the header will be removed from the set of * headers to include in the next request. - * * @param headerID the identifier to include in the message - * * @param headerValue the value of the header identifier - * - * @throws IllegalArgumentException if the header identifier provided is - * not one defined in this interface or a user-defined header; if the type of - * <code>headerValue</code> is not the correct Java type as defined in the - * description of this interface\ + * @throws IllegalArgumentException if the header identifier provided is not + * one defined in this interface or a user-defined header; if the + * type of <code>headerValue</code> is not the correct Java type as + * defined in the description of this interface\ */ public void setHeader(int headerID, Object headerValue) { long temp = -1; @@ -435,20 +430,16 @@ public final class HeaderSet { } /** - * Retrieves the value of the header identifier provided. The type of the + * Retrieves the value of the header identifier provided. The type of the * Object returned is defined in the description of this interface. - * * @param headerID the header identifier whose value is to be returned - * * @return the value of the header provided or <code>null</code> if the - * header identifier specified is not part of this <code>HeaderSet</code> - * object - * - * @throws IllegalArgumentException if the <code>headerID</code> is not - * one defined in this interface or any of the user-defined headers - * + * header identifier specified is not part of this + * <code>HeaderSet</code> object + * @throws IllegalArgumentException if the <code>headerID</code> is not one + * defined in this interface or any of the user-defined headers * @throws IOException if an error occurred in the transport layer during - * the operation or if the connection has been closed + * the operation or if the connection has been closed */ public Object getHeader(int headerID) throws IOException { @@ -500,17 +491,14 @@ public final class HeaderSet { /** * Retrieves the list of headers that may be retrieved via the - * <code>getHeader</code> method that will not return <code>null</code>. - * In other words, this method returns all the headers that are available - * in this object. - * + * <code>getHeader</code> method that will not return <code>null</code>. In + * other words, this method returns all the headers that are available in + * this object. * @see #getHeader - * * @return the array of headers that are set in this object or - * <code>null</code> if no headers are available - * + * <code>null</code> if no headers are available * @throws IOException if an error occurred in the transport layer during - * the operation or the connection has been closed + * the operation or the connection has been closed */ public int[] getHeaderList() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -594,51 +582,41 @@ public final class HeaderSet { } /** - * Sets the authentication challenge header. The <code>realm</code> will - * be encoded based upon the default encoding scheme used by the - * implementation to encode strings. Therefore, the encoding scheme used - * to encode the <code>realm</code> is application dependent. - * + * Sets the authentication challenge header. The <code>realm</code> will be + * encoded based upon the default encoding scheme used by the implementation + * to encode strings. Therefore, the encoding scheme used to encode the + * <code>realm</code> is application dependent. * @param realm a short description that describes what password to use; if - * <code>null</code> no realm will be sent in the authentication challenge - * header - * + * <code>null</code> no realm will be sent in the authentication + * challenge header * @param userID if <code>true</code>, a user ID is required in the reply; - * if <code>false</code>, no user ID is required - * + * if <code>false</code>, no user ID is required * @param access if <code>true</code> then full access will be granted if - * successful; if <code>false</code> then read-only access will be granted - * if successful + * successful; if <code>false</code> then read-only access will be + * granted if successful * @throws IOException */ public void createAuthenticationChallenge(String realm, boolean userID, boolean access) throws IOException { - try { - nonce = new byte[16]; - for (int i = 0; i < 16; i++) { - nonce[i] = (byte)mRandom.nextInt(); - } - - mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID); - } catch (IOException e) { - throw e; + nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte)mRandom.nextInt(); } + + mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID); } /** - * Returns the response code received from the server. Response codes - * are defined in the <code>ResponseCodes</code> class. - * + * Returns the response code received from the server. Response codes are + * defined in the <code>ResponseCodes</code> class. * @see ResponseCodes - * * @return the response code retrieved from the server - * * @throws IOException if an error occurred in the transport layer during - * the transaction; if this method is called on a <code>HeaderSet</code> - * object created by calling <code>createHeaderSet()</code> in a - * <code>ClientSession</code> object; if this object was created by an OBEX - * server + * the transaction; if this method is called on a + * <code>HeaderSet</code> object created by calling + * <code>createHeaderSet()</code> in a <code>ClientSession</code> + * object; if this object was created by an OBEX server */ public int getResponseCode() throws IOException { if (responseCode == -1) { diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java index 511b7c667531..f5695954f000 100644 --- a/obex/javax/obex/ObexHelper.java +++ b/obex/javax/obex/ObexHelper.java @@ -43,16 +43,14 @@ import java.util.TimeZone; /** * This class defines a set of helper methods for the implementation of Obex. - * * @hide */ public final class ObexHelper { /** - * Defines the basic packet length used by OBEX. Every OBEX packet has the + * Defines the basic packet length used by OBEX. Every OBEX packet has the * same basic format:<BR> - * Byte 0: Request or Response Code - * Byte 1&2: Length of the packet. + * Byte 0: Request or Response Code Byte 1&2: Length of the packet. */ public static final int BASE_PACKET_LENGTH = 3; @@ -61,17 +59,14 @@ public final class ObexHelper { } /** - * The maximum packet size for OBEX packets that this client can handle. - * At present, this must be changed for each port. - * - * TODO: The max packet size should be the Max incoming MTU minus - * TODO: L2CAP package headers and RFCOMM package headers. - * - * TODO: Retrieve the max incoming MTU from - * TODO: LocalDevice.getProperty(). + * The maximum packet size for OBEX packets that this client can handle. At + * present, this must be changed for each port. TODO: The max packet size + * should be the Max incoming MTU minus TODO: L2CAP package headers and + * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO: + * LocalDevice.getProperty(). */ - /** android note - * set as 0xFFFE to match remote MPS + /* + * android note set as 0xFFFE to match remote MPS */ public static final int MAX_PACKET_SIZE_INT = 0xFFFE; @@ -119,34 +114,46 @@ public final class ObexHelper { /** * Updates the HeaderSet with the headers received in the byte array - * provided. Invalid headers are ignored. + * provided. Invalid headers are ignored. * <P> - * The first two bits of an OBEX Header specifies the type of object that - * is being sent. The table below specifies the meaning of the high - * bits. + * The first two bits of an OBEX Header specifies the type of object that is + * being sent. The table below specifies the meaning of the high bits. * <TABLE> - * <TR><TH>Bits 8 and 7</TH><TH>Value</TH><TH>Description</TH></TR> - * <TR><TD>00</TD><TD>0x00</TD><TD>Null Terminated Unicode text, prefixed - * with 2 byte unsigned integer</TD></TR> - * <TR><TD>01</TD><TD>0x40</TD><TD>Byte Sequence, length prefixed with - * 2 byte unsigned integer</TD></TR> - * <TR><TD>10</TD><TD>0x80</TD><TD>1 byte quantity</TD></TR> - * <TR><TD>11</TD><TD>0xC0</TD><TD>4 byte quantity - transmitted in - * network byte order (high byte first</TD></TR> + * <TR> + * <TH>Bits 8 and 7</TH> + * <TH>Value</TH> + * <TH>Description</TH> + * </TR> + * <TR> + * <TD>00</TD> + * <TD>0x00</TD> + * <TD>Null Terminated Unicode text, prefixed with 2 byte unsigned integer</TD> + * </TR> + * <TR> + * <TD>01</TD> + * <TD>0x40</TD> + * <TD>Byte Sequence, length prefixed with 2 byte unsigned integer</TD> + * </TR> + * <TR> + * <TD>10</TD> + * <TD>0x80</TD> + * <TD>1 byte quantity</TD> + * </TR> + * <TR> + * <TD>11</TD> + * <TD>0xC0</TD> + * <TD>4 byte quantity - transmitted in network byte order (high byte first</TD> + * </TR> * </TABLE> * This method uses the information in this table to determine the type of - * Java object to create and passes that object with the full header - * to setHeader() to update the HeaderSet object. Invalid headers will - * cause an exception to be thrown. When it is thrown, it is ignored. - * + * Java object to create and passes that object with the full header to + * setHeader() to update the HeaderSet object. Invalid headers will cause an + * exception to be thrown. When it is thrown, it is ignored. * @param header the HeaderSet to update - * * @param headerArray the byte array containing headers - * * @return the result of the last start body or end body header provided; - * the first byte in the result will specify if a body or end of body is - * received - * + * the first byte in the result will specify if a body or end of + * body is received * @throws IOException if an invalid header was found */ public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException { @@ -316,18 +323,13 @@ public final class ObexHelper { /** * Creates the header part of OBEX packet based on the header provided. - * - * TODO: Could use getHeaderList() to get the array of headers to - * TODO: include and then use the high two bits to determine the - * TODO: the type of the object and construct the byte array from - * TODO: that. This will make the size smaller. - * + * TODO: Could use getHeaderList() to get the array of headers to include + * and then use the high two bits to determine the the type of the object + * and construct the byte array from that. This will make the size smaller. * @param head the header used to construct the byte array - * * @param nullOut <code>true</code> if the header should be set to - * <code>null</code> once it is added to the array or <code>false</code> - * if it should not be nulled out - * + * <code>null</code> once it is added to the array or + * <code>false</code> if it should not be nulled out * @return the header of an OBEX packet */ public static byte[] createHeader(HeaderSet head, boolean nullOut) { @@ -342,9 +344,6 @@ public final class ObexHelper { int length; HeaderSet headImpl = null; ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (!(head instanceof HeaderSet)) { - throw new IllegalArgumentException("Header not created by createHeaderSet"); - } headImpl = head; try { @@ -675,18 +674,14 @@ public final class ObexHelper { } /** - * Determines where the maximum divide is between headers. This method is + * Determines where the maximum divide is between headers. This method is * used by put and get operations to separate headers to a size that meets * the max packet size allowed. - * * @param headerArray the headers to separate - * * @param start the starting index to search - * * @param maxSize the maximum size of a packet - * * @return the index of the end of the header block to send or -1 if the - * header could not be divided because the header is too large + * header could not be divided because the header is too large */ public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) { @@ -757,9 +752,7 @@ public final class ObexHelper { /** * Converts the byte array to a long. - * * @param b the byte array to convert to a long - * * @return the byte array as a long */ public static long convertToLong(byte[] b) { @@ -781,10 +774,8 @@ public final class ObexHelper { } /** - * Converts the long to a 4 byte array. The long must be non negative. - * + * Converts the long to a 4 byte array. The long must be non negative. * @param l the long to convert - * * @return a byte array that is the same as the long */ public static byte[] convertToByteArray(long l) { @@ -799,11 +790,9 @@ public final class ObexHelper { } /** - * Converts the String to a UNICODE byte array. It will also add the ending + * Converts the String to a UNICODE byte array. It will also add the ending * null characters to the end of the string. - * * @param s the string to convert - * * @return the unicode byte array of the string */ public static byte[] convertToUnicodeByteArray(String s) { @@ -826,13 +815,10 @@ public final class ObexHelper { } /** - * Retrieves the value from the byte array for the tag value specified. The + * Retrieves the value from the byte array for the tag value specified. The * array should be of the form Tag - Length - Value triplet. - * * @param tag the tag to retrieve from the byte array - * * @param triplet the byte sequence containing the tag length value form - * * @return the value of the specified tag */ public static byte[] getTagValue(byte tag, byte[] triplet) { @@ -854,11 +840,8 @@ public final class ObexHelper { /** * Finds the index that starts the tag value pair in the byte array provide. - * * @param tag the tag to look for - * * @param value the byte array to search - * * @return the starting index of the tag or -1 if the tag could not be found */ public static int findTag(byte tag, byte[] value) { @@ -884,19 +867,15 @@ public final class ObexHelper { /** * Converts the byte array provided to a unicode string. - * * @param b the byte array to convert to a string - * * @param includesNull determine if the byte string provided contains the - * UNICODE null character at the end or not; if it does, it will be - * removed - * + * UNICODE null character at the end or not; if it does, it will be + * removed * @return a Unicode string - * - * @param IllegalArgumentException if the byte array has an odd length + * @throws IllegalArgumentException if the byte array has an odd length */ public static String convertToUnicode(byte[] b, boolean includesNull) { - if (b == null) { + if (b == null || b.length == 0) { return null; } int arrayLength = b.length; @@ -926,9 +905,8 @@ public final class ObexHelper { } /** - * Compute the MD5 hash of the byte array provided. - * Does not accumulate input. - * + * Compute the MD5 hash of the byte array provided. Does not accumulate + * input. * @param in the byte array to hash * @return the MD5 hash of the byte array */ @@ -939,23 +917,16 @@ public final class ObexHelper { /** * Computes an authentication challenge header. - * - * - * @param nonce the challenge that will be provided to the peer; the - * challenge must be 16 bytes long - * + * @param nonce the challenge that will be provided to the peer; the + * challenge must be 16 bytes long * @param realm a short description that describes what password to use - * * @param access if <code>true</code> then full access will be granted if - * successful; if <code>false</code> then read only access will be granted - * if successful - * + * successful; if <code>false</code> then read only access will be + * granted if successful * @param userID if <code>true</code>, a user ID is required in the reply; - * if <code>false</code>, no user ID is required - * - * @throws IllegalArgumentException if the challenge is not 16 bytes - * long; if the realm can not be encoded in less then 255 bytes - * + * if <code>false</code>, no user ID is required + * @throws IllegalArgumentException if the challenge is not 16 bytes long; + * if the realm can not be encoded in less then 255 bytes * @throws IOException if the encoding scheme ISO 8859-1 is not supported */ public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access, diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java index 97d65e099d2f..a7daeb533e16 100644 --- a/obex/javax/obex/ObexSession.java +++ b/obex/javax/obex/ObexSession.java @@ -41,10 +41,8 @@ import java.io.IOException; * of the same connection, which is established by server's accepting of a * client issued "CONNECT". * <P> - * * This interface serves as the common super class for * <CODE>ClientSession</CODE> and <CODE>ServerSession</CODE>. - * * @hide */ public class ObexSession { @@ -56,10 +54,7 @@ public class ObexSession { /** * Called when the server received an authentication challenge header. This * will cause the authenticator to handle the authentication challenge. - * - * @param header - * the header with the authentication challenge - * + * @param header the header with the authentication challenge * @return <code>true</code> if the last request should be resent; * <code>false</code> if the last request should not be resent * @throws IOException @@ -188,10 +183,7 @@ public class ObexSession { /** * Called when the server received an authentication response header. This * will cause the authenticator to handle the authentication response. - * - * @param authResp - * the authentication response - * + * @param authResp the authentication response * @return <code>true</code> if the response passed; <code>false</code> if * the response failed */ diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java index d0ba0c92f10f..445e2675b212 100644 --- a/obex/javax/obex/ObexTransport.java +++ b/obex/javax/obex/ObexTransport.java @@ -51,7 +51,6 @@ import java.io.OutputStream; * Different kind of medium may have different construction - for example, the * RFCOMM device file medium may be constructed from a file descriptor or simply * a string while the TCP medium usually from a socket. - * * @hide */ public interface ObexTransport { diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java index f265f5335999..20653f26df2d 100644 --- a/obex/javax/obex/Operation.java +++ b/obex/javax/obex/Operation.java @@ -40,44 +40,39 @@ import java.io.OutputStream; /** * The <code>Operation</code> interface provides ways to manipulate a single - * OBEX PUT or GET operation. The implementation of this interface sends - * OBEX packets as they are built. If during the operation the peer in the - * operation ends the operation, an <code>IOException</code> is thrown on - * the next read from the input stream, write to the output stream, or call to + * OBEX PUT or GET operation. The implementation of this interface sends OBEX + * packets as they are built. If during the operation the peer in the operation + * ends the operation, an <code>IOException</code> is thrown on the next read + * from the input stream, write to the output stream, or call to * <code>sendHeaders()</code>. * <P> - * <STRONG>Definition of methods inherited from <code>ContentConnection</code></STRONG> + * <STRONG>Definition of methods inherited from <code>ContentConnection</code> + * </STRONG> * <P> - * <code>getEncoding()</code> will always return <code>null</code>. - * <BR><code>getLength()</code> will return the length specified by the OBEX Length - * header or -1 if the OBEX Length header was not included. - * <BR><code>getType()</code> will return the value specified in the OBEX Type + * <code>getEncoding()</code> will always return <code>null</code>. <BR> + * <code>getLength()</code> will return the length specified by the OBEX Length + * header or -1 if the OBEX Length header was not included. <BR> + * <code>getType()</code> will return the value specified in the OBEX Type * header or <code>null</code> if the OBEX Type header was not included.<BR> * <P> * <STRONG>How Headers are Handled</STRONG> * <P> * As headers are received, they may be retrieved through the - * <code>getReceivedHeaders()</code> method. If new headers are set during the + * <code>getReceivedHeaders()</code> method. If new headers are set during the * operation, the new headers will be sent during the next packet exchange. * <P> * <STRONG>PUT example</STRONG> * <P> * <PRE> - * void putObjectViaOBEX(ClientSession conn, HeaderSet head, byte[] obj) - * throws IOException { - * + * void putObjectViaOBEX(ClientSession conn, HeaderSet head, byte[] obj) throws IOException { * // Include the length header * head.setHeader(head.LENGTH, new Long(obj.length)); - * * // Initiate the PUT request * Operation op = conn.put(head); - * * // Open the output stream to put the object to it * DataOutputStream out = op.openDataOutputStream(); - * * // Send the object to the server * out.write(obj); - * * // End the transaction * out.close(); * op.close(); @@ -88,64 +83,55 @@ import java.io.OutputStream; * <P> * <PRE> * byte[] getObjectViaOBEX(ClientSession conn, HeaderSet head) throws IOException { - * * // Send the initial GET request to the server * Operation op = conn.get(head); - * * // Retrieve the length of the object being sent back * int length = op.getLength(); - * - * // Create space for the object - * byte[] obj = new byte[length]; - * + * // Create space for the object + * byte[] obj = new byte[length]; * // Get the object from the input stream * DataInputStream in = trans.openDataInputStream(); * in.read(obj); - * * // End the transaction * in.close(); * op.close(); - * * return obj; * } * </PRE> - * <H3>Client PUT Operation Flow</H3> - * For PUT operations, a call to <code>close()</code> the <code>OutputStream</code> - * returned from <code>openOutputStream()</code> or <code>openDataOutputStream()</code> - * will signal that the request is done. (In OBEX terms, the End-Of-Body header should - * be sent and the final bit in the request will be set.) At this point, the - * reply from the server may begin to be processed. A call to + * + * <H3>Client PUT Operation Flow</H3> For PUT operations, a call to + * <code>close()</code> the <code>OutputStream</code> returned from + * <code>openOutputStream()</code> or <code>openDataOutputStream()</code> will + * signal that the request is done. (In OBEX terms, the End-Of-Body header + * should be sent and the final bit in the request will be set.) At this point, + * the reply from the server may begin to be processed. A call to * <code>getResponseCode()</code> will do an implicit close on the * <code>OutputStream</code> and therefore signal that the request is done. - * <H3>Client GET Operation Flow</H3> - * For GET operation, a call to <code>openInputStream()</code> or - * <code>openDataInputStream()</code> will signal that the request is done. (In OBEX - * terms, the final bit in the request will be set.) A call to - * <code>getResponseCode()</code> will cause an implicit close on the - * <code>InputStream</code>. No further data may be read at this point. - * + * <H3>Client GET Operation Flow</H3> For GET operation, a call to + * <code>openInputStream()</code> or <code>openDataInputStream()</code> will + * signal that the request is done. (In OBEX terms, the final bit in the request + * will be set.) A call to <code>getResponseCode()</code> will cause an implicit + * close on the <code>InputStream</code>. No further data may be read at this + * point. * @hide */ public interface Operation { /** - * Sends an ABORT message to the server. By calling this method, the + * Sends an ABORT message to the server. By calling this method, the * corresponding input and output streams will be closed along with this - * object. No headers are sent in the abort request. This will end the + * object. No headers are sent in the abort request. This will end the * operation since <code>close()</code> will be called by this method. - * - * @throws IOException if the transaction has already ended or if an - * OBEX server calls this method + * @throws IOException if the transaction has already ended or if an OBEX + * server calls this method */ void abort() throws IOException; /** * Returns the headers that have been received during the operation. - * Modifying the object returned has no effect on the headers that are - * sent or retrieved. - * + * Modifying the object returned has no effect on the headers that are sent + * or retrieved. * @return the headers received during this <code>Operation</code> - * * @throws IOException if this <code>Operation</code> has been closed */ HeaderSet getReceivedHeader() throws IOException; @@ -153,30 +139,23 @@ public interface Operation { /** * Specifies the headers that should be sent in the next OBEX message that * is sent. - * * @param headers the headers to send in the next message - * - * @throws IOException if this <code>Operation</code> has been closed - * or the transaction has ended and no further messages will be exchanged - * + * @throws IOException if this <code>Operation</code> has been closed or the + * transaction has ended and no further messages will be exchanged * @throws IllegalArgumentException if <code>headers</code> was not created - * by a call to <code>ServerRequestHandler.createHeaderSet()</code> or - * <code>ClientSession.createHeaderSet()</code> - * + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + * or <code>ClientSession.createHeaderSet()</code> * @throws NullPointerException if <code>headers</code> if <code>null</code> */ void sendHeaders(HeaderSet headers) throws IOException; /** - * Returns the response code received from the server. Response codes - * are defined in the <code>ResponseCodes</code> class. - * + * Returns the response code received from the server. Response codes are + * defined in the <code>ResponseCodes</code> class. * @see ResponseCodes - * * @return the response code retrieved from the server - * * @throws IOException if an error occurred in the transport layer during - * the transaction; if this object was created by an OBEX server + * the transaction; if this object was created by an OBEX server */ int getResponseCode() throws IOException; diff --git a/obex/javax/obex/PasswordAuthentication.java b/obex/javax/obex/PasswordAuthentication.java index e81a8611391a..326b1ff56a74 100644 --- a/obex/javax/obex/PasswordAuthentication.java +++ b/obex/javax/obex/PasswordAuthentication.java @@ -34,7 +34,6 @@ package javax.obex; /** * This class holds user name and password combinations. - * * @hide */ public final class PasswordAuthentication { @@ -44,15 +43,12 @@ public final class PasswordAuthentication { private final byte[] mPassword; /** - * Creates a new <code>PasswordAuthentication</code> with the user name - * and password provided. - * + * Creates a new <code>PasswordAuthentication</code> with the user name and + * password provided. * @param userName the user name to include; this may be <code>null</code> - * * @param password the password to include in the response - * * @throws NullPointerException if <code>password</code> is - * <code>null</code> + * <code>null</code> */ public PasswordAuthentication(final byte[] userName, final byte[] password) { if (userName != null) { @@ -65,9 +61,8 @@ public final class PasswordAuthentication { } /** - * Retrieves the user name that was specified in the constructor. - * The user name may be <code>null</code>. - * + * Retrieves the user name that was specified in the constructor. The user + * name may be <code>null</code>. * @return the user name */ public byte[] getUserName() { @@ -76,7 +71,6 @@ public final class PasswordAuthentication { /** * Retrieves the password. - * * @return the password */ public byte[] getPassword() { diff --git a/obex/javax/obex/PrivateInputStream.java b/obex/javax/obex/PrivateInputStream.java index 2dc02da511f1..5daee72474fc 100644 --- a/obex/javax/obex/PrivateInputStream.java +++ b/obex/javax/obex/PrivateInputStream.java @@ -38,7 +38,6 @@ import java.io.IOException; /** * This object provides an input stream to the Operation objects used in this * package. - * * @hide */ public final class PrivateInputStream extends InputStream { @@ -53,7 +52,6 @@ public final class PrivateInputStream extends InputStream { /** * Creates an input stream for the <code>Operation</code> to read from - * * @param p the connection this input stream is for */ public PrivateInputStream(BaseStream p) { @@ -68,10 +66,8 @@ public final class PrivateInputStream extends InputStream { * input stream without blocking by the next caller of a method for this * input stream. The next caller might be the same thread or or another * thread. - * * @return the number of bytes that can be read from this input stream - * without blocking - * + * without blocking * @throws IOException if an I/O error occurs */ @Override @@ -82,14 +78,12 @@ public final class PrivateInputStream extends InputStream { /** * Reads the next byte of data from the input stream. The value byte is - * returned as an int in the range 0 to 255. If no byte is available - * because the end of the stream has been reached, the value -1 is - * returned. This method blocks until input data is available, the end of - * the stream is detected, or an exception is thrown. - * - * @return the byte read from the input stream or -1 if it reaches the end - * of stream - * + * returned as an int in the range 0 to 255. If no byte is available because + * the end of the stream has been reached, the value -1 is returned. This + * method blocks until input data is available, the end of the stream is + * detected, or an exception is thrown. + * @return the byte read from the input stream or -1 if it reaches the end of + * stream * @throws IOException if an I/O error occurs */ @Override @@ -147,9 +141,7 @@ public final class PrivateInputStream extends InputStream { /** * Allows the <code>OperationImpl</code> thread to add body data to the * input stream. - * * @param body the data to add to the stream - * * @param start the start of the body to array to copy */ public synchronized void writeBytes(byte[] body, int start) { @@ -167,7 +159,6 @@ public final class PrivateInputStream extends InputStream { /** * Verifies that this stream is open - * * @throws IOException if the stream is not open */ private void ensureOpen() throws IOException { @@ -178,9 +169,8 @@ public final class PrivateInputStream extends InputStream { } /** - * Closes the input stream. If the input stream is already closed, do + * Closes the input stream. If the input stream is already closed, do * nothing. - * * @throws IOException this will never happen */ @Override diff --git a/obex/javax/obex/PrivateOutputStream.java b/obex/javax/obex/PrivateOutputStream.java index d972f78a22ad..ca420afdda37 100644 --- a/obex/javax/obex/PrivateOutputStream.java +++ b/obex/javax/obex/PrivateOutputStream.java @@ -39,7 +39,6 @@ import java.io.ByteArrayOutputStream; /** * This object provides an output stream to the Operation objects used in this * package. - * * @hide */ public final class PrivateOutputStream extends OutputStream { @@ -54,18 +53,17 @@ public final class PrivateOutputStream extends OutputStream { /** * Creates an empty <code>PrivateOutputStream</code> to write to. - * * @param p the connection that this stream runs over */ public PrivateOutputStream(BaseStream p, int maxSize) { mParent = p; mArray = new ByteArrayOutputStream(); mMaxPacketSize = maxSize; + mOpen = true; } /** * Determines how many bytes have been written to the output stream. - * * @return the number of bytes written to the output stream */ public int size() { @@ -73,13 +71,11 @@ public final class PrivateOutputStream extends OutputStream { } /** - * Writes the specified byte to this output stream. The general contract - * for write is that one byte is written to the output stream. The byte to - * be written is the eight low-order bits of the argument b. The 24 - * high-order bits of b are ignored. - * + * Writes the specified byte to this output stream. The general contract for + * write is that one byte is written to the output stream. The byte to be + * written is the eight low-order bits of the argument b. The 24 high-order + * bits of b are ignored. * @param b the byte to write - * * @throws IOException if an I/O error occurs */ @Override @@ -128,9 +124,7 @@ public final class PrivateOutputStream extends OutputStream { /** * Reads the bytes that have been written to this stream. - * * @param size the size of the array to return - * * @return the byte array that is written */ public synchronized byte[] readBytes(int size) { @@ -150,7 +144,6 @@ public final class PrivateOutputStream extends OutputStream { /** * Verifies that this stream is open - * * @throws IOException if the stream is not open */ private void ensureOpen() throws IOException { @@ -161,9 +154,8 @@ public final class PrivateOutputStream extends OutputStream { } /** - * Closes the output stream. If the input stream is already closed, do + * Closes the output stream. If the input stream is already closed, do * nothing. - * * @throws IOException this will never happen */ @Override @@ -174,9 +166,8 @@ public final class PrivateOutputStream extends OutputStream { /** * Determines if the connection is closed - * - * @return <code>true</code> if the connection is closed; - * <code>false</code> if the connection is open + * @return <code>true</code> if the connection is closed; <code>false</code> + * if the connection is open */ public boolean isClosed() { return !mOpen; diff --git a/obex/javax/obex/ResponseCodes.java b/obex/javax/obex/ResponseCodes.java index f6cc9c16a95f..a2b9a37b4af1 100644 --- a/obex/javax/obex/ResponseCodes.java +++ b/obex/javax/obex/ResponseCodes.java @@ -33,8 +33,8 @@ package javax.obex; /** - * The <code>ResponseCodes</code> class contains the list of valid - * response codes a server may send to a client. + * The <code>ResponseCodes</code> class contains the list of valid response + * codes a server may send to a client. * <P> * <STRONG>IMPORTANT NOTE</STRONG> * <P> @@ -42,13 +42,12 @@ package javax.obex; * specification, which is different with the HTTP specification. * <P> * <code>OBEX_DATABASE_FULL</code> and <code>OBEX_DATABASE_LOCKED</code> require - * further description since they are not defined in HTTP. The server will send + * further description since they are not defined in HTTP. The server will send * an <code>OBEX_DATABASE_FULL</code> message when the client requests that * something be placed into a database but the database is full (cannot take - * more data). <code>OBEX_DATABASE_LOCKED</code> will be returned when the + * more data). <code>OBEX_DATABASE_LOCKED</code> will be returned when the * client wishes to access a database, database table, or database record that * has been locked. - * * @hide */ public final class ResponseCodes { diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java index 6c3d9ba1801e..8710c64a46d6 100644 --- a/obex/javax/obex/ServerOperation.java +++ b/obex/javax/obex/ServerOperation.java @@ -42,18 +42,14 @@ import java.io.ByteArrayOutputStream; /** * This class implements the Operation interface for server side connections. * <P> - * <STRONG>Request Codes</STRONG> - * There are four different request codes that are in this class. 0x02 is a - * PUT request that signals that the request is not complete and requires an - * additional OBEX packet. 0x82 is a PUT request that says that request is - * complete. In this case, the server can begin sending the response. The - * 0x03 is a GET request that signals that the request is not finished. When - * the server receives a 0x83, the client is signaling the server that it is - * done with its request. - * - * TODO: Extend the ClientOperation and reuse the methods defined - * TODO: in that class. - * + * <STRONG>Request Codes</STRONG> There are four different request codes that + * are in this class. 0x02 is a PUT request that signals that the request is not + * complete and requires an additional OBEX packet. 0x82 is a PUT request that + * says that request is complete. In this case, the server can begin sending the + * response. The 0x03 is a GET request that signals that the request is not + * finished. When the server receives a 0x83, the client is signaling the server + * that it is done with its request. TODO: Extend the ClientOperation and reuse + * the methods defined TODO: in that class. * @hide */ public final class ServerOperation implements Operation, BaseStream { @@ -94,19 +90,12 @@ public final class ServerOperation implements Operation, BaseStream { /** * Creates new ServerOperation - * * @param p the parent that created this object - * * @param in the input stream to read from - * * @param out the output stream to write to - * * @param request the initial request that was received from the client - * * @param maxSize the max packet size that the client will accept - * * @param listen the listener that is responding to the request - * * @throws IOException if an IO error occurs */ public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, @@ -240,17 +229,16 @@ public final class ServerOperation implements Operation, BaseStream { } /** - * Determines if the operation should continue or should wait. If it - * should continue, this method will continue the operation. - * + * Determines if the operation should continue or should wait. If it should + * continue, this method will continue the operation. * @param sendEmpty if <code>true</code> then this will continue the - * operation even if no headers will be sent; if <code>false</code> then - * this method will only continue the operation if there are headers to - * send - * @param isStream if<code>true</code> the stream is input stream, otherwise - * output stream + * operation even if no headers will be sent; if <code>false</code> + * then this method will only continue the operation if there are + * headers to send + * @param inStream if<code>true</code> the stream is input stream, otherwise + * output stream * @return <code>true</code> if the operation was completed; - * <code>false</code> if no operation took place + * <code>false</code> if no operation took place */ public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException { @@ -277,15 +265,13 @@ public final class ServerOperation implements Operation, BaseStream { } /** - * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it + * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it * will wait for a response from the client before ending. - * * @param type the response code to send back to the client - * * @return <code>true</code> if the final bit was not set on the reply; - * <code>false</code> if no reply was received because the operation ended, - * an abort was received, or the final bit was set in the reply - * + * <code>false</code> if no reply was received because the operation + * ended, an abort was received, or the final bit was set in the + * reply * @throws IOException if an IO error occurs */ public synchronized boolean sendReply(int type) throws IOException { @@ -492,12 +478,11 @@ public final class ServerOperation implements Operation, BaseStream { } /** - * Sends an ABORT message to the server. By calling this method, the + * Sends an ABORT message to the server. By calling this method, the * corresponding input and output streams will be closed along with this * object. - * - * @throws IOException if the transaction has already ended or if an - * OBEX server called this method + * @throws IOException if the transaction has already ended or if an OBEX + * server called this method */ public void abort() throws IOException { throw new IOException("Called from a server"); @@ -505,11 +490,9 @@ public final class ServerOperation implements Operation, BaseStream { /** * Returns the headers that have been received during the operation. - * Modifying the object returned has no effect on the headers that are - * sent or retrieved. - * + * Modifying the object returned has no effect on the headers that are sent + * or retrieved. * @return the headers received during this <code>Operation</code> - * * @throws IOException if this <code>Operation</code> has been closed */ public HeaderSet getReceivedHeader() throws IOException { @@ -520,14 +503,11 @@ public final class ServerOperation implements Operation, BaseStream { /** * Specifies the headers that should be sent in the next OBEX message that * is sent. - * * @param headers the headers to send in the next message - * - * @throws IOException if this <code>Operation</code> has been closed - * or the transaction has ended and no further messages will be exchanged - * + * @throws IOException if this <code>Operation</code> has been closed or the + * transaction has ended and no further messages will be exchanged * @throws IllegalArgumentException if <code>headers</code> was not created - * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> */ public void sendHeaders(HeaderSet headers) throws IOException { ensureOpen(); @@ -546,15 +526,14 @@ public final class ServerOperation implements Operation, BaseStream { } /** - * Retrieves the response code retrieved from the server. Response codes - * are defined in the <code>ResponseCodes</code> interface. - * + * Retrieves the response code retrieved from the server. Response codes are + * defined in the <code>ResponseCodes</code> interface. * @return the response code retrieved from the server - * * @throws IOException if an error occurred in the transport layer during - * the transaction; if this method is called on a <code>HeaderSet</code> - * object created by calling <code>createHeaderSet</code> in a - * <code>ClientSession</code> object; if this is called from a server + * the transaction; if this method is called on a + * <code>HeaderSet</code> object created by calling + * <code>createHeaderSet</code> in a <code>ClientSession</code> + * object; if this is called from a server */ public int getResponseCode() throws IOException { throw new IOException("Called from a server"); @@ -562,7 +541,6 @@ public final class ServerOperation implements Operation, BaseStream { /** * Always returns <code>null</code> - * * @return <code>null</code> */ public String getEncoding() { @@ -573,9 +551,8 @@ public final class ServerOperation implements Operation, BaseStream { * Returns the type of content that the resource connected to is providing. * E.g. if the connection is via HTTP, then the value of the content-type * header field is returned. - * * @return the content type of the resource that the URL references, or - * <code>null</code> if not known + * <code>null</code> if not known */ public String getType() { try { @@ -587,11 +564,10 @@ public final class ServerOperation implements Operation, BaseStream { /** * Returns the length of the content which is being provided. E.g. if the - * connection is via HTTP, then the value of the content-length header - * field is returned. - * + * connection is via HTTP, then the value of the content-length header field + * is returned. * @return the content length of the resource that this connection's URL - * references, or -1 if the content length is not known + * references, or -1 if the content length is not known */ public long getLength() { try { @@ -613,9 +589,7 @@ public final class ServerOperation implements Operation, BaseStream { /** * Open and return an input stream for a connection. - * * @return an input stream - * * @throws IOException if an I/O error occurs */ public InputStream openInputStream() throws IOException { @@ -625,9 +599,7 @@ public final class ServerOperation implements Operation, BaseStream { /** * Open and return a data input stream for a connection. - * * @return an input stream - * * @throws IOException if an I/O error occurs */ public DataInputStream openDataInputStream() throws IOException { @@ -636,9 +608,7 @@ public final class ServerOperation implements Operation, BaseStream { /** * Open and return an output stream for a connection. - * * @return an output stream - * * @throws IOException if an I/O error occurs */ public OutputStream openOutputStream() throws IOException { @@ -661,9 +631,7 @@ public final class ServerOperation implements Operation, BaseStream { /** * Open and return a data output stream for a connection. - * * @return an output stream - * * @throws IOException if an I/O error occurs */ public DataOutputStream openDataOutputStream() throws IOException { @@ -672,7 +640,6 @@ public final class ServerOperation implements Operation, BaseStream { /** * Closes the connection and ends the transaction - * * @throws IOException if the operation has already ended or is closed */ public void close() throws IOException { @@ -682,7 +649,6 @@ public final class ServerOperation implements Operation, BaseStream { /** * Verifies that the connection is open and no exceptions should be thrown. - * * @throws IOException if an exception needs to be thrown */ public void ensureOpen() throws IOException { @@ -695,26 +661,23 @@ public final class ServerOperation implements Operation, BaseStream { } /** - * Verifies that additional information may be sent. In other words, the + * Verifies that additional information may be sent. In other words, the * operation is not done. * <P> - * Included to implement the BaseStream interface only. It does not do + * Included to implement the BaseStream interface only. It does not do * anything on the server side since the operation of the Operation object * is not done until after the handler returns from its method. - * * @throws IOException if the operation is completed */ public void ensureNotDone() throws IOException { } /** - * Called when the output or input stream is closed. It does not do - * anything on the server side since the operation of the Operation object - * is not done until after the handler returns from its method. - * + * Called when the output or input stream is closed. It does not do anything + * on the server side since the operation of the Operation object is not + * done until after the handler returns from its method. * @param inStream <code>true</code> if the input stream is closed; - * <code>false</code> if the output stream is closed - * + * <code>false</code> if the output stream is closed * @throws IOException if an IO error occurs */ public void streamClosed(boolean inStream) throws IOException { diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java index e468b83f2f62..d93e5b61ab9c 100644 --- a/obex/javax/obex/ServerRequestHandler.java +++ b/obex/javax/obex/ServerRequestHandler.java @@ -33,40 +33,38 @@ package javax.obex; /** - * The <code>ServerRequestHandler</code> class defines an event - * listener that will respond to OBEX requests made to the server. + * The <code>ServerRequestHandler</code> class defines an event listener that + * will respond to OBEX requests made to the server. * <P> - * The <code>onConnect()</code>, <code>onSetPath()</code>, <code>onDelete()</code>, - * <code>onGet()</code>, - * and <code>onPut()</code> methods may return any response code defined - * in the <code>ResponseCodes</code> class except for - * <code>OBEX_HTTP_CONTINUE</code>. If <code>OBEX_HTTP_CONTINUE</code> or - * a value not defined in the <code>ResponseCodes</code> class is returned, - * the server implementation will send an <code>OBEX_HTTP_INTERNAL_ERROR</code> - * response to the client. + * The <code>onConnect()</code>, <code>onSetPath()</code>, + * <code>onDelete()</code>, <code>onGet()</code>, and <code>onPut()</code> + * methods may return any response code defined in the + * <code>ResponseCodes</code> class except for <code>OBEX_HTTP_CONTINUE</code>. + * If <code>OBEX_HTTP_CONTINUE</code> or a value not defined in the + * <code>ResponseCodes</code> class is returned, the server implementation will + * send an <code>OBEX_HTTP_INTERNAL_ERROR</code> response to the client. * <P> * <STRONG>Connection ID and Target Headers</STRONG> * <P> * According to the IrOBEX specification, a packet may not contain a Connection - * ID and Target header. Since the Connection ID header is managed by the + * ID and Target header. Since the Connection ID header is managed by the * implementation, it will not send a Connection ID header, if a Connection ID - * was specified, in a packet that has a Target header. In other words, if an - * application adds a Target header to a <code>HeaderSet</code> object used - * in an OBEX operation and a Connection ID was specified, no Connection ID - * will be sent in the packet containing the Target header. + * was specified, in a packet that has a Target header. In other words, if an + * application adds a Target header to a <code>HeaderSet</code> object used in + * an OBEX operation and a Connection ID was specified, no Connection ID will be + * sent in the packet containing the Target header. * <P> * <STRONG>CREATE-EMPTY Requests</STRONG> * <P> * A CREATE-EMPTY request allows clients to create empty objects on the server. - * When a CREATE-EMPTY request is received, the <code>onPut()</code> method - * will be called by the implementation. To differentiate between a normal - * PUT request and a CREATE-EMPTY request, an application must open the - * <code>InputStream</code> from the <code>Operation</code> object passed - * to the <code>onPut()</code> method. For a PUT request, the application - * will be able to read Body data from this <code>InputStream</code>. For - * a CREATE-EMPTY request, there will be no Body data to read. Therefore, - * a call to <code>InputStream.read()</code> will return -1. - * + * When a CREATE-EMPTY request is received, the <code>onPut()</code> method will + * be called by the implementation. To differentiate between a normal PUT + * request and a CREATE-EMPTY request, an application must open the + * <code>InputStream</code> from the <code>Operation</code> object passed to the + * <code>onPut()</code> method. For a PUT request, the application will be able + * to read Body data from this <code>InputStream</code>. For a CREATE-EMPTY + * request, there will be no Body data to read. Therefore, a call to + * <code>InputStream.read()</code> will return -1. * @hide */ public class ServerRequestHandler { @@ -74,8 +72,8 @@ public class ServerRequestHandler { private long mConnectionId; /** - * Creates a <code>ServerRequestHandler</code>. - */ + * Creates a <code>ServerRequestHandler</code>. + */ protected ServerRequestHandler() { /* * A connection ID of -1 implies there is no conenction ID @@ -85,12 +83,10 @@ public class ServerRequestHandler { /** * Sets the connection ID header to include in the reply packets. - * - * @param connectionId the connection ID to use; -1 if no connection ID should be - * sent - * - * @throws IllegalArgumentException if <code>id</code> is not in the - * range -1 to 2<sup>32</sup>-1 + * @param connectionId the connection ID to use; -1 if no connection ID + * should be sent + * @throws IllegalArgumentException if <code>id</code> is not in the range + * -1 to 2<sup>32</sup>-1 */ public void setConnectionId(final long connectionId) { if ((connectionId < -1) || (connectionId > 0xFFFFFFFFL)) { @@ -102,9 +98,8 @@ public class ServerRequestHandler { /** * Retrieves the connection ID that is being used in the present connection. * This method will return -1 if no connection ID is being used. - * * @return the connection id being used or -1 if no connection ID is being - * used + * used */ public long getConnectionId() { return mConnectionId; @@ -113,23 +108,21 @@ public class ServerRequestHandler { /** * Called when a CONNECT request is received. * <P> - * If this method is not implemented by the class that extends this - * class, <code>onConnect()</code> will always return an - * <code>OBEX_HTTP_OK</code> response code. + * If this method is not implemented by the class that extends this class, + * <code>onConnect()</code> will always return an <code>OBEX_HTTP_OK</code> + * response code. * <P> * The headers received in the request can be retrieved from the - * <code>request</code> argument. The headers that should be sent - * in the reply must be specified in the <code>reply</code> argument. - * + * <code>request</code> argument. The headers that should be sent in the + * reply must be specified in the <code>reply</code> argument. * @param request contains the headers sent by the client; - * <code>request</code> will never be <code>null</code> - * + * <code>request</code> will never be <code>null</code> * @param reply the headers that should be sent in the reply; - * <code>reply</code> will never be <code>null</code> - * - * @return a response code defined in <code>ResponseCodes</code> that will be - * returned to the client; if an invalid response code is provided, the - * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + * <code>reply</code> will never be <code>null</code> + * @return a response code defined in <code>ResponseCodes</code> that will + * be returned to the client; if an invalid response code is + * provided, the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code + * will be used */ public int onConnect(HeaderSet request, HeaderSet reply) { return ResponseCodes.OBEX_HTTP_OK; @@ -139,14 +132,12 @@ public class ServerRequestHandler { * Called when a DISCONNECT request is received. * <P> * The headers received in the request can be retrieved from the - * <code>request</code> argument. The headers that should be sent - * in the reply must be specified in the <code>reply</code> argument. - * + * <code>request</code> argument. The headers that should be sent in the + * reply must be specified in the <code>reply</code> argument. * @param request contains the headers sent by the client; - * <code>request</code> will never be <code>null</code> - * + * <code>request</code> will never be <code>null</code> * @param reply the headers that should be sent in the reply; - * <code>reply</code> will never be <code>null</code> + * <code>reply</code> will never be <code>null</code> */ public void onDisconnect(HeaderSet request, HeaderSet reply) { } @@ -154,32 +145,28 @@ public class ServerRequestHandler { /** * Called when a SETPATH request is received. * <P> - * If this method is not implemented by the class that extends this - * class, <code>onSetPath()</code> will always return an + * If this method is not implemented by the class that extends this class, + * <code>onSetPath()</code> will always return an * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. * <P> * The headers received in the request can be retrieved from the - * <code>request</code> argument. The headers that should be sent - * in the reply must be specified in the <code>reply</code> argument. - * + * <code>request</code> argument. The headers that should be sent in the + * reply must be specified in the <code>reply</code> argument. * @param request contains the headers sent by the client; - * <code>request</code> will never be <code>null</code> - * + * <code>request</code> will never be <code>null</code> * @param reply the headers that should be sent in the reply; - * <code>reply</code> will never be <code>null</code> - * + * <code>reply</code> will never be <code>null</code> * @param backup <code>true</code> if the client requests that the server - * back up one directory before changing to the path described by - * <code>name</code>; <code>false</code> to apply the request to the present - * path - * + * back up one directory before changing to the path described by + * <code>name</code>; <code>false</code> to apply the request to the + * present path * @param create <code>true</code> if the path should be created if it does - * not already exist; <code>false</code> if the path should not be created - * if it does not exist and an error code should be returned - * - * @return a response code defined in <code>ResponseCodes</code> that will be - * returned to the client; if an invalid response code is provided, the - * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + * not already exist; <code>false</code> if the path should not be + * created if it does not exist and an error code should be returned + * @return a response code defined in <code>ResponseCodes</code> that will + * be returned to the client; if an invalid response code is + * provided, the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code + * will be used */ public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) { @@ -189,23 +176,21 @@ public class ServerRequestHandler { /** * Called when a DELETE request is received. * <P> - * If this method is not implemented by the class that extends this - * class, <code>onDelete()</code> will always return an + * If this method is not implemented by the class that extends this class, + * <code>onDelete()</code> will always return an * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. * <P> * The headers received in the request can be retrieved from the - * <code>request</code> argument. The headers that should be sent - * in the reply must be specified in the <code>reply</code> argument. - * + * <code>request</code> argument. The headers that should be sent in the + * reply must be specified in the <code>reply</code> argument. * @param request contains the headers sent by the client; - * <code>request</code> will never be <code>null</code> - * + * <code>request</code> will never be <code>null</code> * @param reply the headers that should be sent in the reply; - * <code>reply</code> will never be <code>null</code> - * - * @return a response code defined in <code>ResponseCodes</code> that will be - * returned to the client; if an invalid response code is provided, the - * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + * <code>reply</code> will never be <code>null</code> + * @return a response code defined in <code>ResponseCodes</code> that will + * be returned to the client; if an invalid response code is + * provided, the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code + * will be used */ public int onDelete(HeaderSet request, HeaderSet reply) { return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; @@ -214,20 +199,19 @@ public class ServerRequestHandler { /** * Called when a PUT request is received. * <P> - * If this method is not implemented by the class that extends this - * class, <code>onPut()</code> will always return an + * If this method is not implemented by the class that extends this class, + * <code>onPut()</code> will always return an * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. * <P> * If an ABORT request is received during the processing of a PUT request, * <code>op</code> will be closed by the implementation. - * * @param operation contains the headers sent by the client and allows new - * headers to be sent in the reply; <code>op</code> will never be - * <code>null</code> - * - * @return a response code defined in <code>ResponseCodes</code> that will be - * returned to the client; if an invalid response code is provided, the - * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * @return a response code defined in <code>ResponseCodes</code> that will + * be returned to the client; if an invalid response code is + * provided, the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code + * will be used */ public int onPut(Operation operation) { return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; @@ -236,20 +220,19 @@ public class ServerRequestHandler { /** * Called when a GET request is received. * <P> - * If this method is not implemented by the class that extends this - * class, <code>onGet()</code> will always return an + * If this method is not implemented by the class that extends this class, + * <code>onGet()</code> will always return an * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. * <P> * If an ABORT request is received during the processing of a GET request, * <code>op</code> will be closed by the implementation. - * * @param operation contains the headers sent by the client and allows new - * headers to be sent in the reply; <code>op</code> will never be - * <code>null</code> - * - * @return a response code defined in <code>ResponseCodes</code> that will be - * returned to the client; if an invalid response code is provided, the - * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * @return a response code defined in <code>ResponseCodes</code> that will + * be returned to the client; if an invalid response code is + * provided, the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code + * will be used */ public int onGet(Operation operation) { return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; @@ -262,9 +245,8 @@ public class ServerRequestHandler { * <P> * If this method is not implemented by the class that extends this class, * this method will do nothing. - * * @param userName the user name returned in the authentication response; - * <code>null</code> if no user name was provided in the response + * <code>null</code> if no user name was provided in the response */ public void onAuthenticationFailure(byte[] userName) { } @@ -274,7 +256,6 @@ public class ServerRequestHandler { * <P> * If this method is not implemented by the class that extends this class, * this method will do nothing. - * */ public void updateStatus(String message) { } @@ -284,7 +265,6 @@ public class ServerRequestHandler { * <P> * If this method is not implemented by the class that extends this class, * this method will do nothing. - * */ public void onClose() { } diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java index 3a0e81501acf..423d5a757b89 100644 --- a/obex/javax/obex/ServerSession.java +++ b/obex/javax/obex/ServerSession.java @@ -40,7 +40,6 @@ import java.io.OutputStream; /** * This class in an implementation of the OBEX ServerSession. - * * @hide */ public final class ServerSession extends ObexSession implements Runnable { @@ -63,19 +62,11 @@ public final class ServerSession extends ObexSession implements Runnable { /** * Creates new ServerSession. - * - * @param trans - * the connection to the client - * - * @param handler - * the event listener that will process requests - * - * @param auth - * the authenticator to use with this connection - * - * @throws IOException - * if an error occurred while opening the input and output - * streams + * @param trans the connection to the client + * @param handler the event listener that will process requests + * @param auth the authenticator to use with this connection + * @throws IOException if an error occurred while opening the input and + * output streams */ public ServerSession(ObexTransport trans, ServerRequestHandler handler, Authenticator auth) throws IOException { @@ -163,12 +154,8 @@ public final class ServerSession extends ObexSession implements Runnable { * <code>ServerOperation</code> object will always reply with a * OBEX_HTTP_CONTINUE reply. It will only reply if further information is * needed. - * - * @param type - * the type of request received; either 0x02 or 0x82 - * - * @throws IOException - * if an error occurred at the transport layer + * @param type the type of request received; either 0x02 or 0x82 + * @throws IOException if an error occurred at the transport layer */ private void handlePutRequest(int type) throws IOException { ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); @@ -191,7 +178,14 @@ public final class ServerSession extends ObexSession implements Runnable { op.sendReply(response); } } catch (Exception e) { - sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + /*To fix bugs in aborted cases, + *(client abort file transfer prior to the last packet which has the end of body header, + *internal error should not be sent because server has already replied with + *OK response in "sendReply") + */ + if (!op.isAborted) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } } } @@ -205,12 +199,8 @@ public final class ServerSession extends ObexSession implements Runnable { * <code>ServerOperation</code> object will always reply with a * OBEX_HTTP_CONTINUE reply. It will only reply if further information is * needed. - * - * @param type - * the type of request received; either 0x03 or 0x83 - * - * @throws IOException - * if an error occurred at the transport layer + * @param type the type of request received; either 0x03 or 0x83 + * @throws IOException if an error occurred at the transport layer */ private void handleGetRequest(int type) throws IOException { ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); @@ -227,15 +217,9 @@ public final class ServerSession extends ObexSession implements Runnable { /** * Send standard response. - * - * @param code - * the response code to send - * - * @param header - * the headers to include in the response - * - * @throws IOException - * if an IO error occurs + * @param code the response code to send + * @param header the headers to include in the response + * @throws IOException if an IO error occurs */ public void sendResponse(int code, byte[] header) throws IOException { int totalLength = 3; @@ -265,9 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable { * <code>ServerRequestHandler</code> object. After the handler processes the * request, this method will create a reply message to send to the server * with the response code provided. - * - * @throws IOException - * if an error occurred at the transport layer + * @throws IOException if an error occurred at the transport layer */ private void handleSetPathRequest() throws IOException { int length; @@ -393,9 +375,7 @@ public final class ServerSession extends ObexSession implements Runnable { * will create a <code>HeaderSet</code> object to pass to the * <code>ServerRequestHandler</code> object. After the handler processes the * request, this method will create a reply message to send to the server. - * - * @throws IOException - * if an error occurred at the transport layer + * @throws IOException if an error occurred at the transport layer */ private void handleDisconnectRequest() throws IOException { int length; @@ -500,9 +480,7 @@ public final class ServerSession extends ObexSession implements Runnable { * <code>ServerRequestHandler</code> object. After the handler processes the * request, this method will create a reply message to send to the server * with the response code provided. - * - * @throws IOException - * if an error occurred at the transport layer + * @throws IOException if an error occurred at the transport layer */ private void handleConnectRequest() throws IOException { int packetLength; @@ -660,10 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable { /** * Verifies that the response code is valid. If it is not valid, it will * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code. - * - * @param code - * the response code to check - * + * @param code the response code to check * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code> * if <code>code</code> is not valid */ diff --git a/obex/javax/obex/SessionNotifier.java b/obex/javax/obex/SessionNotifier.java index 36e0ebf14226..9836dd60f2c6 100644 --- a/obex/javax/obex/SessionNotifier.java +++ b/obex/javax/obex/SessionNotifier.java @@ -36,73 +36,53 @@ import java.io.IOException; /** * The <code>SessionNotifier</code> interface defines a connection notifier for - * server-side OBEX connections. When a <code>SessionNotifier</code> is - * created and calls <code>acceptAndOpen()</code>, it will begin listening for - * clients to create a connection at the transport layer. When the transport - * layer connection is received, the <code>acceptAndOpen()</code> method will - * return a <code>javax.microedition.io.Connection</code> that is the - * connection to the client. The <code>acceptAndOpen()</code> method also takes a + * server-side OBEX connections. When a <code>SessionNotifier</code> is created + * and calls <code>acceptAndOpen()</code>, it will begin listening for clients + * to create a connection at the transport layer. When the transport layer + * connection is received, the <code>acceptAndOpen()</code> method will return a + * <code>javax.microedition.io.Connection</code> that is the connection to the + * client. The <code>acceptAndOpen()</code> method also takes a * <code>ServerRequestHandler</code> argument that will process the requests * from the client that connects to the server. - * * @hide */ public interface SessionNotifier { /** * Waits for a transport layer connection to be established and specifies - * the handler to handle the requests from the client. No authenticator - * is associated with this connection, therefore, it is implementation + * the handler to handle the requests from the client. No authenticator is + * associated with this connection, therefore, it is implementation * dependent as to how an authentication challenge and authentication * response header will be received and processed. * <P> - * <H4>Additional Note for OBEX over Bluetooth</H4> - * If this method is called on a <code>SessionNotifier</code> object that - * does not have a <code>ServiceRecord</code> in the SDDB, the - * <code>ServiceRecord</code> for this object will be added to the SDDB. - * This method requests the BCC to put the - * local device in connectible mode so that it will respond to + * <H4>Additional Note for OBEX over Bluetooth</H4> If this method is called + * on a <code>SessionNotifier</code> object that does not have a + * <code>ServiceRecord</code> in the SDDB, the <code>ServiceRecord</code> + * for this object will be added to the SDDB. This method requests the BCC + * to put the local device in connectable mode so that it will respond to * connection attempts by clients. * <P> - * The following checks are done to verify that the service record - * provided is valid. If any of these checks fail, then a + * The following checks are done to verify that the service record provided + * is valid. If any of these checks fail, then a * <code>ServiceRegistrationException</code> is thrown. * <UL> - * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory - * service attributes for a <code>btgoep</code> service record, must be - * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory service + * attributes for a <code>btgoep</code> service record, must be present in + * the <code>ServiceRecord</code> associated with this notifier. * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList - * <LI>The <code>ServiceRecord</code> associated with this notifier must - * not have changed the RFCOMM server channel number + * <LI>The <code>ServiceRecord</code> associated with this notifier must not + * have changed the RFCOMM server channel number * </UL> * <P> * This method will not ensure that <code>ServiceRecord</code> associated - * with this notifier is a completely - * valid service record. It is the responsibility of the application to - * ensure that the service record follows all of the applicable - * syntactic and semantic rules for service record correctness. - * + * with this notifier is a completely valid service record. It is the + * responsibility of the application to ensure that the service record + * follows all of the applicable syntactic and semantic rules for service + * record correctness. * @param handler the request handler that will respond to OBEX requests - * * @return the connection to the client - * * @throws IOException if an error occurs in the transport layer - * - * @throws NullPointerException if <code>handler</code> is - * <code>null</code> - * - * @throws ServiceRegistrationException if the structure of the - * associated service record is invalid or if the service record - * could not be added successfully to the local SDDB. The - * structure of service record is invalid if the service - * record is missing any mandatory service attributes, or has - * changed any of the values described above which are fixed and - * cannot be changed. Failures to add the record to the SDDB could - * be due to insufficient disk space, database locks, etc. - * - * @throws BluetoothStateException if the server device could - * not be placed in connectible mode because the device user has - * configured the device to be non-connectible + * @throws NullPointerException if <code>handler</code> is <code>null</code> */ ObexSession acceptAndOpen(ServerRequestHandler handler) throws IOException; @@ -112,56 +92,37 @@ public interface SessionNotifier { * <code>Authenticator</code> to use to respond to authentication challenge * and authentication response headers. * <P> - * <H4>Additional Note for OBEX over Bluetooth</H4> - * If this method is called on a <code>SessionNotifier</code> object that - * does not have a <code>ServiceRecord</code> in the SDDB, the - * <code>ServiceRecord</code> for this object will be added to the SDDB. - * This method requests the BCC to put the - * local device in connectible mode so that it will respond to + * <H4>Additional Note for OBEX over Bluetooth</H4> If this method is called + * on a <code>SessionNotifier</code> object that does not have a + * <code>ServiceRecord</code> in the SDDB, the <code>ServiceRecord</code> + * for this object will be added to the SDDB. This method requests the BCC + * to put the local device in connectable mode so that it will respond to * connection attempts by clients. * <P> - * The following checks are done to verify that the service record - * provided is valid. If any of these checks fail, then a + * The following checks are done to verify that the service record provided + * is valid. If any of these checks fail, then a * <code>ServiceRegistrationException</code> is thrown. * <UL> - * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory - * service attributes for a <code>btgoep</code> service record, must be - * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory service + * attributes for a <code>btgoep</code> service record, must be present in + * the <code>ServiceRecord</code> associated with this notifier. * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList - * <LI>The <code>ServiceRecord</code> associated with this notifier must - * not have changed the RFCOMM server channel number + * <LI>The <code>ServiceRecord</code> associated with this notifier must not + * have changed the RFCOMM server channel number * </UL> * <P> * This method will not ensure that <code>ServiceRecord</code> associated - * with this notifier is a completely - * valid service record. It is the responsibility of the application to - * ensure that the service record follows all of the applicable - * syntactic and semantic rules for service record correctness. - * + * with this notifier is a completely valid service record. It is the + * responsibility of the application to ensure that the service record + * follows all of the applicable syntactic and semantic rules for service + * record correctness. * @param handler the request handler that will respond to OBEX requests - * * @param auth the <code>Authenticator</code> to use with this connection; - * if <code>null</code> then no <code>Authenticator</code> will be used - * + * if <code>null</code> then no <code>Authenticator</code> will be + * used * @return the connection to the client - * * @throws IOException if an error occurs in the transport layer - * - * @throws NullPointerException if <code>handler</code> is - * <code>null</code> - * - * @throws ServiceRegistrationException if the structure of the - * associated service record is invalid or if the service record - * could not be added successfully to the local SDDB. The - * structure of service record is invalid if the service - * record is missing any mandatory service attributes, or has - * changed any of the values described above which are fixed and - * cannot be changed. Failures to add the record to the SDDB could - * be due to insufficient disk space, database locks, etc. - * - * @throws BluetoothStateException if the server device could - * not be placed in connectible mode because the device user has - * configured the device to be non-connectible + * @throws NullPointerException if <code>handler</code> is <code>null</code> */ ObexSession acceptAndOpen(ServerRequestHandler handler, Authenticator auth) throws IOException; } diff --git a/opengl/libagl/Android.mk b/opengl/libagl/Android.mk index d1ed82e5eb00..2522656f2213 100644 --- a/opengl/libagl/Android.mk +++ b/opengl/libagl/Android.mk @@ -38,7 +38,6 @@ endif ifeq ($(LIBAGL_USE_GRALLOC_COPYBITS),1) LOCAL_CFLAGS += -DLIBAGL_USE_GRALLOC_COPYBITS LOCAL_SRC_FILES += copybit.cpp - LOCAL_C_INCLUDES += hardware/libhardware/modules/gralloc endif LOCAL_CFLAGS += -DLOG_TAG=\"libagl\" diff --git a/opengl/libagl/copybit.cpp b/opengl/libagl/copybit.cpp index 3331026f66b9..3c5bcdf39f66 100644 --- a/opengl/libagl/copybit.cpp +++ b/opengl/libagl/copybit.cpp @@ -295,6 +295,9 @@ static bool copybit(GLint x, GLint y, clipRectRegion it(c); status_t err = copybit->stretch(copybit, &dst, &src, &drect, &srect, &it); + if (err != NO_ERROR) { + c->textures.tmu[0].texture->try_copybit = false; + } return err == NO_ERROR ? true : false; } diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp index 6f6656a4d29b..7afcae7504f8 100644 --- a/opengl/libagl/egl.cpp +++ b/opengl/libagl/egl.cpp @@ -48,10 +48,6 @@ #include "texture.h" #include "matrix.h" -#ifdef LIBAGL_USE_GRALLOC_COPYBITS -#include "gralloc_priv.h" -#endif // LIBAGL_USE_GRALLOC_COPYBITS - #undef NELEM #define NELEM(x) (sizeof(x)/sizeof(*(x))) @@ -622,11 +618,10 @@ EGLBoolean egl_window_surface_v2_t::bindDrawSurface(ogles_context_t* gl) #ifdef LIBAGL_USE_GRALLOC_COPYBITS gl->copybits.drawSurfaceBuffer = 0; - if (supportedCopybitsDestinationFormat(buffer.format)) { - buffer_handle_t handle = this->buffer->handle; - if (handle != NULL) { - private_handle_t* hand = private_handle_t::dynamicCast(handle); - if (hand != NULL && hand->usesPhysicallyContiguousMemory()) { + if (gl->copybits.blitEngine != NULL) { + if (supportedCopybitsDestinationFormat(buffer.format)) { + buffer_handle_t handle = this->buffer->handle; + if (handle != NULL) { gl->copybits.drawSurfaceBuffer = handle; } } diff --git a/opengl/libagl/primitives.cpp b/opengl/libagl/primitives.cpp index f164c02eea34..769ec404a8a4 100644 --- a/opengl/libagl/primitives.cpp +++ b/opengl/libagl/primitives.cpp @@ -369,7 +369,7 @@ void compute_iterators_t::iterators0032(int32_t* it, int32_t c0, int32_t c1, int32_t c2) const { int64_t it64[3]; - iterators0032(it, c0, c1, c2); + iterators0032(it64, c0, c1, c2); it[0] = it64[0]; it[1] = it64[1]; it[2] = it64[2]; diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp index d767c31ac88d..4d3c2f4381dd 100644 --- a/opengl/libagl/texture.cpp +++ b/opengl/libagl/texture.cpp @@ -27,7 +27,6 @@ #ifdef LIBAGL_USE_GRALLOC_COPYBITS #include "copybit.h" -#include "gralloc_priv.h" #endif // LIBAGL_USE_GRALLOC_COPYBITS namespace android { @@ -1540,20 +1539,9 @@ void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) sp<EGLTextureObject> tex = getAndBindActiveTextureObject(c); tex->setImage(native_buffer); - /* - * Here an implementation can retrieve the buffer_handle_t of this buffer - * which gives it access to an arbitrary-defined kernel resource - * (or anything else for that matter). - * There needs to be an intimate knowledge between GLES and buffer_handle_t, - * so make sure to validate the handle before using it. - * Typically, buffer_handle_t comes from the gralloc HAL which is provided - * by the implementor of GLES. - * - */ #ifdef LIBAGL_USE_GRALLOC_COPYBITS tex->try_copybit = false; - private_handle_t* hnd = private_handle_t::dynamicCast(native_buffer->handle); - if (hnd && hnd->usesPhysicallyContiguousMemory()) { + if (c->copybits.blitEngine != NULL) { tex->try_copybit = true; } #endif // LIBAGL_USE_GRALLOC_COPYBITS diff --git a/packages/SettingsProvider/etc/bookmarks.xml b/packages/SettingsProvider/etc/bookmarks.xml index 33b4c977a363..734e0cd90b00 100644 --- a/packages/SettingsProvider/etc/bookmarks.xml +++ b/packages/SettingsProvider/etc/bookmarks.xml @@ -55,6 +55,6 @@ shortcut="s" /> <bookmark package="com.google.android.youtube" - class="com.google.android.youtube.HomePage" + class="com.google.android.youtube.HomeActivity" shortcut="y" /> </bookmarks> diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp index 424748364fda..82067be1d8f1 100644 --- a/packages/TtsService/jni/android_tts_SynthProxy.cpp +++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp @@ -59,6 +59,9 @@ struct afterSynthData_t { // ---------------------------------------------------------------------------- static fields_t javaTTSFields; +// TODO move to synth member once we have multiple simultaneous engines running +static Mutex engineMutex; + // ---------------------------------------------------------------------------- class SynthProxyJniStorage { public : @@ -84,6 +87,7 @@ class SynthProxyJniStorage { mNbChannels = DEFAULT_TTS_NB_CHANNELS; mBufferSize = DEFAULT_TTS_BUFFERSIZE; mBuffer = new int8_t[mBufferSize]; + memset(mBuffer, 0, mBufferSize); } ~SynthProxyJniStorage() { @@ -194,6 +198,7 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, prepAudioTrack(pJniData, pForAfter->streamType, rate, format, channel); if (pJniData->mAudioOut) { pJniData->mAudioOut->write(wav, bufferSize); + memset(wav, 0, bufferSize); //LOGV("AudioTrack wrote: %d bytes", bufferSize); } else { LOGE("Can't play, null audiotrack"); @@ -208,6 +213,7 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, } if (bufferSize > 0){ fwrite(wav, 1, bufferSize, pForAfter->outputFile); + memset(wav, 0, bufferSize); } } // Future update: @@ -283,6 +289,7 @@ android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) { if (jniData) { SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + env->DeleteGlobalRef(pSynthData->tts_ref); delete pSynthData; } } @@ -326,6 +333,8 @@ android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; const char *langNativeString = env->GetStringUTFChars(language, 0); const char *countryNativeString = env->GetStringUTFChars(country, 0); @@ -385,6 +394,8 @@ android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData, char buffer [bufSize]; sprintf(buffer, "%d", speechRate); + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; LOGI("setting speech rate to %d", speechRate); @@ -407,6 +418,8 @@ android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + int bufSize = 10; char buffer [bufSize]; sprintf(buffer, "%d", pitch); @@ -439,6 +452,8 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + // Retrieve audio parameters before writing the file header AudioSystem::audio_format encoding = DEFAULT_TTS_FORMAT; uint32_t rate = DEFAULT_TTS_RATE; @@ -473,6 +488,7 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, unsigned int unique_identifier; + memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter); @@ -540,10 +556,11 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; if (pSynthData->mAudioOut) { - pSynthData->mAudioOut->stop(); pSynthData->mAudioOut->start(); } @@ -554,6 +571,7 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, if (pSynthData->mNativeSynthInterface) { const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter); env->ReleaseStringUTFChars(textJavaString, textNativeString); @@ -575,12 +593,12 @@ android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - if (pSynthData->mNativeSynthInterface) { - result = pSynthData->mNativeSynthInterface->stop(); - } if (pSynthData->mAudioOut) { pSynthData->mAudioOut->stop(); } + if (pSynthData->mNativeSynthInterface) { + result = pSynthData->mNativeSynthInterface->stop(); + } return result; } @@ -594,6 +612,8 @@ android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) return; } + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; if (pSynthData->mNativeSynthInterface) { pSynthData->mNativeSynthInterface->shutdown(); @@ -602,24 +622,6 @@ android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) } -// TODO add buffer format -static void -android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData, - int bufferPointer, int bufferSize) -{ -LOGI("android_tts_SynthProxy_playAudioBuffer"); - if (jniData == 0) { - LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data"); - return; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - short* wav = (short*) bufferPointer; - pSynthData->mAudioOut->write(wav, bufferSize); - //LOGI("AudioTrack wrote: %d bytes", bufferSize); -} - - static jobjectArray android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) { @@ -705,10 +707,6 @@ static JNINativeMethod gMethods[] = { "(II)I", (void*)android_tts_SynthProxy_setPitch }, - { "native_playAudioBuffer", - "(III)V", - (void*)android_tts_SynthProxy_playAudioBuffer - }, { "native_getLanguage", "(I)[Ljava/lang/String;", (void*)android_tts_SynthProxy_getLanguage diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java index 41ff92a1b9bd..a0814aa912a2 100755 --- a/packages/TtsService/src/android/tts/SynthProxy.java +++ b/packages/TtsService/src/android/tts/SynthProxy.java @@ -109,13 +109,6 @@ public class SynthProxy { } /** - * Plays the given audio buffer. - */ - public void playAudioBuffer(int bufferPointer, int bufferSize) { - native_playAudioBuffer(mJniData, bufferPointer, bufferSize); - } - - /** * Returns the currently set language, country and variant information. */ public String[] getLanguage() { @@ -180,9 +173,6 @@ public class SynthProxy { private native final int native_setPitch(int jniData, int speechRate); - // TODO add buffer format - private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize); - private native final String[] native_getLanguage(int jniData); private native final int native_getRate(int jniData); diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index 4d2518327d2d..cfefcb79c492 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -130,6 +130,8 @@ public class TtsService extends Service implements OnCompletionListener { private HashMap<String, SoundResource> mUtterances; private MediaPlayer mPlayer; private SpeechItem mCurrentSpeechItem; + private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls + // are killed when stop is used. private TtsService mSelf; private ContentResolver mResolver; @@ -137,16 +139,18 @@ public class TtsService extends Service implements OnCompletionListener { private final ReentrantLock speechQueueLock = new ReentrantLock(); private final ReentrantLock synthesizerLock = new ReentrantLock(); - private SynthProxy nativeSynth; + private static SynthProxy sNativeSynth = null; @Override public void onCreate() { super.onCreate(); - Log.i("TTS", "TTS starting"); + Log.i("TtsService", "TtsService.onCreate()"); mResolver = getContentResolver(); String soLibPath = "/system/lib/libttspico.so"; - nativeSynth = new SynthProxy(soLibPath); + if (sNativeSynth == null) { + sNativeSynth = new SynthProxy(soLibPath); + } mSelf = this; mIsSpeaking = false; @@ -158,6 +162,7 @@ public class TtsService extends Service implements OnCompletionListener { mSpeechQueue = new ArrayList<SpeechItem>(); mPlayer = null; mCurrentSpeechItem = null; + mKillList = new HashMap<SpeechItem, Boolean>(); setDefaultSettings(); } @@ -168,7 +173,7 @@ public class TtsService extends Service implements OnCompletionListener { // Don't hog the media player cleanUpPlayer(); - nativeSynth.shutdown(); + sNativeSynth.shutdown(); // Unregister all callbacks. mCallbacks.kill(); @@ -236,36 +241,36 @@ public class TtsService extends Service implements OnCompletionListener { private int setSpeechRate(String callingApp, int rate) { if (isDefaultEnforced()) { - return nativeSynth.setSpeechRate(getDefaultRate()); + return sNativeSynth.setSpeechRate(getDefaultRate()); } else { - return nativeSynth.setSpeechRate(rate); + return sNativeSynth.setSpeechRate(rate); } } private int setPitch(String callingApp, int pitch) { - return nativeSynth.setPitch(pitch); + return sNativeSynth.setPitch(pitch); } private int isLanguageAvailable(String lang, String country, String variant) { - //Log.v("TTS", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")"); - return nativeSynth.isLanguageAvailable(lang, country, variant); + //Log.v("TtsService", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")"); + return sNativeSynth.isLanguageAvailable(lang, country, variant); } private String[] getLanguage() { - return nativeSynth.getLanguage(); + return sNativeSynth.getLanguage(); } private int setLanguage(String callingApp, String lang, String country, String variant) { - //Log.v("TTS", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); + Log.v("TtsService", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); if (isDefaultEnforced()) { - return nativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), + return sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); } else { - return nativeSynth.setLanguage(lang, country, variant); + return sNativeSynth.setLanguage(lang, country, variant); } } @@ -337,9 +342,11 @@ public class TtsService extends Service implements OnCompletionListener { * engines. */ private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { - Log.i("TTS service received", text); + Log.v("TtsService", "TTS service received " + text); if (queueMode == TextToSpeech.TTS_QUEUE_FLUSH) { stop(callingApp); + } else if (queueMode == 2) { + stopAll(callingApp); } mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); if (!mIsSpeaking) { @@ -364,6 +371,8 @@ public class TtsService extends Service implements OnCompletionListener { ArrayList<String> params) { if (queueMode == TextToSpeech.TTS_QUEUE_FLUSH) { stop(callingApp); + } else if (queueMode == 2) { + stopAll(callingApp); } mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); if (!mIsSpeaking) { @@ -373,7 +382,7 @@ public class TtsService extends Service implements OnCompletionListener { } /** - * Stops all speech output and removes any utterances still in the queue. + * Stops all speech output and removes any utterances still in the queue for the calling app. */ private int stop(String callingApp) { int result = TextToSpeech.TTS_ERROR; @@ -383,31 +392,86 @@ public class TtsService extends Service implements OnCompletionListener { // something has gone very wrong with processSpeechQueue. speechQueueAvailable = speechQueueLock.tryLock(1000, TimeUnit.MILLISECONDS); if (speechQueueAvailable) { - Log.i("TTS", "Stopping"); + Log.i("TtsService", "Stopping"); for (int i = mSpeechQueue.size() - 1; i > -1; i--){ if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ mSpeechQueue.remove(i); } } + if ((mCurrentSpeechItem != null) && + mCurrentSpeechItem.mCallingApp.equals(callingApp)) { + result = sNativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + mIsSpeaking = false; + mCurrentSpeechItem = null; + } else { + result = TextToSpeech.TTS_SUCCESS; + } + Log.i("TtsService", "Stopped"); + } + } catch (InterruptedException e) { + Log.e("TtsService", "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; + } + } - result = nativeSynth.stop(); - mIsSpeaking = false; - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. + + + /** + * Stops all speech output and removes any utterances still in the queue globally. + */ + private int stopAll(String callingApp) { + 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) { + for (int i = mSpeechQueue.size() - 1; i > -1; i--){ + if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ + mSpeechQueue.remove(i); + } + } + if ((mCurrentSpeechItem != null) && + ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || + mCurrentSpeechItem.mCallingApp.equals(callingApp))) { + result = sNativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } } + mIsSpeaking = false; + mCurrentSpeechItem = null; + } else { + result = TextToSpeech.TTS_SUCCESS; } - Log.i("TTS", "Stopped"); + Log.i("TtsService", "Stopped all"); } } catch (InterruptedException e) { - Log.e("TTS stop", "tryLock interrupted"); + Log.e("TtsService", "TTS stopAll: tryLock interrupted"); e.printStackTrace(); } finally { // This check is needed because finally will always run; even if the // method returns somewhere in the try block. - mCurrentSpeechItem = null; if (speechQueueAvailable) { speechQueueLock.unlock(); } @@ -430,7 +494,6 @@ public class TtsService extends Service implements OnCompletionListener { if (utteranceId.length() > 0){ dispatchUtteranceCompletedCallback(utteranceId, callingApp); } - mCurrentSpeechItem = null; processSpeechQueue(); } @@ -466,7 +529,6 @@ public class TtsService extends Service implements OnCompletionListener { if (utteranceId.length() > 0){ dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); } - mCurrentSpeechItem = null; processSpeechQueue(); } } @@ -486,21 +548,21 @@ public class TtsService extends Service implements OnCompletionListener { if (!synthAvailable) { Thread.sleep(100); Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); return; } int streamType = DEFAULT_STREAM_TYPE; + String language = ""; + String country = ""; + String variant = ""; + String speechRate = ""; if (speechItem.mParams != null){ - String language = ""; - String country = ""; - String variant = ""; for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ String param = speechItem.mParams.get(i); if (param != null) { if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)) { - setSpeechRate("", - Integer.parseInt(speechItem.mParams.get(i+1))); + speechRate = speechItem.mParams.get(i+1); } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ language = speechItem.mParams.get(i+1); } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ @@ -519,31 +581,36 @@ public class TtsService extends Service implements OnCompletionListener { } } } + } + // Only do the synthesis if it has not been killed by a subsequent utterance. + if (mKillList.get(speechItem) == null) { if (language.length() > 0){ setLanguage("", language, country, variant); } + if (speechRate.length() > 0){ + setSpeechRate("", Integer.parseInt(speechRate)); + } + sNativeSynth.speak(speechItem.mText, streamType); } - nativeSynth.speak(speechItem.mText, streamType); } catch (InterruptedException e) { - Log.e("TTS speakInternalOnly", "tryLock interrupted"); + Log.e("TtsService", "TTS speakInternalOnly(): 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 (synthAvailable) { - synthesizerLock.unlock(); - } if (utteranceId.length() > 0){ dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); } - mCurrentSpeechItem = null; + if (synthAvailable) { + synthesizerLock.unlock(); + } processSpeechQueue(); } } } Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); } @@ -552,26 +619,26 @@ public class TtsService extends Service implements OnCompletionListener { public void run() { boolean synthAvailable = false; String utteranceId = ""; - Log.i("TTS", "Synthesizing to " + speechItem.mFilename); + Log.i("TtsService", "Synthesizing to " + speechItem.mFilename); try { synthAvailable = synthesizerLock.tryLock(); if (!synthAvailable) { Thread.sleep(100); Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); return; } + String language = ""; + String country = ""; + String variant = ""; + String speechRate = ""; if (speechItem.mParams != null){ - String language = ""; - String country = ""; - String variant = ""; for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ String param = speechItem.mParams.get(i); - if (param != null){ - if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){ - setSpeechRate("", - Integer.parseInt(speechItem.mParams.get(i+1))); + if (param != null) { + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)) { + speechRate = speechItem.mParams.get(i+1); } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ language = speechItem.mParams.get(i+1); } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ @@ -583,31 +650,36 @@ public class TtsService extends Service implements OnCompletionListener { } } } + } + // Only do the synthesis if it has not been killed by a subsequent utterance. + if (mKillList.get(speechItem) == null){ if (language.length() > 0){ setLanguage("", language, country, variant); } + if (speechRate.length() > 0){ + setSpeechRate("", Integer.parseInt(speechRate)); + } + sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); } - nativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); } catch (InterruptedException e) { - Log.e("TTS synthToFileInternalOnly", "tryLock interrupted"); + Log.e("TtsService", "TTS synthToFileInternalOnly(): 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 (synthAvailable) { - synthesizerLock.unlock(); - } if (utteranceId.length() > 0){ dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); } - mCurrentSpeechItem = null; + if (synthAvailable) { + synthesizerLock.unlock(); + } processSpeechQueue(); } } } Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); } @@ -635,7 +707,7 @@ public class TtsService extends Service implements OnCompletionListener { if (cb == null){ return; } - Log.i("TTS callback", "dispatch started"); + Log.i("TtsService", "TTS callback: dispatch started"); // Broadcast to all clients the new value. final int N = mCallbacks.beginBroadcast(); try { @@ -645,7 +717,7 @@ public class TtsService extends Service implements OnCompletionListener { // the dead object for us. } mCallbacks.finishBroadcast(); - Log.i("TTS callback", "dispatch completed to " + N); + Log.i("TtsService", "TTS callback: dispatch completed to " + N); } private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ @@ -694,7 +766,7 @@ public class TtsService extends Service implements OnCompletionListener { SoundResource sr = getSoundResource(mCurrentSpeechItem); // Synth speech as needed - synthesizer should call // processSpeechQueue to continue running the queue - Log.i("TTS processing: ", mCurrentSpeechItem.mText); + Log.i("TtsService", "TTS processing: " + mCurrentSpeechItem.mText); if (sr == null) { if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); diff --git a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java index c6c94521faf8..e4c070ff8b29 100644 --- a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java +++ b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java @@ -106,6 +106,13 @@ public class AndroidServiceProxy extends ProcessProxy { @Override protected void performTask() throws IOException { String svc = mServiceName; + Log.d(mTag, "----- Stop the daemon just in case: " + mServiceName); + SystemProperties.set(SVC_STOP_CMD, mServiceName); + if (!blockUntil(SVC_STATE_STOPPED, 5)) { + throw new IOException("cannot start service anew: " + svc + + ", it is still running"); + } + Log.d(mTag, "+++++ Start: " + svc); SystemProperties.set(SVC_START_CMD, svc); diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java index cf153e3572e8..32b8e51f278d 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java @@ -57,6 +57,7 @@ public class VpnServiceBinder extends Service { public void onStart (Intent intent, int startId) { super.onStart(intent, startId); setForeground(true); + android.util.Log.d("VpnServiceBinder", "becomes a foreground service"); } public IBinder onBind(Intent intent) { @@ -71,9 +72,8 @@ public class VpnServiceBinder extends Service { } private synchronized void checkStatus(VpnProfile p) { - if (mService == null) broadcastConnectivity(p.getName(), VpnState.IDLE); - - if (!p.getName().equals(mService.mProfile.getName())) { + if ((mService == null) + || (!p.getName().equals(mService.mProfile.getName()))) { broadcastConnectivity(p.getName(), VpnState.IDLE); } else { broadcastConnectivity(p.getName(), mService.getState()); diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 131e156748d0..78db6f93727e 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -68,6 +68,7 @@ class AppWidgetService extends IAppWidgetService.Stub private static final String SETTINGS_FILENAME = "appwidgets.xml"; private static final String SETTINGS_TMP_FILENAME = SETTINGS_FILENAME + ".tmp"; + private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes /* * When identifying a Host or Provider based on the calling process, use the uid field. @@ -629,9 +630,12 @@ class AppWidgetService extends IAppWidgetService.Stub Binder.restoreCallingIdentity(token); } if (!alreadyRegistered) { + long period = p.info.updatePeriodMillis; + if (period < MIN_UPDATE_PERIOD) { + period = MIN_UPDATE_PERIOD; + } mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + p.info.updatePeriodMillis, - p.info.updatePeriodMillis, p.broadcast); + SystemClock.elapsedRealtime() + period, period, p.broadcast); } } } diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 20f0750bc8f8..fc1ec033d89d 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -100,18 +100,18 @@ class BackupManagerService extends IBackupManager.Stub { private PowerManager mPowerManager; private AlarmManager mAlarmManager; - private boolean mEnabled; // access to this is synchronized on 'this' - private boolean mProvisioned; - private PowerManager.WakeLock mWakelock; - private final BackupHandler mBackupHandler = new BackupHandler(); - private PendingIntent mRunBackupIntent; - private BroadcastReceiver mRunBackupReceiver; - private IntentFilter mRunBackupFilter; + boolean mEnabled; // access to this is synchronized on 'this' + boolean mProvisioned; + PowerManager.WakeLock mWakelock; + final BackupHandler mBackupHandler = new BackupHandler(); + PendingIntent mRunBackupIntent; + BroadcastReceiver mRunBackupReceiver; + IntentFilter mRunBackupFilter; // map UIDs to the set of backup client services within that UID's app set - private final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants + final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants = new SparseArray<HashSet<ApplicationInfo>>(); // set of backup services that have pending changes - private class BackupRequest { + class BackupRequest { public ApplicationInfo appInfo; public boolean fullBackup; @@ -125,35 +125,35 @@ class BackupManagerService extends IBackupManager.Stub { } } // Backups that we haven't started yet. - private HashMap<ApplicationInfo,BackupRequest> mPendingBackups + HashMap<ApplicationInfo,BackupRequest> mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); // Pseudoname that we use for the Package Manager metadata "package" - private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; + static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; // locking around the pending-backup management - private final Object mQueueLock = new Object(); + final Object mQueueLock = new Object(); // The thread performing the sequence of queued backups binds to each app's agent // in succession. Bind notifications are asynchronously delivered through the // Activity Manager; use this lock object to signal when a requested binding has // completed. - private final Object mAgentConnectLock = new Object(); - private IBackupAgent mConnectedAgent; - private volatile boolean mConnecting; + final Object mAgentConnectLock = new Object(); + IBackupAgent mConnectedAgent; + volatile boolean mConnecting; // A similar synchronicity mechanism around clearing apps' data for restore - private final Object mClearDataLock = new Object(); - private volatile boolean mClearingData; + final Object mClearDataLock = new Object(); + volatile boolean mClearingData; // Transport bookkeeping - private final HashMap<String,IBackupTransport> mTransports + final HashMap<String,IBackupTransport> mTransports = new HashMap<String,IBackupTransport>(); - private String mCurrentTransport; - private IBackupTransport mLocalTransport, mGoogleTransport; - private RestoreSession mActiveRestoreSession; + String mCurrentTransport; + IBackupTransport mLocalTransport, mGoogleTransport; + RestoreSession mActiveRestoreSession; - private class RestoreParams { + class RestoreParams { public IBackupTransport transport; public IRestoreObserver observer; public long token; @@ -165,7 +165,7 @@ class BackupManagerService extends IBackupManager.Stub { } } - private class ClearParams { + class ClearParams { public IBackupTransport transport; public PackageInfo packageInfo; @@ -176,11 +176,17 @@ class BackupManagerService extends IBackupManager.Stub { } // Where we keep our journal files and other bookkeeping - private File mBaseStateDir; - private File mDataDir; - private File mJournalDir; - private File mJournal; - private RandomAccessFile mJournalStream; + File mBaseStateDir; + File mDataDir; + File mJournalDir; + File mJournal; + RandomAccessFile mJournalStream; + + // Keep a log of all the apps we've ever backed up + private File mEverStored; + private RandomAccessFile mEverStoredStream; + HashSet<String> mEverStoredApps = new HashSet<String>(); + public BackupManagerService(Context context) { mContext = context; @@ -215,6 +221,9 @@ class BackupManagerService extends IBackupManager.Stub { mJournalDir.mkdirs(); // creates mBaseStateDir along the way makeJournalLocked(); // okay because no other threads are running yet + // Set up the various sorts of package tracking we do + initPackageTracking(); + // Build our mapping of uid to backup client services. This implicitly // schedules a backup pass on the Package Manager metadata the first // time anything needs to be backed up. @@ -249,14 +258,6 @@ class BackupManagerService extends IBackupManager.Stub { // leftover journal files into the pending backup set parseLeftoverJournals(); - // Register for broadcasts about package install, etc., so we can - // update the provider list. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addDataScheme("package"); - mContext.registerReceiver(mBroadcastReceiver, filter); - // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backup"); @@ -281,6 +282,67 @@ class BackupManagerService extends IBackupManager.Stub { } } + private void initPackageTracking() { + if (DEBUG) Log.v(TAG, "Initializing package tracking"); + + // Keep a log of what apps we've ever backed up. Because we might have + // rebooted in the middle of an operation that was removing something from + // this log, we sanity-check its contents here and reconstruct it. + mEverStored = new File(mBaseStateDir, "processed"); + File tempProcessedFile = new File(mBaseStateDir, "processed.new"); + try { + RandomAccessFile temp = new RandomAccessFile(tempProcessedFile, "rw"); + mEverStoredStream = new RandomAccessFile(mEverStored, "r"); + + // parse its existing contents + mEverStoredStream.seek(0); + temp.seek(0); + try { + while (true) { + PackageInfo info; + String pkg = mEverStoredStream.readUTF(); + try { + info = mPackageManager.getPackageInfo(pkg, 0); + mEverStoredApps.add(pkg); + temp.writeUTF(pkg); + if (DEBUG) Log.v(TAG, " + " + pkg); + } catch (NameNotFoundException e) { + // nope, this package was uninstalled; don't include it + if (DEBUG) Log.v(TAG, " - " + pkg); + } + } + } catch (EOFException e) { + // now we're at EOF + } + + // Once we've rewritten the backup history log, atomically replace the + // old one with the new one then reopen the file for continuing use. + temp.close(); + mEverStoredStream.close(); + tempProcessedFile.renameTo(mEverStored); + mEverStoredStream = new RandomAccessFile(mEverStored, "rwd"); + } catch (IOException e) { + Log.e(TAG, "Unable to open known-stored file!"); + mEverStoredStream = null; + } + + // If we were in the middle of removing something from the ever-backed-up + // file, there might be a transient "processed.new" file still present. + // We've reconstructed a coherent state at this point though, so we can + // safely discard that file now. + if (tempProcessedFile.exists()) { + tempProcessedFile.delete(); + } + + // Register for broadcasts about package install, etc., so we can + // update the provider list. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + private void makeJournalLocked() { try { mJournal = File.createTempFile("journal", null, mJournalDir); @@ -485,6 +547,17 @@ class BackupManagerService extends IBackupManager.Stub { mBackupParticipants.put(uid, set); } set.add(pkg.applicationInfo); + + // If we've never seen this app before, schedule a backup for it + if (!mEverStoredApps.contains(pkg.packageName)) { + if (DEBUG) Log.i(TAG, "New app " + pkg.packageName + + " never backed up; scheduling"); + try { + dataChanged(pkg.packageName); + } catch (RemoteException e) { + // can't happen; it's a local method call + } + } } } } @@ -528,6 +601,7 @@ class BackupManagerService extends IBackupManager.Stub { for (ApplicationInfo entry: set) { if (entry.packageName.equals(pkg.packageName)) { set.remove(entry); + removeEverBackedUp(pkg.packageName); break; } } @@ -540,7 +614,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Returns the set of all applications that define an android:backupAgent attribute - private List<PackageInfo> allAgentPackages() { + List<PackageInfo> allAgentPackages() { // !!! TODO: cache this and regenerate only when necessary int flags = PackageManager.GET_SIGNATURES; List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags); @@ -570,6 +644,65 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLockedInner(packageName, allApps); } + // Called from the backup thread: record that the given app has been successfully + // backed up at least once + void logBackupComplete(String packageName) { + if (mEverStoredStream != null && !packageName.equals(PACKAGE_MANAGER_SENTINEL)) { + synchronized (mEverStoredApps) { + if (mEverStoredApps.add(packageName)) { + try { + mEverStoredStream.writeUTF(packageName); + } catch (IOException e) { + Log.e(TAG, "Unable to log backup of " + packageName + ", ceasing log"); + try { + mEverStoredStream.close(); + } catch (IOException ioe) { + // we're dropping it; no need to handle an exception on close here + } + mEverStoredStream = null; + } + } + } + } + } + + // Remove our awareness of having ever backed up the given package + void removeEverBackedUp(String packageName) { + if (DEBUG) Log.v(TAG, "Removing backed-up knowledge of " + packageName + + ", new set:"); + + if (mEverStoredStream != null) { + synchronized (mEverStoredApps) { + // Rewrite the file and rename to overwrite. If we reboot in the middle, + // we'll recognize on initialization time that the package no longer + // exists and fix it up then. + File tempKnownFile = new File(mBaseStateDir, "processed.new"); + try { + mEverStoredStream.close(); + RandomAccessFile known = new RandomAccessFile(tempKnownFile, "rw"); + mEverStoredApps.remove(packageName); + for (String s : mEverStoredApps) { + known.writeUTF(s); + if (DEBUG) Log.v(TAG, " " + s); + } + known.close(); + tempKnownFile.renameTo(mEverStored); + mEverStoredStream = new RandomAccessFile(mEverStored, "rwd"); + } catch (IOException e) { + // Bad: we couldn't create the new copy. For safety's sake we + // abandon the whole process and remove all what's-backed-up + // state entirely, meaning we'll force a backup pass for every + // participant on the next boot or [re]install. + Log.w(TAG, "Error rewriting backed-up set; halting log"); + mEverStoredStream = null; + mEverStoredApps.clear(); + tempKnownFile.delete(); + mEverStored.delete(); + } + } + } + } + // Return the given transport private IBackupTransport getTransport(String transportName) { synchronized (mTransports) { @@ -799,6 +932,7 @@ class BackupManagerService extends IBackupManager.Stub { boolean success = false; try { agent.doBackup(savedState, backupData, newState); + logBackupComplete(packageName); success = true; } finally { if (savedState != null) { @@ -1142,7 +1276,11 @@ class BackupManagerService extends IBackupManager.Stub { File savedStateName = new File(mStateDir, packageName); newStateName.renameTo(savedStateName); } catch (Exception e) { + // If the agent fails restore, it might have put the app's data + // into an incoherent state. For consistency we wipe its data + // again in this case before propagating the exception Log.e(TAG, "Error restoring data for " + packageName, e); + clearApplicationDataSynchronous(packageName); } } } @@ -1235,7 +1373,8 @@ class BackupManagerService extends IBackupManager.Stub { } } } else { - Log.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"); + Log.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" + + " uid=" + Binder.getCallingUid()); } } @@ -1566,6 +1705,10 @@ class BackupManagerService extends IBackupManager.Stub { pw.println(" " + app.toString()); } } + pw.println("Ever backed up: " + mEverStoredApps.size()); + for (String pkg : mEverStoredApps) { + pw.println(" " + pkg); + } pw.println("Pending: " + mPendingBackups.size()); for (BackupRequest req : mPendingBackups.values()) { pw.println(" " + req); diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 5cdce5b3b63f..a682fcb444ba 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -254,15 +254,15 @@ class BatteryService extends Binder { // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some // applications may want to have smart behavior based on this. + Intent statusIntent = new Intent(); + statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); if (mPlugType != 0 && mLastPlugType == 0) { - Intent intent = new Intent(Intent.ACTION_POWER_CONNECTED); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + statusIntent.setAction(Intent.ACTION_POWER_CONNECTED); + mContext.sendBroadcast(statusIntent); } else if (mPlugType == 0 && mLastPlugType != 0) { - Intent intent = new Intent(Intent.ACTION_POWER_DISCONNECTED); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED); + mContext.sendBroadcast(statusIntent); } final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; @@ -289,10 +289,12 @@ class BatteryService extends Binder { sendIntent(); if (sendBatteryLow) { mSentLowBatteryBroadcast = true; - mContext.sendBroadcast(new Intent(Intent.ACTION_BATTERY_LOW)); + statusIntent.setAction(Intent.ACTION_BATTERY_LOW); + mContext.sendBroadcast(statusIntent); } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= BATTERY_LEVEL_CLOSE_WARNING) { mSentLowBatteryBroadcast = false; - mContext.sendBroadcast(new Intent(Intent.ACTION_BATTERY_OKAY)); + statusIntent.setAction(Intent.ACTION_BATTERY_OKAY); + mContext.sendBroadcast(statusIntent); } // This needs to be done after sendIntent() so that we get the lastest battery stats. diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 493bd099065f..62b839dc1861 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -74,7 +74,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static class ConnectivityThread extends Thread { private Context mContext; - + private ConnectivityThread(Context context) { super("ConnectivityThread"); mContext = context; @@ -89,11 +89,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Looper.loop(); } - + public static ConnectivityService getServiceInstance(Context context) { ConnectivityThread thread = new ConnectivityThread(context); thread.start(); - + synchronized (thread) { while (sServiceInstance == null) { try { @@ -101,27 +101,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { thread.wait(); } catch (InterruptedException ignore) { Log.e(TAG, - "Unexpected InterruptedException while waiting for ConnectivityService thread"); + "Unexpected InterruptedException while waiting"+ + " for ConnectivityService thread"); } } } - + return sServiceInstance; } } - + public static ConnectivityService getInstance(Context context) { return ConnectivityThread.getServiceInstance(context); } - + private ConnectivityService(Context context) { if (DBG) Log.v(TAG, "ConnectivityService starting up"); mContext = context; mNetTrackers = new NetworkStateTracker[2]; Handler handler = new MyHandler(); - + mNetworkPreference = getPersistedNetworkPreference(); - + /* * Create the network state trackers for Wi-Fi and mobile * data. Maybe this could be done with a factory class, @@ -137,7 +138,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mMobileDataStateTracker = new MobileDataStateTracker(context, handler); mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker; - + mActiveNetwork = null; mNumDnsEntries = 0; @@ -148,11 +149,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { t.startMonitoring(); // Constructing this starts it too - mWifiWatchdogService = new WifiWatchdogService(context, mWifiStateTracker); + mWifiWatchdogService = new WifiWatchdogService(context, + mWifiStateTracker); } /** - * Sets the preferred network. + * Sets the preferred network. * @param preference the new preference */ public synchronized void setNetworkPreference(int preference) { @@ -173,9 +175,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void persistNetworkPreference(int networkPreference) { final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference); + Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, + networkPreference); } - + private int getPersistedNetworkPreference() { final ContentResolver cr = mContext.getContentResolver(); @@ -187,9 +190,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; } - + /** - * Make the state of network connectivity conform to the preference settings. + * Make the state of network connectivity conform to the preference settings * In this method, we only tear down a non-preferred network. Establishing * a connection to the preferred network is taken care of when we handle * the disconnect event from the non-preferred network @@ -207,7 +210,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { ConnectivityManager.TYPE_WIFI); if (t.getNetworkInfo().getType() != mNetworkPreference) { - NetworkStateTracker otherTracker = mNetTrackers[otherNetType]; + NetworkStateTracker otherTracker = + mNetTrackers[otherNetType]; if (otherTracker.isAvailable()) { teardown(t); } @@ -229,7 +233,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { * Return NetworkInfo for the active (i.e., connected) network interface. * It is assumed that at most one network is active at a time. If more * than one is active, it is indeterminate which will be returned. - * @return the info for the active network, or {@code null} if none is active + * @return the info for the active network, or {@code null} if none is + * active */ public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); @@ -287,7 +292,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker != null) { - return tracker.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + return tracker.startUsingNetworkFeature(feature, getCallingPid(), + getCallingUid()); } return -1; } @@ -299,7 +305,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker != null) { - return tracker.stopUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); + return tracker.stopUsingNetworkFeature(feature, getCallingPid(), + getCallingUid()); } return -1; } @@ -307,9 +314,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. - * @param networkType the type of the network over which traffic to the specified - * host is to be routed - * @param hostAddress the IP address of the host to which the route is desired + * @param networkType the type of the network over which traffic to the + * specified host is to be routed + * @param hostAddress the IP address of the host to which the route is + * desired * @return {@code true} on success, {@code false} on failure */ public boolean requestRouteToHost(int networkType, int hostAddress) { @@ -340,7 +348,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) == 1; } - + /** * @see ConnectivityManager#setBackgroundDataSetting(boolean) */ @@ -348,22 +356,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); - + if (getBackgroundDataSetting() == allowBackgroundDataUsage) return; Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKGROUND_DATA, allowBackgroundDataUsage ? 1 : 0); - + Settings.Secure.BACKGROUND_DATA, + allowBackgroundDataUsage ? 1 : 0); + Intent broadcast = new Intent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); mContext.sendBroadcast(broadcast); - } - + } + private int getNumConnectedNetworks() { int numConnectedNets = 0; for (NetworkStateTracker nt : mNetTrackers) { - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { ++numConnectedNets; } } @@ -371,21 +381,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void enforceAccessPermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, - "ConnectivityService"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, + "ConnectivityService"); } private void enforceChangePermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, - "ConnectivityService"); - + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CHANGE_NETWORK_STATE, + "ConnectivityService"); } /** - * Handle a {@code DISCONNECTED} event. If this pertains to the non-active network, - * we ignore it. If it is for the active network, we send out a broadcast. - * But first, we check whether it might be possible to connect to a different - * network. + * Handle a {@code DISCONNECTED} event. If this pertains to the non-active + * network, we ignore it. If it is for the active network, we send out a + * broadcast. But first, we check whether it might be possible to connect + * to a different network. * @param info the {@code NetworkInfo} for the network */ private void handleDisconnect(NetworkInfo info) { @@ -399,7 +410,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { * getting the disconnect for a network that we explicitly disabled * in accordance with network preference policies. */ - if (mActiveNetwork == null || info.getType() != mActiveNetwork.getNetworkInfo().getType()) + if (mActiveNetwork == null || + info.getType() != mActiveNetwork.getNetworkInfo().getType()) return; NetworkStateTracker newNet; @@ -439,28 +451,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + info.getExtraInfo()); } if (switchTo != null) { otherNetworkConnected = switchTo.isConnected(); if (DBG) { if (otherNetworkConnected) { - Log.v(TAG, "Switching to already connected " + switchTo.getTypeName()); + Log.v(TAG, "Switching to already connected " + + switchTo.getTypeName()); } else { - Log.v(TAG, "Attempting to switch to " + switchTo.getTypeName()); + Log.v(TAG, "Attempting to switch to " + + switchTo.getTypeName()); } } - intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); + intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, + switchTo); } else { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } - if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + info.getTypeName() + + if (DBG) Log.v(TAG, "Sending DISCONNECT bcast for " + + info.getTypeName() + (switchTo == null ? "" : " other=" + switchTo.getTypeName())); mContext.sendStickyBroadcast(intent); /* - * If the failover network is already connected, then immediately send out - * a followup broadcast indicating successful failover + * If the failover network is already connected, then immediately send + * out a followup broadcast indicating successful failover */ if (switchTo != null && otherNetworkConnected) sendConnectedBroadcast(switchTo); @@ -477,7 +494,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + info.getExtraInfo()); } mContext.sendStickyBroadcast(intent); } @@ -499,9 +517,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { reasonText = " (" + reason + ")."; } - Log.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); + Log.v(TAG, "Attempt to connect to " + info.getTypeName() + + " failed" + reasonText); } - + Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); @@ -509,7 +528,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); } if (extraInfo != null) { - intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); + intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, + extraInfo); } if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); @@ -562,32 +582,34 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ if (!toredown || deadnet.getNetworkInfo().getType() != info.getType()) { mActiveNetwork = thisNet; - if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + info.getTypeName()); + if (DBG) Log.v(TAG, "Sending CONNECT bcast for " + + info.getTypeName()); thisNet.updateNetworkSettings(); sendConnectedBroadcast(info); if (isFailover) { otherNet.releaseWakeLock(); } } else { - if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn down network " + - info.getTypeName()); + if (DBG) Log.v(TAG, "Not broadcasting CONNECT_ACTION to torn " + + "down network " + info.getTypeName()); } } private void handleScanResultsAvailable(NetworkInfo info) { int networkType = info.getType(); if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network." - + " Don't know how to handle."); + if (DBG) Log.v(TAG, "Got ScanResultsAvailable for " + + info.getTypeName() + " network. Don't know how to handle."); } - + mNetTrackers[networkType].interpretScanResultsAvailable(); } - private void handleNotificationChange(boolean visible, int id, Notification notification) { + private void handleNotificationChange(boolean visible, int id, + Notification notification) { NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); - + if (visible) { notificationManager.notify(id, notification); } else { @@ -629,12 +651,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { int index = 1; String lastDns = ""; int numConnectedNets = 0; - int incrValue = ConnectivityManager.TYPE_MOBILE - ConnectivityManager.TYPE_WIFI; + int incrValue = ConnectivityManager.TYPE_MOBILE - + ConnectivityManager.TYPE_WIFI; int stopValue = ConnectivityManager.TYPE_MOBILE + incrValue; - for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue; netType += incrValue) { + for (int netType = ConnectivityManager.TYPE_WIFI; netType != stopValue; + netType += incrValue) { NetworkStateTracker nt = mNetTrackers[netType]; - if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + if (nt.getNetworkInfo().isConnected() && + !nt.isTeardownRequested()) { ++numConnectedNets; String[] dnsList = nt.getNameServers(); for (int i = 0; i < dnsList.length && dnsList[i] != null; i++) { @@ -652,23 +677,26 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNumDnsEntries = index - 1; // Notify the name resolver library of the change - SystemProperties.set("net.dnschange", String.valueOf(sDnsChangeCounter++)); + SystemProperties.set("net.dnschange", + String.valueOf(sDnsChangeCounter++)); return numConnectedNets; } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump ConnectivityService from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); + pw.println("Permission Denial: can't dump ConnectivityService " + + "from from pid=" + Binder.getCallingPid() + ", uid=" + + Binder.getCallingUid()); return; } if (mActiveNetwork == null) { pw.println("No active network"); } else { - pw.println("Active network: " + mActiveNetwork.getNetworkInfo().getTypeName()); + pw.println("Active network: " + + mActiveNetwork.getNetworkInfo().getTypeName()); } pw.println(); for (NetworkStateTracker nst : mNetTrackers) { @@ -685,29 +713,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { switch (msg.what) { case NetworkStateTracker.EVENT_STATE_CHANGED: info = (NetworkInfo) msg.obj; - if (DBG) Log.v(TAG, "ConnectivityChange for " + info.getTypeName() + ": " + + if (DBG) Log.v(TAG, "ConnectivityChange for " + + info.getTypeName() + ": " + info.getState() + "/" + info.getDetailedState()); // Connectivity state changed: // [31-13] Reserved for future use - // [12-9] Network subtype (for mobile network, as defined by TelephonyManager) - // [8-3] Detailed state ordinal (as defined by NetworkInfo.DetailedState) + // [12-9] Network subtype (for mobile network, as defined + // by TelephonyManager) + // [8-3] Detailed state ordinal (as defined by + // NetworkInfo.DetailedState) // [2-0] Network type (as defined by ConnectivityManager) int eventLogParam = (info.getType() & 0x7) | ((info.getDetailedState().ordinal() & 0x3f) << 3) | (info.getSubtype() << 9); - EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, eventLogParam); - - if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { + EventLog.writeEvent(EVENTLOG_CONNECTIVITY_STATE_CHANGED, + eventLogParam); + + if (info.getDetailedState() == + NetworkInfo.DetailedState.FAILED) { handleConnectionFailure(info); - } else if (info.getState() == NetworkInfo.State.DISCONNECTED) { + } else if (info.getState() == + NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (info.getState() == NetworkInfo.State.SUSPENDED) { // TODO: need to think this over. - // the logic here is, handle SUSPENDED the same as DISCONNECTED. The - // only difference being we are broadcasting an intent with NetworkInfo - // that's suspended. This allows the applications an opportunity to - // handle DISCONNECTED and SUSPENDED differently, or not. + // the logic here is, handle SUSPENDED the same as + // DISCONNECTED. The only difference being we are + // broadcasting an intent with NetworkInfo that's + // suspended. This allows the applications an + // opportunity to handle DISCONNECTED and SUSPENDED + // differently, or not. handleDisconnect(info); } else if (info.getState() == NetworkInfo.State.CONNECTED) { handleConnect(info); @@ -719,9 +755,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { info = (NetworkInfo) msg.obj; handleScanResultsAvailable(info); break; - + case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: - handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj); + handleNotificationChange(msg.arg1 == 1, msg.arg2, + (Notification) msg.obj); case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: handleConfigurationChange(); diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java index 9c1f9421a818..a71c39a0b3b5 100644 --- a/services/java/com/android/server/InputDevice.java +++ b/services/java/com/android/server/InputDevice.java @@ -66,19 +66,56 @@ public class InputDevice { MotionEvent generateMotion(InputDevice device, long curTime, long curTimeNano, boolean isAbs, Display display, int orientation, int metaState) { - if (!changed) { - return null; - } - float scaledX = x; float scaledY = y; float temp; float scaledPressure = 1.0f; float scaledSize = 0; int edgeFlags = 0; + + int action; + if (down != lastDown) { + if (isAbs) { + final AbsoluteInfo absX = device.absX; + final AbsoluteInfo absY = device.absY; + if (down && absX != null && absY != null) { + // We don't let downs start unless we are + // inside of the screen. There are two reasons for + // this: to avoid spurious touches when holding + // the edges of the device near the touchscreen, + // and to avoid reporting events if there are virtual + // keys on the touchscreen outside of the display + // area. + if (scaledX < absX.minValue || scaledX > absX.maxValue + || scaledY < absY.minValue || scaledY > absY.maxValue) { + if (false) Log.v("InputDevice", "Rejecting (" + scaledX + "," + + scaledY + "): outside of (" + + absX.minValue + "," + absY.minValue + + ")-(" + absX.maxValue + "," + + absY.maxValue + ")"); + return null; + } + } + } else { + x = y = 0; + } + lastDown = down; + if (down) { + action = MotionEvent.ACTION_DOWN; + downTime = curTime; + } else { + action = MotionEvent.ACTION_UP; + } + currentMove = null; + } else { + action = MotionEvent.ACTION_MOVE; + } + if (isAbs) { - int w = display.getWidth()-1; - int h = display.getHeight()-1; + final int dispW = display.getWidth()-1; + final int dispH = display.getHeight()-1; + int w = dispW; + int h = dispH; if (orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270) { int tmp = w; @@ -120,16 +157,17 @@ public class InputDevice { break; } - if (scaledX == 0) { - edgeFlags += MotionEvent.EDGE_LEFT; - } else if (scaledX == display.getWidth() - 1.0f) { - edgeFlags += MotionEvent.EDGE_RIGHT; - } - - if (scaledY == 0) { - edgeFlags += MotionEvent.EDGE_TOP; - } else if (scaledY == display.getHeight() - 1.0f) { - edgeFlags += MotionEvent.EDGE_BOTTOM; + if (action != MotionEvent.ACTION_DOWN) { + if (scaledX <= 0) { + edgeFlags += MotionEvent.EDGE_LEFT; + } else if (scaledX >= dispW) { + edgeFlags += MotionEvent.EDGE_RIGHT; + } + if (scaledY <= 0) { + edgeFlags += MotionEvent.EDGE_TOP; + } else if (scaledY >= dispH) { + edgeFlags += MotionEvent.EDGE_BOTTOM; + } } } else { @@ -153,41 +191,25 @@ public class InputDevice { } } - changed = false; - if (down != lastDown) { - int action; - lastDown = down; - if (down) { - action = MotionEvent.ACTION_DOWN; - downTime = curTime; - } else { - action = MotionEvent.ACTION_UP; - } - currentMove = null; - if (!isAbs) { - x = y = 0; - } - return MotionEvent.obtainNano(downTime, curTime, curTimeNano, action, - scaledX, scaledY, scaledPressure, scaledSize, metaState, - xPrecision, yPrecision, device.id, edgeFlags); - } else { - if (currentMove != null) { - if (false) Log.i("InputDevice", "Adding batch x=" + scaledX - + " y=" + scaledY + " to " + currentMove); - currentMove.addBatch(curTime, scaledX, scaledY, - scaledPressure, scaledSize, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Log.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; + if (currentMove != null) { + if (false) Log.i("InputDevice", "Adding batch x=" + scaledX + + " y=" + scaledY + " to " + currentMove); + currentMove.addBatch(curTime, scaledX, scaledY, + scaledPressure, scaledSize, metaState); + if (WindowManagerPolicy.WATCH_POINTER) { + Log.i("KeyInputQueue", "Updating: " + currentMove); } - MotionEvent me = MotionEvent.obtainNano(downTime, curTime, curTimeNano, - MotionEvent.ACTION_MOVE, scaledX, scaledY, - scaledPressure, scaledSize, metaState, - xPrecision, yPrecision, device.id, edgeFlags); + return null; + } + + MotionEvent me = MotionEvent.obtainNano(downTime, curTime, + curTimeNano, action, scaledX, scaledY, + scaledPressure, scaledSize, metaState, + xPrecision, yPrecision, device.id, edgeFlags); + if (action == MotionEvent.ACTION_MOVE) { currentMove = me; - return me; } + return me; } } diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java index 78cdf8b97eef..77051bd0023c 100644 --- a/services/java/com/android/server/KeyInputQueue.java +++ b/services/java/com/android/server/KeyInputQueue.java @@ -18,11 +18,13 @@ package com.android.server; import android.content.Context; import android.content.res.Configuration; +import android.os.Environment; import android.os.LatencyTimer; import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; +import android.util.Xml; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -30,10 +32,29 @@ import android.view.RawInputEvent; import android.view.Surface; import android.view.WindowManagerPolicy; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + public abstract class KeyInputQueue { static final String TAG = "KeyInputQueue"; - SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); + static final boolean DEBUG_VIRTUAL_KEYS = false; + + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + + final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); + final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); int mGlobalMetaState = 0; boolean mHaveGlobalMetaState = false; @@ -44,10 +65,14 @@ public abstract class KeyInputQueue { int mCacheCount; Display mDisplay = null; + int mDisplayWidth; + int mDisplayHeight; int mOrientation = Surface.ROTATION_0; int[] mKeyRotationMap = null; + VirtualKey mPressedVirtualKey = null; + PowerManager.WakeLock mWakeLock; static final int[] KEY_90_MAP = new int[] { @@ -110,11 +135,143 @@ public abstract class KeyInputQueue { QueuedEvent next; } + /** + * A key that exists as a part of the touch-screen, outside of the normal + * display area of the screen. + */ + static class VirtualKey { + int scancode; + int centerx; + int centery; + int width; + int height; + + int hitLeft; + int hitTop; + int hitRight; + int hitBottom; + + InputDevice lastDevice; + int lastKeycode; + + boolean checkHit(int x, int y) { + return (x >= hitLeft && x <= hitRight + && y >= hitTop && y <= hitBottom); + } + + void computeHitRect(InputDevice dev, int dw, int dh) { + if (dev == lastDevice) { + return; + } + + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode + + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); + + lastDevice = dev; + + int minx = dev.absX.minValue; + int maxx = dev.absX.maxValue; + + int halfw = width/2; + int left = centerx - halfw; + int right = centerx + halfw; + hitLeft = minx + ((left*maxx-minx)/dw); + hitRight = minx + ((right*maxx-minx)/dw); + + int miny = dev.absY.minValue; + int maxy = dev.absY.maxValue; + + int halfh = height/2; + int top = centery - halfh; + int bottom = centery + halfh; + hitTop = miny + ((top*maxy-miny)/dh); + hitBottom = miny + ((bottom*maxy-miny)/dh); + } + } + + private void readVirtualKeys() { + try { + FileInputStream fis = new FileInputStream( + "/sys/board_properties/virtualkeys.synaptics-rmi-touchscreen"); + InputStreamReader isr = new InputStreamReader(fis); + BufferedReader br = new BufferedReader(isr); + String str = br.readLine(); + if (str != null) { + String[] it = str.split(":"); + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "***** VIRTUAL KEYS: " + it); + final int N = it.length-6; + for (int i=0; i<=N; i+=6) { + if (!"0x01".equals(it[i])) { + Log.w(TAG, "Unknown virtual key type at elem #" + i + + ": " + it[i]); + continue; + } + try { + VirtualKey sb = new VirtualKey(); + sb.scancode = Integer.parseInt(it[i+1]); + sb.centerx = Integer.parseInt(it[i+2]); + sb.centery = Integer.parseInt(it[i+3]); + sb.width = Integer.parseInt(it[i+4]); + sb.height = Integer.parseInt(it[i+5]); + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key " + + sb.scancode + ": center=" + sb.centerx + "," + + sb.centery + " size=" + sb.width + "x" + + sb.height); + mVirtualKeys.add(sb); + } catch (NumberFormatException e) { + Log.w(TAG, "Bad number at region " + i + " in: " + + str, e); + } + } + } + br.close(); + } catch (FileNotFoundException e) { + Log.i(TAG, "No virtual keys found"); + } catch (IOException e) { + Log.w(TAG, "Error reading virtual keys", e); + } + } + + private void readExcludedDevices() { + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + Log.d(TAG, "addExcludedDevice " + name); + addExcludedDevice(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Log.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + } + KeyInputQueue(Context context) { if (MEASURE_LATENCY) { lt = new LatencyTimer(100, 1000); } + readVirtualKeys(); + readExcludedDevices(); + PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, @@ -131,6 +288,12 @@ public abstract class KeyInputQueue { public void setDisplay(Display display) { mDisplay = display; + + // We assume at this point that the display dimensions reflect the + // natural, unrotated display. We will perform hit tests for soft + // buttons based on that display. + mDisplayWidth = display.getWidth(); + mDisplayHeight = display.getHeight(); } public void getInputConfiguration(Configuration config) { @@ -165,6 +328,7 @@ public abstract class KeyInputQueue { public static native String getDeviceName(int deviceId); public static native int getDeviceClasses(int deviceId); + public static native void addExcludedDevice(String deviceName); public static native boolean getAbsoluteInfo(int deviceId, int axis, InputDevice.AbsoluteInfo outInfo); public static native int getSwitchState(int sw); @@ -173,6 +337,7 @@ public abstract class KeyInputQueue { public static native int getScancodeState(int deviceId, int sw); public static native int getKeycodeState(int sw); public static native int getKeycodeState(int deviceId, int sw); + public static native int scancodeToKeycode(int deviceId, int scancode); public static native boolean hasKeys(int[] keycodes, boolean[] keyExists); public static KeyEvent newKeyEvent(InputDevice device, long downTime, @@ -189,6 +354,7 @@ public abstract class KeyInputQueue { Thread mThread = new Thread("InputDeviceReader") { public void run() { + Log.d(TAG, "InputDeviceReader.run()"); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); @@ -339,36 +505,127 @@ public abstract class KeyInputQueue { } MotionEvent me; - me = di.mAbs.generateMotion(di, curTime, curTimeNano, true, - mDisplay, mOrientation, mGlobalMetaState); - if (false) Log.v(TAG, "Absolute: x=" + di.mAbs.x - + " y=" + di.mAbs.y + " ev=" + me); - if (me != null) { - if (WindowManagerPolicy.WATCH_POINTER) { - Log.i(TAG, "Enqueueing: " + me); + + InputDevice.MotionState ms = di.mAbs; + if (ms.changed) { + ms.changed = false; + + boolean doMotion = true; + + // Look for virtual buttons. + VirtualKey vk = mPressedVirtualKey; + if (vk != null) { + doMotion = false; + if (!ms.down) { + mPressedVirtualKey = null; + ms.lastDown = ms.down; + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, + "Generate key up for: " + vk.scancode); + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_KEYBOARD, + newKeyEvent(di, di.mDownTime, + curTime, false, + vk.lastKeycode, + 0, vk.scancode, 0)); + } + } else if (ms.down && !ms.lastDown) { + vk = findSoftButton(di); + if (vk != null) { + doMotion = false; + mPressedVirtualKey = vk; + vk.lastKeycode = scancodeToKeycode( + di.id, vk.scancode); + ms.lastDown = ms.down; + di.mDownTime = curTime; + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, + "Generate key down for: " + vk.scancode + + " (keycode=" + vk.lastKeycode + ")"); + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_KEYBOARD, + newKeyEvent(di, di.mDownTime, + curTime, true, + vk.lastKeycode, 0, + vk.scancode, 0)); + } + } + + if (doMotion) { + me = ms.generateMotion(di, curTime, + curTimeNano, true, mDisplay, + mOrientation, mGlobalMetaState); + if (false) Log.v(TAG, "Absolute: x=" + di.mAbs.x + + " y=" + di.mAbs.y + " ev=" + me); + if (me != null) { + if (WindowManagerPolicy.WATCH_POINTER) { + Log.i(TAG, "Enqueueing: " + me); + } + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_TOUCHSCREEN, me); + } } - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TOUCHSCREEN, me); } - me = di.mRel.generateMotion(di, curTime, curTimeNano, false, - mDisplay, mOrientation, mGlobalMetaState); - if (false) Log.v(TAG, "Relative: x=" + di.mRel.x - + " y=" + di.mRel.y + " ev=" + me); - if (me != null) { - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TRACKBALL, me); + + ms = di.mRel; + if (ms.changed) { + ms.changed = false; + + me = ms.generateMotion(di, curTime, + curTimeNano, false, mDisplay, + mOrientation, mGlobalMetaState); + if (false) Log.v(TAG, "Relative: x=" + di.mRel.x + + " y=" + di.mRel.y + " ev=" + me); + if (me != null) { + addLocked(di, curTimeNano, ev.flags, + RawInputEvent.CLASS_TRACKBALL, me); + } } } } } } - } - catch (RuntimeException exc) { + + } catch (RuntimeException exc) { Log.e(TAG, "InputReaderThread uncaught exception", exc); } } }; + private VirtualKey findSoftButton(InputDevice dev) { + final int N = mVirtualKeys.size(); + if (N <= 0) { + return null; + } + + final InputDevice.AbsoluteInfo absx = dev.absX; + final InputDevice.AbsoluteInfo absy = dev.absY; + final InputDevice.MotionState absm = dev.mAbs; + if (absx == null || absy == null || absm == null) { + return null; + } + + if (absm.x >= absx.minValue && absm.x <= absx.maxValue + && absm.y >= absy.minValue && absm.y <= absy.maxValue) { + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input (" + absm.x + + "," + absm.y + ") inside of display"); + return null; + } + + for (int i=0; i<N; i++) { + VirtualKey sb = mVirtualKeys.get(i); + sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test (" + absm.x + "," + + absm.y + ") in code " + sb.scancode + " - (" + sb.hitLeft + + "," + sb.hitTop + ")-(" + sb.hitRight + "," + + sb.hitBottom + ")"); + if (sb.checkHit(absm.x, absm.y)) { + if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!"); + return sb; + } + } + + return null; + } + /** * Returns a new meta state for the given keys and old state. */ diff --git a/services/java/com/android/server/MountListener.java b/services/java/com/android/server/MountListener.java index 2e430c849d97..3e535851b9e9 100644 --- a/services/java/com/android/server/MountListener.java +++ b/services/java/com/android/server/MountListener.java @@ -202,6 +202,7 @@ final class MountListener implements Runnable { byte[] buffer = new byte[100]; writeCommand(VOLD_CMD_SEND_UMS_STATUS); + mountMedia(Environment.getExternalStorageDirectory().getAbsolutePath()); while (true) { int count = inputStream.read(buffer); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 682eaa7c89bb..7025e79776cd 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4908,6 +4908,22 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(" resourcePath="); pw.println(ps.resourcePathString); if (ps.pkg != null) { pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); + pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); + pw.print(" densities="); pw.println(ps.pkg.supportsDensityList); + ArrayList<String> screens = new ArrayList<String>(); + if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { + screens.add("medium"); + } + if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { + screens.add("large"); + } + if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + screens.add("small,"); + } + pw.print(" supportsScreens="); pw.println(screens); } pw.print(" timeStamp="); pw.println(ps.getTimeStampStr()); pw.print(" signatures="); pw.println(ps.signatures); diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index a561d119cd79..67e8cf3a2683 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -171,6 +171,7 @@ public class WifiService extends IWifiManager.Stub { WifiService(Context context, WifiStateTracker tracker) { mContext = context; mWifiStateTracker = tracker; + mWifiStateTracker.enableRssiPolling(true); mBatteryStats = BatteryStatsService.getService(); mScanResultCache = new LinkedHashMap<String, ScanResult>( @@ -1367,9 +1368,11 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.cancel(mIdleIntent); mDeviceIdle = false; mScreenOff = false; + mWifiStateTracker.enableRssiPolling(true); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { Log.d(TAG, "ACTION_SCREEN_OFF"); mScreenOff = true; + mWifiStateTracker.enableRssiPolling(false); /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off * AND the "stay on while plugged in" setting doesn't match the diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 25aff5c60603..4bc7c682a02c 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -24,8 +24,10 @@ import static android.os.LocalPowerManager.TOUCH_UP_EVENT; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -102,6 +104,7 @@ import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; +import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; @@ -175,6 +178,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo */ static final int DEFAULT_DIM_DURATION = 200; + /** Amount of time (in milliseconds) to animate the fade-in-out transition for + * compatible windows. + */ + static final int DEFAULT_FADE_IN_OUT_DURATION = 400; + /** Adjustment to time to perform a dim, to make it more dramatic. */ static final int DIM_DURATION_MULTIPLIER = 6; @@ -328,12 +336,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo IInputMethodManager mInputMethodManager; SurfaceSession mFxSession; - Surface mDimSurface; - boolean mDimShown; - float mDimCurrentAlpha; - float mDimTargetAlpha; - float mDimDeltaPerMs; - long mLastDimAnimTime; + private DimAnimator mDimAnimator = null; Surface mBlurSurface; boolean mBlurShown; @@ -1861,44 +1864,51 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // artifacts when we unfreeze the display if some different animation // is running. if (!mDisplayFrozen) { - int animAttr = 0; - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_OPEN: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_CLOSE: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: - animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation - : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; - break; + Animation a; + if (lp != null && (lp.flags & FLAG_COMPATIBLE_WINDOW) != 0) { + a = new FadeInOutAnimation(enter); + if (DEBUG_ANIM) Log.v(TAG, + "applying FadeInOutAnimation for a window in compatibility mode"); + } else { + int animAttr = 0; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: + animAttr = enter + ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation + : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; + break; + } + a = loadAnimation(lp, animAttr); + if (DEBUG_ANIM) Log.v(TAG, "applyAnimation: wtoken=" + wtoken + + " anim=" + a + + " animAttr=0x" + Integer.toHexString(animAttr) + + " transit=" + transit); } - Animation a = loadAnimation(lp, animAttr); - if (DEBUG_ANIM) Log.v(TAG, "applyAnimation: wtoken=" + wtoken - + " anim=" + a - + " animAttr=0x" + Integer.toHexString(animAttr) - + " transit=" + transit); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = new RuntimeException(); @@ -5909,9 +5919,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final Rect display = mDisplayFrame; display.set(df); - if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW) != 0) { + if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { container.intersect(mCompatibleScreenFrame); - display.intersect(mCompatibleScreenFrame); + if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) { + display.intersect(mCompatibleScreenFrame); + } } final int pw = container.right - container.left; @@ -6628,12 +6640,17 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return false; } final Rect frame = shownFrame ? mShownFrame : mFrame; - if (frame.left <= 0 && frame.top <= 0 - && frame.right >= screenWidth - && frame.bottom >= screenHeight) { - return true; + + if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { + return frame.left <= mCompatibleScreenFrame.left && + frame.top <= mCompatibleScreenFrame.top && + frame.right >= mCompatibleScreenFrame.right && + frame.bottom >= mCompatibleScreenFrame.bottom; + } else { + return frame.left <= 0 && frame.top <= 0 + && frame.right >= screenWidth + && frame.bottom >= screenHeight; } - return false; } /** @@ -6647,13 +6664,16 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo boolean needsBackgroundFiller(int screenWidth, int screenHeight) { return // only if the application is requesting compatible window - (mAttrs.flags & mAttrs.FLAG_COMPATIBLE_WINDOW) != 0 && - // and only if the application wanted to fill the screen - mAttrs.width == mAttrs.FILL_PARENT && - mAttrs.height == mAttrs.FILL_PARENT && - // and only if the screen is bigger - ((mFrame.right - mFrame.right) < screenWidth || - (mFrame.bottom - mFrame.top) < screenHeight); + (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0 && + // only if it's visible + mHasDrawn && mViewVisibility == View.VISIBLE && + // and only if the application fills the compatible screen + mFrame.left <= mCompatibleScreenFrame.left && + mFrame.top <= mCompatibleScreenFrame.top && + mFrame.right >= mCompatibleScreenFrame.right && + mFrame.bottom >= mCompatibleScreenFrame.bottom && + // and starting window do not need background filler + mAttrs.type != mAttrs.TYPE_APPLICATION_STARTING; } boolean isFullscreen(int screenWidth, int screenHeight) { @@ -7266,17 +7286,27 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo public static WindowManager.LayoutParams findAnimations( ArrayList<AppWindowToken> order, - ArrayList<AppWindowToken> tokenList1, - ArrayList<AppWindowToken> tokenList2) { + ArrayList<AppWindowToken> openingTokenList1, + ArrayList<AppWindowToken> closingTokenList2) { // We need to figure out which animation to use... + + // First, check if there is a compatible window in opening/closing + // apps, and use it if exists. WindowManager.LayoutParams animParams = null; int animSrc = 0; - + animParams = findCompatibleWindowParams(openingTokenList1); + if (animParams == null) { + animParams = findCompatibleWindowParams(closingTokenList2); + } + if (animParams != null) { + return animParams; + } + //Log.i(TAG, "Looking for animations..."); for (int i=order.size()-1; i>=0; i--) { AppWindowToken wtoken = order.get(i); //Log.i(TAG, "Token " + wtoken + " with " + wtoken.windows.size() + " windows"); - if (tokenList1.contains(wtoken) || tokenList2.contains(wtoken)) { + if (openingTokenList1.contains(wtoken) || closingTokenList2.contains(wtoken)) { int j = wtoken.windows.size(); while (j > 0) { j--; @@ -7304,6 +7334,21 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo return animParams; } + private static LayoutParams findCompatibleWindowParams(ArrayList<AppWindowToken> tokenList) { + for (int appCount = tokenList.size() - 1; appCount >= 0; appCount--) { + AppWindowToken wtoken = tokenList.get(appCount); + // Just checking one window is sufficient as all windows have the compatible flag + // if the application is in compatibility mode. + if (wtoken.windows.size() > 0) { + WindowManager.LayoutParams params = wtoken.windows.get(0).mAttrs; + if ((params.flags & FLAG_COMPATIBLE_WINDOW) != 0) { + return params; + } + } + } + return null; + } + // ------------------------------------------------------------- // DummyAnimation // ------------------------------------------------------------- @@ -8457,7 +8502,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo obscured = true; } else if (opaqueDrawn && w.needsBackgroundFiller(dw, dh)) { if (SHOW_TRANSACTIONS) Log.d(TAG, "showing background filler"); - // This window is in compatibility mode, and needs background filler. + // This window is in compatibility mode, and needs background filler. obscured = true; if (mBackgroundFillerSurface == null) { try { @@ -8491,56 +8536,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (!dimming) { //Log.i(TAG, "DIM BEHIND: " + w); dimming = true; - mDimShown = true; - if (mDimSurface == null) { - if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " - + mDimSurface + ": CREATE"); - try { - mDimSurface = new Surface(mFxSession, 0, - -1, 16, 16, - PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); - } catch (Exception e) { - Log.e(TAG, "Exception creating Dim surface", e); - } - } - if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " - + mDimSurface + ": SHOW pos=(0,0) (" + - dw + "x" + dh + "), layer=" + (w.mAnimLayer-1)); - if (mDimSurface != null) { - try { - mDimSurface.setPosition(0, 0); - mDimSurface.setSize(dw, dh); - mDimSurface.show(); - } catch (RuntimeException e) { - Log.w(TAG, "Failure showing dim surface", e); - } - } - } - mDimSurface.setLayer(w.mAnimLayer-1); - final float target = w.mExiting ? 0 : attrs.dimAmount; - if (mDimTargetAlpha != target) { - // If the desired dim level has changed, then - // start an animation to it. - mLastDimAnimTime = currentTime; - long duration = (w.mAnimating && w.mAnimation != null) - ? w.mAnimation.computeDurationHint() - : DEFAULT_DIM_DURATION; - if (target > mDimTargetAlpha) { - // This is happening behind the activity UI, - // so we can make it run a little longer to - // give a stronger impression without disrupting - // the user. - duration *= DIM_DURATION_MULTIPLIER; + if (mDimAnimator == null) { + mDimAnimator = new DimAnimator(mFxSession); } - if (duration < 1) { - // Don't divide by zero - duration = 1; - } - mDimTargetAlpha = target; - mDimDeltaPerMs = (mDimTargetAlpha-mDimCurrentAlpha) - / duration; + mDimAnimator.show(dw, dh); } + mDimAnimator.updateParameters(w, currentTime); } if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { if (!blurring) { @@ -8588,58 +8589,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - if (!dimming && mDimShown) { - // Time to hide the dim surface... start fading. - if (mDimTargetAlpha != 0) { - mLastDimAnimTime = currentTime; - mDimTargetAlpha = 0; - mDimDeltaPerMs = (-mDimCurrentAlpha) / DEFAULT_DIM_DURATION; - } - } - - if (mDimShown && mLastDimAnimTime != 0) { - mDimCurrentAlpha += mDimDeltaPerMs - * (currentTime-mLastDimAnimTime); - boolean more = true; - if (mDisplayFrozen) { - // If the display is frozen, there is no reason to animate. - more = false; - } else if (mDimDeltaPerMs > 0) { - if (mDimCurrentAlpha > mDimTargetAlpha) { - more = false; - } - } else if (mDimDeltaPerMs < 0) { - if (mDimCurrentAlpha < mDimTargetAlpha) { - more = false; - } - } else { - more = false; - } - - // Do we need to continue animating? - if (more) { - if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " - + mDimSurface + ": alpha=" + mDimCurrentAlpha); - mLastDimAnimTime = currentTime; - mDimSurface.setAlpha(mDimCurrentAlpha); - animating = true; - } else { - mDimCurrentAlpha = mDimTargetAlpha; - mLastDimAnimTime = 0; - if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " - + mDimSurface + ": final alpha=" + mDimCurrentAlpha); - mDimSurface.setAlpha(mDimCurrentAlpha); - if (!dimming) { - if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + mDimSurface - + ": HIDE"); - try { - mDimSurface.hide(); - } catch (RuntimeException e) { - Log.w(TAG, "Illegal argument exception hiding dim surface"); - } - mDimShown = false; - } - } + if (mDimAnimator != null && mDimAnimator.mDimShown) { + animating |= mDimAnimator.updateSurface(dimming, currentTime, mDisplayFrozen); } if (!blurring && mBlurShown) { @@ -9182,11 +9133,11 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); pw.print(" mLayoutNeeded="); pw.print(mLayoutNeeded); pw.print(" mBlurShown="); pw.println(mBlurShown); - pw.print(" mDimShown="); pw.print(mDimShown); - pw.print(" current="); pw.print(mDimCurrentAlpha); - pw.print(" target="); pw.print(mDimTargetAlpha); - pw.print(" delta="); pw.print(mDimDeltaPerMs); - pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); + if (mDimAnimator != null) { + mDimAnimator.printTo(pw); + } else { + pw.print( " no DimAnimator "); + } pw.print(" mInputMethodAnimLayerAdjustment="); pw.println(mInputMethodAnimLayerAdjustment); pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); @@ -9227,4 +9178,188 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo synchronized (mKeyguardDisabled) { } synchronized (mKeyWaiter) { } } + + /** + * DimAnimator class that controls the dim animation. This holds the surface and + * all state used for dim animation. + */ + private static class DimAnimator { + Surface mDimSurface; + boolean mDimShown = false; + float mDimCurrentAlpha; + float mDimTargetAlpha; + float mDimDeltaPerMs; + long mLastDimAnimTime; + + DimAnimator (SurfaceSession session) { + if (mDimSurface == null) { + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + + mDimSurface + ": CREATE"); + try { + mDimSurface = new Surface(session, 0, -1, 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM); + } catch (Exception e) { + Log.e(TAG, "Exception creating Dim surface", e); + } + } + } + + /** + * Show the dim surface. + */ + void show(int dw, int dh) { + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + + dw + "x" + dh + ")"); + mDimShown = true; + try { + mDimSurface.setPosition(0, 0); + mDimSurface.setSize(dw, dh); + mDimSurface.show(); + } catch (RuntimeException e) { + Log.w(TAG, "Failure showing dim surface", e); + } + } + + /** + * Set's the dim surface's layer and update dim parameters that will be used in + * {@link updateSurface} after all windows are examined. + */ + void updateParameters(WindowState w, long currentTime) { + mDimSurface.setLayer(w.mAnimLayer-1); + + final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; + if (SHOW_TRANSACTIONS) Log.i(TAG, "layer=" + (w.mAnimLayer-1) + ", target=" + target); + if (mDimTargetAlpha != target) { + // If the desired dim level has changed, then + // start an animation to it. + mLastDimAnimTime = currentTime; + long duration = (w.mAnimating && w.mAnimation != null) + ? w.mAnimation.computeDurationHint() + : DEFAULT_DIM_DURATION; + if (target > mDimTargetAlpha) { + // This is happening behind the activity UI, + // so we can make it run a little longer to + // give a stronger impression without disrupting + // the user. + duration *= DIM_DURATION_MULTIPLIER; + } + if (duration < 1) { + // Don't divide by zero + duration = 1; + } + mDimTargetAlpha = target; + mDimDeltaPerMs = (mDimTargetAlpha-mDimCurrentAlpha) / duration; + } + } + + /** + * Updating the surface's alpha. Returns true if the animation continues, or returns + * false when the animation is finished and the dim surface is hidden. + */ + boolean updateSurface(boolean dimming, long currentTime, boolean displayFrozen) { + if (!dimming) { + if (mDimTargetAlpha != 0) { + mLastDimAnimTime = currentTime; + mDimTargetAlpha = 0; + mDimDeltaPerMs = (-mDimCurrentAlpha) / DEFAULT_DIM_DURATION; + } + } + + boolean animating = false; + if (mLastDimAnimTime != 0) { + mDimCurrentAlpha += mDimDeltaPerMs + * (currentTime-mLastDimAnimTime); + boolean more = true; + if (displayFrozen) { + // If the display is frozen, there is no reason to animate. + more = false; + } else if (mDimDeltaPerMs > 0) { + if (mDimCurrentAlpha > mDimTargetAlpha) { + more = false; + } + } else if (mDimDeltaPerMs < 0) { + if (mDimCurrentAlpha < mDimTargetAlpha) { + more = false; + } + } else { + more = false; + } + + // Do we need to continue animating? + if (more) { + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + + mDimSurface + ": alpha=" + mDimCurrentAlpha); + mLastDimAnimTime = currentTime; + mDimSurface.setAlpha(mDimCurrentAlpha); + animating = true; + } else { + mDimCurrentAlpha = mDimTargetAlpha; + mLastDimAnimTime = 0; + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + + mDimSurface + ": final alpha=" + mDimCurrentAlpha); + mDimSurface.setAlpha(mDimCurrentAlpha); + if (!dimming) { + if (SHOW_TRANSACTIONS) Log.i(TAG, " DIM " + mDimSurface + + ": HIDE"); + try { + mDimSurface.hide(); + } catch (RuntimeException e) { + Log.w(TAG, "Illegal argument exception hiding dim surface"); + } + mDimShown = false; + } + } + } + return animating; + } + + public void printTo(PrintWriter pw) { + pw.print(" mDimShown="); pw.print(mDimShown); + pw.print(" current="); pw.print(mDimCurrentAlpha); + pw.print(" target="); pw.print(mDimTargetAlpha); + pw.print(" delta="); pw.print(mDimDeltaPerMs); + pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); + } + } + + /** + * Animation that fade in after 0.5 interpolate time, or fade out in reverse order. + * This is used for opening/closing transition for apps in compatible mode. + */ + private static class FadeInOutAnimation extends Animation { + int mWidth; + boolean mFadeIn; + + public FadeInOutAnimation(boolean fadeIn) { + setInterpolator(new AccelerateInterpolator()); + setDuration(DEFAULT_FADE_IN_OUT_DURATION); + mFadeIn = fadeIn; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float x = interpolatedTime; + if (!mFadeIn) { + x = 1.0f - x; // reverse the interpolation for fade out + } + if (x < 0.5) { + // move the window out of the screen. + t.getMatrix().setTranslate(mWidth, 0); + } else { + t.getMatrix().setTranslate(0, 0);// show + t.setAlpha((x - 0.5f) * 2); + } + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + // width is the screen width {@see AppWindowToken#stepAnimatinoLocked} + mWidth = width; + } + + @Override + public int getZAdjustment() { + return Animation.ZORDER_TOP; + } + } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 25991f28ce05..e1ca2015fac7 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -3528,8 +3528,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen intent = new Intent(intent); // Collect information about the target of the Intent. - // Must do this before locking, because resolving the intent - // may require launching a process to run its content provider. ActivityInfo aInfo; try { ResolveInfo rInfo = @@ -3663,17 +3661,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - final int startActivityInPackage(int uid, + public final int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded) { + + // This is so super not safe, that only the system (or okay root) + // can do it. + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + throw new SecurityException( + "startActivityInPackage only available to the system"); + } + final boolean componentSpecified = intent.getComponent() != null; // Don't modify the client's object! intent = new Intent(intent); // Collect information about the target of the Intent. - // Must do this before locking, because resolving the intent - // may require launching a process to run its content provider. ActivityInfo aInfo; try { ResolveInfo rInfo = @@ -11876,10 +11881,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen config.reqTouchScreen = mConfiguration.touchscreen; config.reqKeyboardType = mConfiguration.keyboard; config.reqNavigation = mConfiguration.navigation; - if (mConfiguration.navigation != Configuration.NAVIGATION_NONAV) { + if (mConfiguration.navigation == Configuration.NAVIGATION_DPAD + || mConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) { config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV; } - if (mConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED) { + if (mConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED + && mConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) { config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD; } } diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 39a1ee050226..c834b34e3568 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -41,7 +41,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { final BatteryStatsImpl mStats; Context mContext; - + BatteryStatsService(String filename) { mStats = new BatteryStatsImpl(filename); } diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp index 63830d59c24a..f27596c5c581 100644 --- a/services/jni/com_android_server_KeyInputQueue.cpp +++ b/services/jni/com_android_server_KeyInputQueue.cpp @@ -110,6 +110,23 @@ android_server_KeyInputQueue_getDeviceName(JNIEnv* env, jobject clazz, return NULL; } +static void +android_server_KeyInputQueue_addExcludedDevice(JNIEnv* env, jobject clazz, + jstring deviceName) +{ + gLock.lock(); + sp<EventHub> hub = gHub; + if (hub == NULL) { + hub = new EventHub; + gHub = hub; + } + gLock.unlock(); + + const char* nameStr = env->GetStringUTFChars(deviceName, NULL); + gHub->addExcludedDevice(nameStr); + env->ReleaseStringUTFChars(deviceName, nameStr); +} + static jboolean android_server_KeyInputQueue_getAbsoluteInfo(JNIEnv* env, jobject clazz, jint deviceId, jint axis, @@ -205,6 +222,23 @@ android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz, return st; } +static jint +android_server_KeyInputQueue_scancodeToKeycode(JNIEnv* env, jobject clazz, + jint deviceId, jint scancode) +{ + jint res = 0; + gLock.lock(); + if (gHub != NULL) { + int32_t keycode; + uint32_t flags; + gHub->scancodeToKeycode(deviceId, scancode, &keycode, &flags); + res = keycode; + } + gLock.unlock(); + + return res; +} + static jboolean android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz, jintArray keyCodes, jbooleanArray outFlags) @@ -238,6 +272,8 @@ static JNINativeMethod gInputMethods[] = { (void*) android_server_KeyInputQueue_getDeviceClasses }, { "getDeviceName", "(I)Ljava/lang/String;", (void*) android_server_KeyInputQueue_getDeviceName }, + { "addExcludedDevice", "(Ljava/lang/String;)V", + (void*) android_server_KeyInputQueue_addExcludedDevice }, { "getAbsoluteInfo", "(IILcom/android/server/InputDevice$AbsoluteInfo;)Z", (void*) android_server_KeyInputQueue_getAbsoluteInfo }, { "getSwitchState", "(I)I", @@ -254,6 +290,8 @@ static JNINativeMethod gInputMethods[] = { (void*) android_server_KeyInputQueue_getKeycodeStateDevice }, { "hasKeys", "([I[Z)Z", (void*) android_server_KeyInputQueue_hasKeys }, + { "scancodeToKeycode", "(II)I", + (void*) android_server_KeyInputQueue_scancodeToKeycode }, }; int register_android_server_KeyInputQueue(JNIEnv* env) diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index 7f2b8496f592..0bb2df156c75 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.telephony.CellLocation; +import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; @@ -260,8 +261,8 @@ public interface Phone { /** * Get current coarse-grained voice call state. - * Use {@link #registerForPhoneStateChanged(Handler, int, Object) - * registerForPhoneStateChanged()} for change notification. <p> + * Use {@link #registerForPreciseCallStateChanged(Handler, int, Object) + * registerForPreciseCallStateChanged()} for change notification. <p> * If the phone has an active call and call waiting occurs, * then the phone state is RINGING not OFFHOOK * <strong>Note:</strong> @@ -315,18 +316,21 @@ public interface Phone { void unregisterForUnknownConnection(Handler h); /** - * Notifies when any aspect of the voice call state changes. + * Register for getting notifications for change in the Call State {@link Call.State} + * This is called PreciseCallState because the call state is more precise than the + * {@link Phone.State} which can be obtained using the {@link PhoneStateListener} + * * Resulting events will have an AsyncResult in <code>Message.obj</code>. * AsyncResult.userData will be set to the obj argument here. * The <em>h</em> parameter is held only by a weak reference. */ - void registerForPhoneStateChanged(Handler h, int what, Object obj); + void registerForPreciseCallStateChanged(Handler h, int what, Object obj); /** * Unregisters for voice call state change notifications. * Extraneous calls are tolerated silently. */ - void unregisterForPhoneStateChanged(Handler h); + void unregisterForPreciseCallStateChanged(Handler h); /** @@ -556,8 +560,8 @@ public interface Phone { /** * Answers a ringing or waiting call. Active calls, if any, go on hold. * Answering occurs asynchronously, and final notification occurs via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. * * @exception CallStateException when no call is ringing or waiting */ @@ -567,8 +571,8 @@ public interface Phone { * Reject (ignore) a ringing call. In GSM, this means UDUB * (User Determined User Busy). Reject occurs asynchronously, * and final notification occurs via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. * * @exception CallStateException when no call is ringing or waiting */ @@ -578,8 +582,8 @@ public interface Phone { * Places any active calls on hold, and makes any held calls * active. Switch occurs asynchronously and may fail. * Final notification occurs via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. * * @exception CallStateException if a call is ringing, waiting, or * dialing/alerting. In these cases, this operation may not be performed. @@ -596,8 +600,8 @@ public interface Phone { /** * Conferences holding and active. Conference occurs asynchronously * and may fail. Final notification occurs via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. * * @exception CallStateException if canConference() would return false. * In these cases, this operation may not be performed. @@ -631,8 +635,8 @@ public interface Phone { * Connects the two calls and disconnects the subscriber from both calls * Explicit Call Transfer occurs asynchronously * and may fail. Final notification occurs via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. * * @exception CallStateException if canTransfer() would return false. * In these cases, this operation may not be performed. @@ -659,8 +663,8 @@ public interface Phone { * IDLE, ACTIVE, DIALING, ALERTING, or DISCONNECTED. * * State change notification is available via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. */ Call getForegroundCall(); @@ -676,8 +680,8 @@ public interface Phone { * IDLE, HOLDING or DISCONNECTED. * * State change notification is available via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. */ Call getBackgroundCall(); @@ -693,8 +697,8 @@ public interface Phone { * IDLE, INCOMING, WAITING or DISCONNECTED. * * State change notification is available via - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()}. + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. */ Call getRingingCall(); @@ -1067,8 +1071,8 @@ public interface Phone { /** * Gets current mute status. Use - * {@link #registerForPhoneStateChanged(android.os.Handler, int, - * java.lang.Object) registerForPhoneStateChanged()} + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()} * as a change notifcation, although presently phone state changed is not * fired when setMute() is called. * diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index 1509a6bcb1e3..fbda221ddbbb 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -119,7 +119,7 @@ public abstract class PhoneBase implements Phone { } - protected final RegistrantList mPhoneStateRegistrants + protected final RegistrantList mPreciseCallStateRegistrants = new RegistrantList(); protected final RegistrantList mNewRingingConnectionRegistrants @@ -221,25 +221,24 @@ public abstract class PhoneBase implements Phone { } // Inherited documentation suffices. - public void registerForPhoneStateChanged(Handler h, int what, Object obj) { + public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) { checkCorrectThread(h); - mPhoneStateRegistrants.addUnique(h, what, obj); + mPreciseCallStateRegistrants.addUnique(h, what, obj); } // Inherited documentation suffices. - public void unregisterForPhoneStateChanged(Handler h) { - mPhoneStateRegistrants.remove(h); + public void unregisterForPreciseCallStateChanged(Handler h) { + mPreciseCallStateRegistrants.remove(h); } /** - * Notify registrants of a PhoneStateChanged. * Subclasses of Phone probably want to replace this with a * version scoped to their packages */ - protected void notifyCallStateChangedP() { + protected void notifyPreciseCallStateChangedP() { AsyncResult ar = new AsyncResult(null, this, null); - mPhoneStateRegistrants.notifyRegistrants(ar); + mPreciseCallStateRegistrants.notifyRegistrants(ar); } // Inherited documentation suffices. diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java index da002683a895..979f0cdca574 100644 --- a/telephony/java/com/android/internal/telephony/PhoneProxy.java +++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.telephony.CellLocation; +import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.util.Log; @@ -210,12 +211,12 @@ public class PhoneProxy extends Handler implements Phone { mActivePhone.unregisterForUnknownConnection(h); } - public void registerForPhoneStateChanged(Handler h, int what, Object obj) { - mActivePhone.registerForPhoneStateChanged(h, what, obj); + public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) { + mActivePhone.registerForPreciseCallStateChanged(h, what, obj); } - public void unregisterForPhoneStateChanged(Handler h) { - mActivePhone.unregisterForPhoneStateChanged(h); + public void unregisterForPreciseCallStateChanged(Handler h) { + mActivePhone.unregisterForPreciseCallStateChanged(h); } public void registerForNewRingingConnection(Handler h, int what, Object obj) { diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java index 5ec4020d6fdc..8a1e928ad136 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java +++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java @@ -109,4 +109,10 @@ public interface TelephonyProperties /** The international dialing prefix conversion string */ static final String PROPERTY_IDP_STRING = "ro.cdma.idpstring"; + + /** + * Defines the schema for the carrier specified OTASP number + */ + static final String PROPERTY_OTASP_NUM_SCHEMA = "ro.cdma.otaspnumschema"; + } diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index 0918a8c1437a..bdcea92089ce 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -18,17 +18,23 @@ package com.android.internal.telephony.cdma; import android.app.ActivityManagerNative; import android.content.Context; +import android.content.ContentValues; import android.content.Intent; +import android.content.res.Configuration; import android.content.SharedPreferences; +import android.database.SQLException; +import android.net.Uri; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; +import android.os.RemoteException; import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Settings; +import android.provider.Telephony; import android.telephony.CellLocation; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; @@ -36,11 +42,16 @@ import android.telephony.SignalStrength; import android.text.TextUtils; import android.util.Log; +import com.android.internal.telephony.Call; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.Connection; import com.android.internal.telephony.DataConnection; +// TODO(Moto): need to move MccTable from telephony.gsm to telephony +// since there is no difference between CDMA and GSM for MccTable and +// CDMA uses gsm's MccTable is not good. +import com.android.internal.telephony.gsm.MccTable; import com.android.internal.telephony.IccCard; import com.android.internal.telephony.IccException; import com.android.internal.telephony.IccFileHandler; @@ -56,15 +67,23 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; +import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA; +import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC; +import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY; + import java.util.List; import java.util.Timer; import java.util.TimerTask; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * {@hide} */ public class CDMAPhone extends PhoneBase { static final String LOG_TAG = "CDMA"; - private static final boolean LOCAL_DEBUG = true; + private static final boolean DBG = true; // Default Emergency Callback Mode exit timer private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000; @@ -99,6 +118,8 @@ public class CDMAPhone extends PhoneBase { private Registrant mECMExitRespRegistrant; private String mEsn; private String mMeid; + // string to define how the carrier specifies its own ota sp number + private String mCarrierOtaSpNumSchema; // A runnable which is used to automatically exit from ECM after a period of time. private Runnable mExitEcmRunnable = new Runnable() { @@ -154,6 +175,27 @@ public class CDMAPhone extends PhoneBase { String inEcm=SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false"); mIsPhoneInECMState = inEcm.equals("true"); + // get the string that specifies the carrier OTA Sp number + mCarrierOtaSpNumSchema = SystemProperties.get( + TelephonyProperties.PROPERTY_OTASP_NUM_SCHEMA,""); + + // Sets operator alpha property by retrieving from build-time system property + String operatorAlpha = SystemProperties.get("ro.cdma.home.operator.alpha"); + setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, operatorAlpha); + + // Sets operator numeric property by retrieving from build-time system property + String operatorNumeric = SystemProperties.get("ro.cdma.home.operator.numeric"); + setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric); + + // Sets iso country property by retrieving from build-time system property + setIsoCountryProperty(operatorNumeric); + + // Sets current entry in the telephony carrier table + updateCurrentCarrierInProvider(operatorNumeric); + + // Updates MCC MNC device configuration information + updateMccMncConfiguration(operatorNumeric); + // Notify voicemails. notifier.notifyMessageWaitingChanged(this); } @@ -202,7 +244,7 @@ public class CDMAPhone extends PhoneBase { } protected void finalize() { - if(LOCAL_DEBUG) Log.d(LOG_TAG, "CDMAPhone finalized"); + if(DBG) Log.d(LOG_TAG, "CDMAPhone finalized"); } @@ -427,13 +469,7 @@ public class CDMAPhone extends PhoneBase { } public String getSubscriberId() { - // Subscriber ID is the combination of MCC+MNC+MIN as CDMA IMSI - // TODO(Moto): Replace with call to mRuimRecords.getIMSI_M() when implemented. - if ((getServiceState().getOperatorNumeric() != null) && (getCdmaMin() != null)) { - return (getServiceState().getOperatorNumeric() + getCdmaMin()); - } else { - return null; - } + return mSST.getImsi(); } public boolean canConference() { @@ -783,19 +819,19 @@ public class CDMAPhone extends PhoneBase { } /** - * Notify any interested party of a Phone state change. + * Notify any interested party of a Phone state change {@link Phone.State} */ /*package*/ void notifyPhoneStateChanged() { mNotifier.notifyPhoneState(this); } /** - * Notifies registrants (ie, activities in the Phone app) about - * changes to call state (including Phone and Connection changes). + * Notify registrants of a change in the call state. This notifies changes in {@link Call.State} + * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged. */ - /*package*/ void notifyCallStateChanged() { + /*package*/ void notifyPreciseCallStateChanged() { /* we'd love it if this was package-scoped*/ - super.notifyCallStateChangedP(); + super.notifyPreciseCallStateChangedP(); } void notifyServiceStateChanged(ServiceState ss) { @@ -934,7 +970,7 @@ public class CDMAPhone extends PhoneBase { break; } - if (LOCAL_DEBUG) Log.d(LOG_TAG, "Baseband version: " + ar.result); + if (DBG) Log.d(LOG_TAG, "Baseband version: " + ar.result); setSystemProperty(TelephonyProperties.PROPERTY_BASEBAND_VERSION, (String)ar.result); } break; @@ -1128,10 +1164,10 @@ public class CDMAPhone extends PhoneBase { mSMS.setCellBroadcastConfig(configValuesArray, response); } - public static final String IS683A_FEATURE_CODE = "*228" ; - public static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4 ; - public static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2 ; - public static final int IS683A_SYS_SEL_CODE_OFFSET = 4; + private static final String IS683A_FEATURE_CODE = "*228"; + private static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4; + private static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2; + private static final int IS683A_SYS_SEL_CODE_OFFSET = 4; private static final int IS683_CONST_800MHZ_A_BAND = 0; private static final int IS683_CONST_800MHZ_B_BAND = 1; @@ -1141,6 +1177,7 @@ public class CDMAPhone extends PhoneBase { private static final int IS683_CONST_1900MHZ_D_BLOCK = 5; private static final int IS683_CONST_1900MHZ_E_BLOCK = 6; private static final int IS683_CONST_1900MHZ_F_BLOCK = 7; + private static final int INVALID_SYSTEM_SELECTION_CODE = -1; private boolean isIs683OtaSpDialStr(String dialStr) { int sysSelCodeInt; @@ -1151,38 +1188,146 @@ public class CDMAPhone extends PhoneBase { if (dialStr.equals(IS683A_FEATURE_CODE)) { isOtaspDialString = true; } - } else if ((dialStr.regionMatches(0, IS683A_FEATURE_CODE, 0, - IS683A_FEATURE_CODE_NUM_DIGITS) == true) - && (dialStrLen >= - (IS683A_FEATURE_CODE_NUM_DIGITS + IS683A_SYS_SEL_CODE_NUM_DIGITS))) { - StringBuilder sb = new StringBuilder(dialStr); - // Separate the System Selection Code into its own string - char[] sysSel = new char[2]; - sb.delete(0, IS683A_SYS_SEL_CODE_OFFSET); - sb.getChars(0, IS683A_SYS_SEL_CODE_NUM_DIGITS, sysSel, 0); - - if ((PhoneNumberUtils.isISODigit(sysSel[0])) - && (PhoneNumberUtils.isISODigit(sysSel[1]))) { - String sysSelCode = new String(sysSel); - sysSelCodeInt = Integer.parseInt((String)sysSelCode); - switch (sysSelCodeInt) { - case IS683_CONST_800MHZ_A_BAND: - case IS683_CONST_800MHZ_B_BAND: - case IS683_CONST_1900MHZ_A_BLOCK: - case IS683_CONST_1900MHZ_B_BLOCK: - case IS683_CONST_1900MHZ_C_BLOCK: - case IS683_CONST_1900MHZ_D_BLOCK: - case IS683_CONST_1900MHZ_E_BLOCK: - case IS683_CONST_1900MHZ_F_BLOCK: - isOtaspDialString = true; - break; + } else { + sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr); + switch (sysSelCodeInt) { + case IS683_CONST_800MHZ_A_BAND: + case IS683_CONST_800MHZ_B_BAND: + case IS683_CONST_1900MHZ_A_BLOCK: + case IS683_CONST_1900MHZ_B_BLOCK: + case IS683_CONST_1900MHZ_C_BLOCK: + case IS683_CONST_1900MHZ_D_BLOCK: + case IS683_CONST_1900MHZ_E_BLOCK: + case IS683_CONST_1900MHZ_F_BLOCK: + isOtaspDialString = true; + break; + default: + break; + } + } + return isOtaspDialString; + } + /** + * This function extracts the system selection code from the dial string. + */ + private int extractSelCodeFromOtaSpNum(String dialStr) { + int dialStrLen = dialStr.length(); + int sysSelCodeInt = INVALID_SYSTEM_SELECTION_CODE; + + if ((dialStr.regionMatches(0, IS683A_FEATURE_CODE, + 0, IS683A_FEATURE_CODE_NUM_DIGITS)) && + (dialStrLen >= (IS683A_FEATURE_CODE_NUM_DIGITS + + IS683A_SYS_SEL_CODE_NUM_DIGITS))) { + // Since we checked the condition above, the system selection code + // extracted from dialStr will not cause any exception + sysSelCodeInt = Integer.parseInt ( + dialStr.substring (IS683A_FEATURE_CODE_NUM_DIGITS, + IS683A_FEATURE_CODE_NUM_DIGITS + IS683A_SYS_SEL_CODE_NUM_DIGITS)); + } + if (DBG) Log.d(LOG_TAG, "extractSelCodeFromOtaSpNum " + sysSelCodeInt); + return sysSelCodeInt; + } - default: + /** + * This function checks if the system selection code extracted from + * the dial string "sysSelCodeInt' is the system selection code specified + * in the carrier ota sp number schema "sch". + */ + private boolean + checkOtaSpNumBasedOnSysSelCode (int sysSelCodeInt, String sch[]) { + boolean isOtaSpNum = false; + try { + // Get how many number of system selection code ranges + int selRc = Integer.parseInt((String)sch[1]); + for (int i = 0; i < selRc; i++) { + if (!TextUtils.isEmpty(sch[i+2]) && !TextUtils.isEmpty(sch[i+3])) { + int selMin = Integer.parseInt((String)sch[i+2]); + int selMax = Integer.parseInt((String)sch[i+3]); + // Check if the selection code extracted from the dial string falls + // within any of the range pairs specified in the schema. + if ((sysSelCodeInt >= selMin) && (sysSelCodeInt <= selMax)) { + isOtaSpNum = true; break; + } } } + } catch (NumberFormatException ex) { + // If the carrier ota sp number schema is not correct, we still allow dial + // and only log the error: + Log.e(LOG_TAG, "checkOtaSpNumBasedOnSysSelCode, error", ex); } - return isOtaspDialString; + return isOtaSpNum; + } + + // Define the pattern/format for carrier specified OTASP number schema. + // It separates by comma and/or whitespace. + private static Pattern pOtaSpNumSchema = Pattern.compile("[,\\s]+"); + + /** + * The following function checks if a dial string is a carrier specified + * OTASP number or not by checking against the OTASP number schema stored + * in PROPERTY_OTASP_NUM_SCHEMA. + * + * Currently, there are 2 schemas for carriers to specify the OTASP number: + * 1) Use system selection code: + * The schema is: + * SELC,the # of code pairs,min1,max1,min2,max2,... + * e.g "SELC,3,10,20,30,40,60,70" indicates that there are 3 pairs of + * selection codes, and they are {10,20}, {30,40} and {60,70} respectively. + * + * 2) Use feature code: + * The schema is: + * "FC,length of feature code,feature code". + * e.g "FC,2,*2" indicates that the length of the feature code is 2, + * and the code itself is "*2". + */ + private boolean isCarrierOtaSpNum(String dialStr) { + boolean isOtaSpNum = false; + int sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr); + if (sysSelCodeInt == INVALID_SYSTEM_SELECTION_CODE) { + return isOtaSpNum; + } + // mCarrierOtaSpNumSchema is retrieved from PROPERTY_OTASP_NUM_SCHEMA: + if (!TextUtils.isEmpty(mCarrierOtaSpNumSchema)) { + Matcher m = pOtaSpNumSchema.matcher(mCarrierOtaSpNumSchema); + if (DBG) { + Log.d(LOG_TAG, "isCarrierOtaSpNum,schema" + mCarrierOtaSpNumSchema); + } + + if (m.find()) { + String sch[] = pOtaSpNumSchema.split(mCarrierOtaSpNumSchema); + // If carrier uses system selection code mechanism + if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("SELC")) { + if (sysSelCodeInt!=INVALID_SYSTEM_SELECTION_CODE) { + isOtaSpNum=checkOtaSpNumBasedOnSysSelCode(sysSelCodeInt,sch); + } else { + if (DBG) { + Log.d(LOG_TAG, "isCarrierOtaSpNum,sysSelCodeInt is invalid"); + } + } + } else if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("FC")) { + int fcLen = Integer.parseInt((String)sch[1]); + String fc = (String)sch[2]; + if (dialStr.regionMatches(0,fc,0,fcLen)) { + isOtaSpNum = true; + } else { + if (DBG) Log.d(LOG_TAG, "isCarrierOtaSpNum,not otasp number"); + } + } else { + if (DBG) { + Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema not supported" + sch[0]); + } + } + } else { + if (DBG) { + Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern not right" + + mCarrierOtaSpNumSchema); + } + } + } else { + if (DBG) Log.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern empty"); + } + return isOtaSpNum; } /** @@ -1195,12 +1340,13 @@ public class CDMAPhone extends PhoneBase { @Override public boolean isOtaSpNumber(String dialStr){ boolean isOtaSpNum = false; - if(dialStr != null){ - isOtaSpNum=isIs683OtaSpDialStr(dialStr); + if (dialStr != null) { + isOtaSpNum = isIs683OtaSpDialStr(dialStr); if(isOtaSpNum == false){ - //TO DO:Add carrier specific OTASP number detection here. + isOtaSpNum = isCarrierOtaSpNum(dialStr); } } + if (DBG) Log.d(LOG_TAG, "isOtaSpNumber " + isOtaSpNum); return isOtaSpNum; } @@ -1244,4 +1390,66 @@ public class CDMAPhone extends PhoneBase { editor.commit(); } + /** + * Sets PROPERTY_ICC_OPERATOR_ISO_COUNTRY property + * + */ + private void setIsoCountryProperty(String operatorNumeric) { + if (TextUtils.isEmpty(operatorNumeric)) { + setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + } else { + String iso = ""; + try { + iso = MccTable.countryCodeForMcc(Integer.parseInt( + operatorNumeric.substring(0,3))); + } catch (NumberFormatException ex) { + Log.w(LOG_TAG, "countryCodeForMcc error" + ex); + } catch (StringIndexOutOfBoundsException ex) { + Log.w(LOG_TAG, "countryCodeForMcc error" + ex); + } + + setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, iso); + } + } + + /** + * Sets the "current" field in the telephony provider according to the build-time + * operator numeric property + * + * @return true for success; false otherwise. + */ + // TODO(Moto): move this method into PhoneBase, since it looks identical to + // the one in GsmPhone + private boolean updateCurrentCarrierInProvider(String operatorNumeric) { + if (!TextUtils.isEmpty(operatorNumeric)) { + try { + Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current"); + ContentValues map = new ContentValues(); + map.put(Telephony.Carriers.NUMERIC, operatorNumeric); + getContext().getContentResolver().insert(uri, map); + return true; + } catch (SQLException e) { + Log.e(LOG_TAG, "Can't store current operator", e); + } + } + return false; + } + + /** + * Updates MCC and MNC device configuration information for application retrieving + * correct version of resources + * + */ + private void updateMccMncConfiguration(String operatorNumeric) { + if (operatorNumeric.length() >= 5) { + Configuration config = new Configuration(); + config.mcc = Integer.parseInt(operatorNumeric.substring(0,3)); + config.mnc = Integer.parseInt(operatorNumeric.substring(3)); + try { + ActivityManagerNative.getDefault().updateConfiguration(config); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Can't update configuration", e); + } + } + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java index ed2ea90d3f23..cc456c5f5b19 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java @@ -222,7 +222,7 @@ public final class CdmaCallTracker extends CallTracker { } updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); return pendingMO; } @@ -262,6 +262,7 @@ public final class CdmaCallTracker extends CallTracker { // triggered by updateParent. cwConn.updateParent(ringingCall, foregroundCall); cwConn.onConnectedInOrOut(); + updatePhoneState(); switchWaitingOrHoldingAndActive(); } else { throw new CallStateException("phone not ringing"); @@ -305,7 +306,7 @@ public final class CdmaCallTracker extends CallTracker { internalClearDisconnected(); updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); } boolean @@ -531,17 +532,6 @@ public final class CdmaCallTracker extends CallTracker { // Dropped connections are removed from the CallTracker // list but kept in the Call list connections[i] = null; - } else if (conn != null && dc != null && !conn.compareTo(dc)) { - // Connection in CLCC response does not match what - // we were tracking. Assume dropped call and new call - - droppedDuringPoll.add(conn); - connections[i] = new CdmaConnection (phone.getContext(), dc, this, i); - - if (connections[i].getCall() == ringingCall) { - newRinging = connections[i]; - } // else something strange happened - hasNonHangupStateChanged = true; } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */ boolean changed; changed = conn.update(dc); @@ -644,7 +634,7 @@ public final class CdmaCallTracker extends CallTracker { } if (hasNonHangupStateChanged || newRinging != null) { - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); } //dumpState(); @@ -678,7 +668,8 @@ public final class CdmaCallTracker extends CallTracker { // the hangup reason is user ignoring or timing out. So conn.onDisconnect() // is not called here. Instead, conn.onLocalDisconnect() is called. conn.onLocalDisconnect(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); + updatePhoneState(); return; } else { try { @@ -821,7 +812,7 @@ public final class CdmaCallTracker extends CallTracker { // the status of the call is after a call waiting is answered, // 3 way call merged or a switch between calls. foregroundCall.setGeneric(true); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); } private Phone.SuppService getFailedService(int what) { @@ -865,6 +856,7 @@ public final class CdmaCallTracker extends CallTracker { // Create a new CdmaConnection which attaches itself to ringingCall. ringingCall.setGeneric(false); new CdmaConnection(phone.getContext(), cw, this, ringingCall); + updatePhoneState(); // Finally notify application notifyCallWaitingInfo(cw); @@ -926,7 +918,7 @@ public final class CdmaCallTracker extends CallTracker { updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); droppedDuringPoll.clear(); break; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java index 92492b4ef893..e78570988f59 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java @@ -19,11 +19,8 @@ package com.android.internal.telephony.cdma; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; -import android.content.ContentValues; import android.content.Intent; import android.database.ContentObserver; -import android.database.SQLException; -import android.net.Uri; import android.os.AsyncResult; import android.os.Handler; import android.os.Message; @@ -35,7 +32,6 @@ import android.os.SystemProperties; import android.provider.Checkin; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; -import android.provider.Telephony; import android.provider.Telephony.Intents; import android.telephony.ServiceState; import android.telephony.SignalStrength; @@ -64,6 +60,7 @@ import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERAT import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISROAMING; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_NUMERIC; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA; +import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC; import java.util.Arrays; import java.util.Date; @@ -401,8 +398,15 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { String cdmaSubscription[] = (String[])ar.result; if (cdmaSubscription != null && cdmaSubscription.length >= 5) { mMdn = cdmaSubscription[0]; - mHomeSystemId = Integer.parseInt(cdmaSubscription[1], 16); - mHomeNetworkId = Integer.parseInt(cdmaSubscription[2], 16); + // TODO: Only grabbing the first SID/NID for now. + if (cdmaSubscription[1] != null) { + String[] sid = cdmaSubscription[1].split(","); + mHomeSystemId = sid.length > 0 ? Integer.parseInt(sid[0]) : 0; + } + if (cdmaSubscription[2] != null) { + String[] nid = cdmaSubscription[2].split(","); + mHomeNetworkId = nid.length > 0 ? Integer.parseInt(nid[0]) : 0; + } mMin = cdmaSubscription[3]; mPrlVersion = cdmaSubscription[4]; Log.d(LOG_TAG,"GET_CDMA_SUBSCRIPTION MDN=" + mMdn); @@ -679,27 +683,6 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } else { newSS.setOperatorName(opNames[0], opNames[1], opNames[2]); } - - if (!(opNames[2].equals(currentCarrier))) { - // TODO(Moto): jsh asks, "This uses the MCC+MNC of the current registered - // network to set the "current" entry in the APN table. But the correct - // entry should be the MCC+MNC that matches the subscribed operator - // (eg, phone issuer). These can be different when roaming." - try { - // Set the current field of the telephony provider according to - // the CDMA's operator - Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current"); - ContentValues map = new ContentValues(); - map.put(Telephony.Carriers.NUMERIC, opNames[2]); - cr.insert(uri, map); - // save current carrier for the next time check - currentCarrier = opNames[2]; - } catch (SQLException e) { - Log.e(LOG_TAG, "Can't store current operator", e); - } - } else { - Log.i(LOG_TAG, "current carrier is not changed"); - } } else { Log.w(LOG_TAG, "error parsing opNames"); } @@ -1495,4 +1478,19 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { return mPrlVersion; } + /** + * Returns IMSI as MCC + MNC + MIN + */ + /*package*/ String getImsi() { + // TODO(Moto): When RUIM is enabled, IMSI will come from RUIM + // not build-time props. Moto will provide implementation + // for RUIM-ready case later. + String operatorNumeric = SystemProperties.get(PROPERTY_ICC_OPERATOR_NUMERIC, ""); + + if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) { + return (operatorNumeric + getCdmaMin()); + } else { + return null; + } + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java index 7e50cc54d6cf..7c743141e07a 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java @@ -51,8 +51,6 @@ public final class RuimRecords extends IccRecords { private String mImsi; private String mMyMobileNumber; - private String mSid; - private String mNid; private String mMin2Min1; private String mPrlVersion; @@ -116,21 +114,12 @@ public final class RuimRecords extends IccRecords { adnCache.reset(); - phone.setSystemProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, null); - phone.setSystemProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, null); - // recordsRequested is set to false indicating that the SIM // read requests made so far are not valid. This is set to // true only when fresh set of read requests are made. recordsRequested = false; } - /** Returns null if RUIM is not yet ready */ - public String getIMSI_M() { - // TODO(Moto): mImsi is not initialized, fix. - return mImsi; - } - public String getMdnNumber() { return mMyMobileNumber; } @@ -223,8 +212,6 @@ public final class RuimRecords extends IccRecords { } mMyMobileNumber = localTemp[0]; - mSid = localTemp[1]; - mNid = localTemp[2]; mMin2Min1 = localTemp[3]; mPrlVersion = localTemp[4]; diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index d1e4b4f21cab..ebbf096e14dc 100755 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -51,6 +51,7 @@ import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDI import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION; +import com.android.internal.telephony.Call; import com.android.internal.telephony.CallForwardInfo; import com.android.internal.telephony.CallStateException; import com.android.internal.telephony.CommandsInterface; @@ -378,20 +379,19 @@ public class GSMPhone extends PhoneBase { } /** - * Notify any interested party of a Phone state change. + * Notify any interested party of a Phone state change {@link Phone.State} */ /*package*/ void notifyPhoneStateChanged() { mNotifier.notifyPhoneState(this); } /** - * Notifies registrants (ie, activities in the Phone app) about - * changes to call state (including Phone and Connection changes). + * Notify registrants of a change in the call state. This notifies changes in {@link Call.State} + * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged. */ - /*package*/ void - notifyCallStateChanged() { + /*package*/ void notifyPreciseCallStateChanged() { /* we'd love it if this was package-scoped*/ - super.notifyCallStateChangedP(); + super.notifyPreciseCallStateChangedP(); } /*package*/ void diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java index 5c5090ffe911..f3b7596f6bd3 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java @@ -212,7 +212,7 @@ public final class GsmCallTracker extends CallTracker { } updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); return pendingMO; } @@ -279,7 +279,7 @@ public final class GsmCallTracker extends CallTracker { internalClearDisconnected(); updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); } boolean @@ -600,7 +600,7 @@ public final class GsmCallTracker extends CallTracker { } if (hasNonHangupStateChanged || newRinging != null) { - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); } //dumpState(); @@ -883,7 +883,7 @@ public final class GsmCallTracker extends CallTracker { updatePhoneState(); - phone.notifyCallStateChanged(); + phone.notifyPreciseCallStateChanged(); droppedDuringPoll.clear(); break; diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index 55d4d64bf547..845f54749641 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -48,6 +48,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_GSERVICES" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" /> diff --git a/tests/AndroidTests/res/raw/v21_simple_1.vcf b/tests/AndroidTests/res/raw/v21_simple_1.vcf new file mode 100644 index 000000000000..6aabb4c023e0 --- /dev/null +++ b/tests/AndroidTests/res/raw/v21_simple_1.vcf @@ -0,0 +1,3 @@ +BEGIN:VCARD
+N:Ando;Roid;
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_simple_2.vcf b/tests/AndroidTests/res/raw/v21_simple_2.vcf new file mode 100644 index 000000000000..f0d5ab506ae6 --- /dev/null +++ b/tests/AndroidTests/res/raw/v21_simple_2.vcf @@ -0,0 +1,3 @@ +BEGIN:VCARD
+FN:Ando Roid
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_simple.vcf b/tests/AndroidTests/res/raw/v21_simple_3.vcf index beddabb96086..beddabb96086 100644 --- a/tests/AndroidTests/res/raw/v21_simple.vcf +++ b/tests/AndroidTests/res/raw/v21_simple_3.vcf diff --git a/tests/AndroidTests/src/com/android/unit_tests/HtmlTest.java b/tests/AndroidTests/src/com/android/unit_tests/HtmlTest.java index 27da4f1c72ae..027730fa3f43 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/HtmlTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/HtmlTest.java @@ -16,11 +16,25 @@ package com.android.unit_tests; +import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Typeface; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; -import android.text.*; -import android.text.style.*; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.QuoteSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.SubscriptSpan; +import android.text.style.SuperscriptSpan; +import android.text.style.TextAppearanceSpan; +import android.text.style.TypefaceSpan; +import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; import junit.framework.TestCase; @@ -35,14 +49,54 @@ public class HtmlTest extends TestCase { s = Html.fromHtml("<font color=\"#00FF00\">something</font>"); colors = s.getSpans(0, s.length(), ForegroundColorSpan.class); - assertEquals(colors[0].getForegroundColor(), 0xFF00FF00); + assertEquals(1, colors.length); + assertEquals(0xFF00FF00, colors[0].getForegroundColor()); s = Html.fromHtml("<font color=\"navy\">something</font>"); colors = s.getSpans(0, s.length(), ForegroundColorSpan.class); - assertEquals(colors[0].getForegroundColor(), 0xFF000080); + assertEquals(1, colors.length); + assertEquals(0xFF000080, colors[0].getForegroundColor()); s = Html.fromHtml("<font color=\"gibberish\">something</font>"); colors = s.getSpans(0, s.length(), ForegroundColorSpan.class); + assertEquals(0, colors.length); + } + + @MediumTest + public void testResourceColor() throws Exception { + ColorStateList c = + Resources.getSystem().getColorStateList(android.R.color.primary_text_dark); + Spanned s; + TextAppearanceSpan[] colors; + + s = Html.fromHtml("<font color=\"@android:color/primary_text_dark\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); + assertEquals(1, colors.length); + assertEquals(c.toString(), colors[0].getTextColor().toString()); + + s = Html.fromHtml("<font color=\"@android:primary_text_dark\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); + assertEquals(1, colors.length); + assertEquals(c.toString(), colors[0].getTextColor().toString()); + + s = Html.fromHtml("<font color=\"@color/primary_text_dark\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); + assertEquals(1, colors.length); + assertEquals(c.toString(), colors[0].getTextColor().toString()); + + s = Html.fromHtml("<font color=\"@primary_text_dark\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); + assertEquals(1, colors.length); + assertEquals(c.toString(), colors[0].getTextColor().toString()); + + s = Html.fromHtml("<font color=\"@" + android.R.color.primary_text_dark + + "\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); + assertEquals(1, colors.length); + assertEquals(c.toString(), colors[0].getTextColor().toString()); + + s = Html.fromHtml("<font color=\"gibberish\">something</font>"); + colors = s.getSpans(0, s.length(), TextAppearanceSpan.class); assertEquals(colors.length, 0); } diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java index ecc8dfe90599..4e5f7a947e95 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/SearchablesTest.java @@ -93,8 +93,8 @@ public class SearchablesTest extends AndroidTestCase { Context appContext = si.getActivityContext(mContext); assertNotNull(appContext); MoreAsserts.assertNotEqual(appContext, mContext); - assertEquals("Android Search", appContext.getString(si.getHintId())); - assertEquals("Google", appContext.getString(si.getLabelId())); + assertEquals("Quick Search Box", appContext.getString(si.getHintId())); + assertEquals("Quick Search Box", appContext.getString(si.getLabelId())); } /** diff --git a/tests/AndroidTests/src/com/android/unit_tests/VpnTest.java b/tests/AndroidTests/src/com/android/unit_tests/VpnTest.java new file mode 100755 index 000000000000..7dc13141e0c6 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/VpnTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.unit_tests; + +import android.net.vpn.L2tpIpsecProfile; +import android.net.vpn.VpnType; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * Unit test class to test VPN api + * Use the below command to run the vpn unit test only + * runtest vpntest or + * adb shell am instrument -e class 'com.android.unit_tests.VpnTest' + * -w com.android.unit_tests/android.test.InstrumentationTestRunner + */ +public class VpnTest extends AndroidTestCase { + + @Override + public void setUp() { + } + + @Override + public void tearDown() { + } + + @SmallTest + public void testGetType() { + L2tpIpsecProfile li = new L2tpIpsecProfile(); + assertTrue(VpnType.L2TP_IPSEC== li.getType()); + } +} diff --git a/tests/AndroidTests/src/com/android/unit_tests/activity/ServiceTest.java b/tests/AndroidTests/src/com/android/unit_tests/activity/ServiceTest.java index db523dc4d506..95f6e362f065 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/activity/ServiceTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/activity/ServiceTest.java @@ -27,10 +27,14 @@ import android.os.IBinder; import android.os.Parcel; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; import android.util.Log; // These test binders purport to support an interface whose canonical // interface name is ServiceTest.SERVICE_LOCAL +// Temporarily suppress, this test is causing unit test suite run to fail +// TODO: remove this suppress +@Suppress public class ServiceTest extends ActivityTestsBase { public static final String SERVICE_LOCAL = @@ -131,7 +135,7 @@ public class ServiceTest extends ActivityTestsBase { mSetReporter = setReporter; mMonitor = !setReporter; } - + void setMonitor(boolean v) { mMonitor = v; } @@ -148,7 +152,7 @@ public class ServiceTest extends ActivityTestsBase { } data.recycle(); } - + if (mMonitor) { mCount++; if (mStartState == STATE_START_1) { @@ -260,7 +264,7 @@ public class ServiceTest extends ActivityTestsBase { waitForResultOrThrow(5 * 1000, "existing connection to lose service"); getContext().unbindService(conn); - + conn = new TestConnection(true, true); success = false; try { @@ -290,7 +294,7 @@ public class ServiceTest extends ActivityTestsBase { waitForResultOrThrow(5 * 1000, "existing connection to lose service"); getContext().unbindService(conn); - + conn = new TestConnection(true, true); success = false; try { @@ -318,12 +322,12 @@ public class ServiceTest extends ActivityTestsBase { mStartState = STATE_UNBIND_ONLY; getContext().unbindService(conn); waitForResultOrThrow(5 * 1000, "existing connection to unbind service"); - + // Expect to see the service rebound. mStartState = STATE_REBIND; getContext().bindService(service, conn, 0); waitForResultOrThrow(5 * 1000, "existing connection to rebind service"); - + // Expect to see the service unbind and then destroyed. mStartState = STATE_UNBIND; getContext().stopService(service); diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java index e6639d3720cd..a065d7016af0 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/content/ConfigTest.java @@ -133,7 +133,7 @@ public class ConfigTest extends AndroidTestCase { case DENSITY: // this is the ratio from the standard - mMetrics.density = (((float)value)/((float)DisplayMetrics.DEFAULT_DENSITY)); + mMetrics.density = (((float)value)/((float)DisplayMetrics.DENSITY_DEFAULT)); break; default: assert(false); diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java new file mode 100644 index 000000000000..0ee74dfc16f0 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.unit_tests.vcard; + +import android.content.ContentValues; + +import org.apache.commons.codec.binary.Base64; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +/** + * @hide old class just for test + */ +public class PropertyNode { + public String propName; + public String propValue; + public List<String> propValue_vector; + + /** Store value as byte[],after decode. + * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc. + */ + public byte[] propValue_bytes; + + /** param store: key=paramType, value=paramValue + * Note that currently PropertyNode class does not support multiple param-values + * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as + * one String value like "A,B", not ["A", "B"]... + * TODO: fix this. + */ + public ContentValues paramMap; + + /** Only for TYPE=??? param store. */ + public Set<String> paramMap_TYPE; + + /** Store group values. Used only in VCard. */ + public Set<String> propGroupSet; + + public PropertyNode() { + propName = ""; + propValue = ""; + propValue_vector = new ArrayList<String>(); + paramMap = new ContentValues(); + paramMap_TYPE = new HashSet<String>(); + propGroupSet = new HashSet<String>(); + } + + public PropertyNode( + String propName, String propValue, List<String> propValue_vector, + byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE, + Set<String> propGroupSet) { + if (propName != null) { + this.propName = propName; + } else { + this.propName = ""; + } + if (propValue != null) { + this.propValue = propValue; + } else { + this.propValue = ""; + } + if (propValue_vector != null) { + this.propValue_vector = propValue_vector; + } else { + this.propValue_vector = new ArrayList<String>(); + } + this.propValue_bytes = propValue_bytes; + if (paramMap != null) { + this.paramMap = paramMap; + } else { + this.paramMap = new ContentValues(); + } + if (paramMap_TYPE != null) { + this.paramMap_TYPE = paramMap_TYPE; + } else { + this.paramMap_TYPE = new HashSet<String>(); + } + if (propGroupSet != null) { + this.propGroupSet = propGroupSet; + } else { + this.propGroupSet = new HashSet<String>(); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PropertyNode)) { + return false; + } + + PropertyNode node = (PropertyNode)obj; + + if (propName == null || !propName.equals(node.propName)) { + return false; + } else if (!paramMap.equals(node.paramMap)) { + return false; + } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) { + return false; + } else if (!propGroupSet.equals(node.propGroupSet)) { + return false; + } + + if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) { + return true; + } else { + // Log.d("@@@", propValue + ", " + node.propValue); + if (!propValue.equals(node.propValue)) { + return false; + } + + // The value in propValue_vector is not decoded even if it should be + // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector + // is 1, the encoded value is stored in propValue, so we do not have to + // check it. + return (propValue_vector.equals(node.propValue_vector) || + propValue_vector.size() == 1 || + node.propValue_vector.size() == 1); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("propName: "); + builder.append(propName); + builder.append(", paramMap: "); + builder.append(paramMap.toString()); + builder.append(", propmMap_TYPE: "); + builder.append(paramMap_TYPE.toString()); + builder.append(", propGroupSet: "); + builder.append(propGroupSet.toString()); + if (propValue_vector != null && propValue_vector.size() > 1) { + builder.append(", propValue_vector size: "); + builder.append(propValue_vector.size()); + } + if (propValue_bytes != null) { + builder.append(", propValue_bytes size: "); + builder.append(propValue_bytes.length); + } + builder.append(", propValue: "); + builder.append(propValue); + return builder.toString(); + } + + /** + * Encode this object into a string which can be decoded. + */ + public String encode() { + // PropertyNode#toString() is for reading, not for parsing in the future. + // We construct appropriate String here. + StringBuilder builder = new StringBuilder(); + if (propName.length() > 0) { + builder.append("propName:["); + builder.append(propName); + builder.append("],"); + } + int size = propGroupSet.size(); + if (size > 0) { + Set<String> set = propGroupSet; + builder.append("propGroup:["); + int i = 0; + for (String group : set) { + // We do not need to double quote groups. + // group = 1*(ALPHA / DIGIT / "-") + builder.append(group); + if (i < size - 1) { + builder.append(","); + } + i++; + } + builder.append("],"); + } + + if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) { + ContentValues values = paramMap; + builder.append("paramMap:["); + size = paramMap.size(); + int i = 0; + for (Entry<String, Object> entry : values.valueSet()) { + // Assuming param-key does not contain NON-ASCII nor symbols. + // + // According to vCard 3.0: + // param-name = iana-token / x-name + builder.append(entry.getKey()); + + // param-value may contain any value including NON-ASCIIs. + // We use the following replacing rule. + // \ -> \\ + // , -> \, + // In String#replaceAll(), "\\\\" means a single backslash. + builder.append("="); + builder.append(entry.getValue().toString() + .replaceAll("\\\\", "\\\\\\\\") + .replaceAll(",", "\\\\,")); + if (i < size -1) { + builder.append(","); + } + i++; + } + + Set<String> set = paramMap_TYPE; + size = paramMap_TYPE.size(); + if (i > 0 && size > 0) { + builder.append(","); + } + i = 0; + for (String type : set) { + builder.append("TYPE="); + builder.append(type + .replaceAll("\\\\", "\\\\\\\\") + .replaceAll(",", "\\\\,")); + if (i < size - 1) { + builder.append(","); + } + i++; + } + builder.append("],"); + } + + size = propValue_vector.size(); + if (size > 0) { + builder.append("propValue:["); + List<String> list = propValue_vector; + for (int i = 0; i < size; i++) { + builder.append(list.get(i) + .replaceAll("\\\\", "\\\\\\\\") + .replaceAll(",", "\\\\,")); + if (i < size -1) { + builder.append(","); + } + } + builder.append("],"); + } + + return builder.toString(); + } + + public static PropertyNode decode(String encodedString) { + PropertyNode propertyNode = new PropertyNode(); + String trimed = encodedString.trim(); + if (trimed.length() == 0) { + return propertyNode; + } + String[] elems = trimed.split("],"); + + for (String elem : elems) { + int index = elem.indexOf('['); + String name = elem.substring(0, index - 1); + Pattern pattern = Pattern.compile("(?<!\\\\),"); + String[] values = pattern.split(elem.substring(index + 1), -1); + if (name.equals("propName")) { + propertyNode.propName = values[0]; + } else if (name.equals("propGroupSet")) { + for (String value : values) { + propertyNode.propGroupSet.add(value); + } + } else if (name.equals("paramMap")) { + ContentValues paramMap = propertyNode.paramMap; + Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE; + for (String value : values) { + String[] tmp = value.split("=", 2); + String mapKey = tmp[0]; + // \, -> , + // \\ -> \ + // In String#replaceAll(), "\\\\" means a single backslash. + String mapValue = + tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\"); + if (mapKey.equalsIgnoreCase("TYPE")) { + paramMap_TYPE.add(mapValue); + } else { + paramMap.put(mapKey, mapValue); + } + } + } else if (name.equals("propValue")) { + StringBuilder builder = new StringBuilder(); + List<String> list = propertyNode.propValue_vector; + int length = values.length; + for (int i = 0; i < length; i++) { + String normValue = values[i] + .replaceAll("\\\\,", ",") + .replaceAll("\\\\\\\\", "\\\\"); + list.add(normValue); + builder.append(normValue); + if (i < length - 1) { + builder.append(";"); + } + } + propertyNode.propValue = builder.toString(); + } + } + + // At this time, QUOTED-PRINTABLE is already decoded to Java String. + // We just need to decode BASE64 String to binary. + String encoding = propertyNode.paramMap.getAsString("ENCODING"); + if (encoding != null && + (encoding.equalsIgnoreCase("BASE64") || + encoding.equalsIgnoreCase("B"))) { + propertyNode.propValue_bytes = + Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes()); + } + + return propertyNode; + } +} diff --git a/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java index b7f562ddc947..af5562ad4ba4 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/VCardTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java @@ -14,36 +14,132 @@ * limitations under the License. */ -package com.android.unit_tests; +package com.android.unit_tests.vcard; import android.content.ContentValues; -import android.syncml.pim.PropertyNode; -import android.syncml.pim.VDataBuilder; -import android.syncml.pim.VNode; -import android.syncml.pim.vcard.VCardException; -import android.syncml.pim.vcard.VCardParser_V21; -import android.syncml.pim.vcard.VCardParser_V30; +import android.pim.vcard.ContactStruct; +import android.pim.vcard.EntryHandler; +import android.pim.vcard.VCardBuilder; +import android.pim.vcard.VCardBuilderCollection; +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardDataBuilder; +import android.pim.vcard.VCardParser; +import android.pim.vcard.VCardParser_V21; +import android.pim.vcard.VCardParser_V30; +import android.pim.vcard.exception.VCardException; import android.test.AndroidTestCase; +import com.android.unit_tests.R; + import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.Vector; +import java.util.List; +import java.util.Map; public class VCardTests extends AndroidTestCase { + // TODO: Use EntityIterator, which is added in Eclair. + private static class EntryHolder implements EntryHandler { + public List<ContactStruct> contacts = new ArrayList<ContactStruct>(); + public void onEntryCreated(ContactStruct contactStruct) { + contacts.add(contactStruct); + } + public void onFinal() { + } + } + + static void verify(ContactStruct expected, ContactStruct actual) { + if (!equalsString(expected.getName(), actual.getName())) { + fail(String.format("Names do not equal: \"%s\" != \"%s\"", + expected.getName(), actual.getName())); + } + if (!equalsString( + expected.getPhoneticName(), actual.getPhoneticName())) { + fail(String.format("Phonetic names do not equal: \"%s\" != \"%s\"", + expected.getPhoneticName(), actual.getPhoneticName())); + } + { + final byte[] expectedPhotoBytes = expected.getPhotoBytes(); + final byte[] actualPhotoBytes = actual.getPhotoBytes(); + if (!((expectedPhotoBytes == null && actualPhotoBytes == null) || + Arrays.equals(expectedPhotoBytes, actualPhotoBytes))) { + fail("photoBytes is not equal."); + } + } + verifyInternal(expected.getNotes(), actual.getNotes(), "notes"); + verifyInternal(expected.getPhoneList(), actual.getPhoneList(), "phones"); + verifyInternal(expected.getContactMethodList(), actual.getContactMethodList(), + "contact lists"); + verifyInternal(expected.getOrganizationList(), actual.getOrganizationList(), + "organizations"); + { + final Map<String, List<String>> expectedMap = + expected.getExtensionMap(); + final Map<String, List<String>> actualMap = + actual.getExtensionMap(); + if (verifySize((expectedMap == null ? 0 : expectedMap.size()), + (actualMap == null ? 0 : actualMap.size()), "extensions") > 0) { + for (String key : expectedMap.keySet()) { + if (!actualMap.containsKey(key)) { + fail(String.format( + "Actual does not have %s extension while expected has", + key)); + } + final List<String> expectedList = expectedMap.get(key); + final List<String> actualList = actualMap.get(key); + verifyInternal(expectedList, actualList, + String.format("extension \"%s\"", key)); + } + } + } + } + + private static boolean equalsString(String a, String b) { + if (a == null || a.length() == 0) { + return b == null || b.length() == 0; + } else { + return a.equals(b); + } + } + + private static int verifySize(int expectedSize, int actualSize, String name) { + if (expectedSize != actualSize) { + fail(String.format("Size of %s is different: %d != %d", + name, expectedSize, actualSize)); + } + return expectedSize; + } + + private static <T> void verifyInternal(final List<T> expected, final List<T> actual, + String name) { + if(verifySize((expected == null ? 0 : expected.size()), + (actual == null ? 0 : actual.size()), name) > 0) { + int size = expected.size(); + for (int i = 0; i < size; i++) { + final T expectedObj = expected.get(i); + final T actualObj = actual.get(i); + if (!expected.equals(actual)) { + fail(String.format("The %i %s are different: %s != %s", + i, name, expectedObj, actualObj)); + } + } + } + } + private class PropertyNodesVerifier { - private HashMap<String, Vector<PropertyNode>> mPropertyNodeMap; + private HashMap<String, ArrayList<PropertyNode>> mPropertyNodeMap; public PropertyNodesVerifier(PropertyNode... nodes) { - mPropertyNodeMap = new HashMap<String, Vector<PropertyNode>>(); + mPropertyNodeMap = new HashMap<String, ArrayList<PropertyNode>>(); for (PropertyNode propertyNode : nodes) { String propName = propertyNode.propName; - Vector<PropertyNode> expectedNodes = + ArrayList<PropertyNode> expectedNodes = mPropertyNodeMap.get(propName); if (expectedNodes == null) { - expectedNodes = new Vector<PropertyNode>(); + expectedNodes = new ArrayList<PropertyNode>(); mPropertyNodeMap.put(propName, expectedNodes); } expectedNodes.add(propertyNode); @@ -53,7 +149,7 @@ public class VCardTests extends AndroidTestCase { public void verify(VNode vnode) { for (PropertyNode propertyNode : vnode.propList) { String propName = propertyNode.propName; - Vector<PropertyNode> nodes = mPropertyNodeMap.get(propName); + ArrayList<PropertyNode> nodes = mPropertyNodeMap.get(propName); if (nodes == null) { fail("Unexpected propName \"" + propName + "\" exists."); } @@ -81,8 +177,8 @@ public class VCardTests extends AndroidTestCase { } } if (mPropertyNodeMap.size() != 0) { - Vector<String> expectedProps = new Vector<String>(); - for (Vector<PropertyNode> nodes : mPropertyNodeMap.values()) { + ArrayList<String> expectedProps = new ArrayList<String>(); + for (ArrayList<PropertyNode> nodes : mPropertyNodeMap.values()) { for (PropertyNode node : nodes) { expectedProps.add(node.propName); } @@ -93,25 +189,82 @@ public class VCardTests extends AndroidTestCase { } } - public void testV21SimpleCase() throws IOException, VCardException { - VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); - InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple); + public void testV21SimpleCase1_1() throws IOException, VCardException { + VCardParser parser = new VCardParser_V21(); + VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH); + EntryHolder holder = new EntryHolder(); + builder.addEntryHandler(holder); + InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); - assertEquals(1, builder.vNodeList.size()); + assertEquals(1, holder.contacts.size()); + verify(new ContactStruct("Roid Ando", null, + null, null, null, null, null, null), + holder.contacts.get(0)); + } + + public void testV21SimpleCase1_2() throws IOException, VCardException { + VCardParser parser = new VCardParser_V21(); + VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_JAPANESE); + EntryHolder holder = new EntryHolder(); + builder.addEntryHandler(holder); + InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1); + assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); + is.close(); + assertEquals(1, holder.contacts.size()); + verify(new ContactStruct("Ando Roid", null, + null, null, null, null, null, null), + holder.contacts.get(0)); + } + + public void testV21SimpleCase2() throws IOException, VCardException { + VCardParser parser = new VCardParser_V21(); + VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH); + EntryHolder holder = new EntryHolder(); + builder.addEntryHandler(holder); + InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_2); + assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); + is.close(); + assertEquals(1, holder.contacts.size()); + verify(new ContactStruct("Ando Roid", null, + null, null, null, null, null, null), + holder.contacts.get(0)); + } + + public void testV21SimpleCase3() throws IOException, VCardException { + VCardParser parser = new VCardParser_V21(); + VCardDataBuilder builder1 = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH); + EntryHolder holder = new EntryHolder(); + builder1.addEntryHandler(holder); + VNodeBuilder builder2 = new VNodeBuilder(); + VCardBuilderCollection collection = + new VCardBuilderCollection( + new ArrayList<VCardBuilder>(Arrays.asList(builder1, builder2))); + InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_3); + assertEquals(true, parser.parse(is,"ISO-8859-1", collection)); + is.close(); + + assertEquals(1, builder2.vNodeList.size()); + VNode vnode = builder2.vNodeList.get(0); PropertyNodesVerifier verifier = new PropertyNodesVerifier( new PropertyNode("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""), null, null, null, null), new PropertyNode("FN", "Ando Roid", null, null, null, null, null)); - verifier.verify(builder.vNodeList.get(0)); + verifier.verify(vnode); + + // FN is prefered. + assertEquals(1, holder.contacts.size()); + ContactStruct actual = holder.contacts.get(0); + verify(new ContactStruct("Ando Roid", null, + null, null, null, null, null, null), + actual); } - + public void testV21BackslashCase() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v21_backslash); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); @@ -129,7 +282,7 @@ public class VCardTests extends AndroidTestCase { public void testV21ComplicatedCase() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v21_complicated); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); @@ -585,7 +738,7 @@ public class VCardTests extends AndroidTestCase { public void testV21Japanese1() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_1); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); @@ -616,7 +769,7 @@ public class VCardTests extends AndroidTestCase { public void testV21Japanese2() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_2); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); @@ -660,7 +813,7 @@ public class VCardTests extends AndroidTestCase { public void testV21MultipleEntryCase() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V21(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v21_multiple_entry); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); @@ -741,7 +894,7 @@ public class VCardTests extends AndroidTestCase { public void testV30SimpleCase() throws IOException, VCardException { VCardParser_V21 parser = new VCardParser_V30(); - VDataBuilder builder = new VDataBuilder(); + VNodeBuilder builder = new VNodeBuilder(); InputStream is = getContext().getResources().openRawResource(R.raw.v30_simple); assertEquals(true, parser.parse(is,"ISO-8859-1", builder)); is.close(); diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java new file mode 100644 index 000000000000..3eb827b60710 --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.unit_tests.vcard; + +import java.util.ArrayList; + +/** + * @hide old class. Just for testing + */ +public class VNode { + public String VName; + + public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>(); + + /** 0:parse over. 1:parsing. */ + public int parseStatus = 1; +} diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java new file mode 100644 index 000000000000..6d692237489a --- /dev/null +++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.unit_tests.vcard; + +import android.content.ContentValues; +import android.pim.vcard.VCardBuilder; +import android.pim.vcard.VCardConfig; +import android.util.CharsetUtils; +import android.util.Log; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.net.QuotedPrintableCodec; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Store the parse result to custom datastruct: VNode, PropertyNode + * Maybe several vcard instance, so use vNodeList to store. + * VNode: standy by a vcard instance. + * PropertyNode: standy by a property line of a card. + * @hide old class, just for testing use + */ +public class VNodeBuilder implements VCardBuilder { + static private String LOG_TAG = "VDATABuilder"; + + /** + * If there's no other information available, this class uses this charset for encoding + * byte arrays. + */ + static public String TARGET_CHARSET = "UTF-8"; + + /** type=VNode */ + public List<VNode> vNodeList = new ArrayList<VNode>(); + private int mNodeListPos = 0; + private VNode mCurrentVNode; + private PropertyNode mCurrentPropNode; + private String mCurrentParamType; + + /** + * The charset using which VParser parses the text. + */ + private String mSourceCharset; + + /** + * The charset with which byte array is encoded to String. + */ + private String mTargetCharset; + + private boolean mStrictLineBreakParsing; + + public VNodeBuilder() { + this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false); + } + + public VNodeBuilder(String charset, boolean strictLineBreakParsing) { + this(null, charset, strictLineBreakParsing); + } + + /** + * @hide sourceCharset is temporal. + */ + public VNodeBuilder(String sourceCharset, String targetCharset, + boolean strictLineBreakParsing) { + if (sourceCharset != null) { + mSourceCharset = sourceCharset; + } else { + mSourceCharset = VCardConfig.DEFAULT_CHARSET; + } + if (targetCharset != null) { + mTargetCharset = targetCharset; + } else { + mTargetCharset = TARGET_CHARSET; + } + mStrictLineBreakParsing = strictLineBreakParsing; + } + + public void start() { + } + + public void end() { + } + + // Note: I guess that this code assumes the Record may nest like this: + // START:VPOS + // ... + // START:VPOS2 + // ... + // END:VPOS2 + // ... + // END:VPOS + // + // However the following code has a bug. + // When error occurs after calling startRecord(), the entry which is probably + // the cause of the error remains to be in vNodeList, while endRecord() is not called. + // + // I leave this code as is since I'm not familiar with vcalendar specification. + // But I believe we should refactor this code in the future. + // Until this, the last entry has to be removed when some error occurs. + public void startRecord(String type) { + + VNode vnode = new VNode(); + vnode.parseStatus = 1; + vnode.VName = type; + // I feel this should be done in endRecord(), but it cannot be done because of + // the reason above. + vNodeList.add(vnode); + mNodeListPos = vNodeList.size() - 1; + mCurrentVNode = vNodeList.get(mNodeListPos); + } + + public void endRecord() { + VNode endNode = vNodeList.get(mNodeListPos); + endNode.parseStatus = 0; + while(mNodeListPos > 0){ + mNodeListPos--; + if((vNodeList.get(mNodeListPos)).parseStatus == 1) + break; + } + mCurrentVNode = vNodeList.get(mNodeListPos); + } + + public void startProperty() { + mCurrentPropNode = new PropertyNode(); + } + + public void endProperty() { + mCurrentVNode.propList.add(mCurrentPropNode); + } + + public void propertyName(String name) { + mCurrentPropNode.propName = name; + } + + // Used only in VCard. + public void propertyGroup(String group) { + mCurrentPropNode.propGroupSet.add(group); + } + + public void propertyParamType(String type) { + mCurrentParamType = type; + } + + public void propertyParamValue(String value) { + if (mCurrentParamType == null || + mCurrentParamType.equalsIgnoreCase("TYPE")) { + mCurrentPropNode.paramMap_TYPE.add(value); + } else { + mCurrentPropNode.paramMap.put(mCurrentParamType, value); + } + + mCurrentParamType = null; + } + + private String encodeString(String originalString, String targetCharset) { + if (mSourceCharset.equalsIgnoreCase(targetCharset)) { + return originalString; + } + Charset charset = Charset.forName(mSourceCharset); + ByteBuffer byteBuffer = charset.encode(originalString); + // byteBuffer.array() "may" return byte array which is larger than + // byteBuffer.remaining(). Here, we keep on the safe side. + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return null; + } + } + + private String handleOneValue(String value, String targetCharset, String encoding) { + if (encoding != null) { + if (encoding.equals("BASE64") || encoding.equals("B")) { + // Assume BASE64 is used only when the number of values is 1. + mCurrentPropNode.propValue_bytes = + Base64.decodeBase64(value.getBytes()); + return value; + } else if (encoding.equals("QUOTED-PRINTABLE")) { + String quotedPrintable = value + .replaceAll("= ", " ").replaceAll("=\t", "\t"); + String[] lines; + if (mStrictLineBreakParsing) { + lines = quotedPrintable.split("\r\n"); + } else { + StringBuilder builder = new StringBuilder(); + int length = quotedPrintable.length(); + ArrayList<String> list = new ArrayList<String>(); + for (int i = 0; i < length; i++) { + char ch = quotedPrintable.charAt(i); + if (ch == '\n') { + list.add(builder.toString()); + builder = new StringBuilder(); + } else if (ch == '\r') { + list.add(builder.toString()); + builder = new StringBuilder(); + if (i < length - 1) { + char nextCh = quotedPrintable.charAt(i + 1); + if (nextCh == '\n') { + i++; + } + } + } else { + builder.append(ch); + } + } + String finalLine = builder.toString(); + if (finalLine.length() > 0) { + list.add(finalLine); + } + lines = list.toArray(new String[0]); + } + StringBuilder builder = new StringBuilder(); + for (String line : lines) { + if (line.endsWith("=")) { + line = line.substring(0, line.length() - 1); + } + builder.append(line); + } + byte[] bytes; + try { + bytes = builder.toString().getBytes(mSourceCharset); + } catch (UnsupportedEncodingException e1) { + Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset); + bytes = builder.toString().getBytes(); + } + + try { + bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes); + } catch (DecoderException e) { + Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e); + return ""; + } + + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return new String(bytes); + } + } + // Unknown encoding. Fall back to default. + } + return encodeString(value, targetCharset); + } + + public void propertyValues(List<String> values) { + if (values == null || values.size() == 0) { + mCurrentPropNode.propValue_bytes = null; + mCurrentPropNode.propValue_vector.clear(); + mCurrentPropNode.propValue_vector.add(""); + mCurrentPropNode.propValue = ""; + return; + } + + ContentValues paramMap = mCurrentPropNode.paramMap; + + String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET")); + String encoding = paramMap.getAsString("ENCODING"); + + if (targetCharset == null || targetCharset.length() == 0) { + targetCharset = mTargetCharset; + } + + for (String value : values) { + mCurrentPropNode.propValue_vector.add( + handleOneValue(value, targetCharset, encoding)); + } + + mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector); + } + + private String listToString(List<String> list){ + int size = list.size(); + if (size > 1) { + StringBuilder typeListB = new StringBuilder(); + for (String type : list) { + typeListB.append(type).append(";"); + } + int len = typeListB.length(); + if (len > 0 && typeListB.charAt(len - 1) == ';') { + return typeListB.substring(0, len - 1); + } + return typeListB.toString(); + } else if (size == 1) { + return list.get(0); + } else { + return ""; + } + } + + public String getResult(){ + return null; + } +} diff --git a/tests/CoreTests/android/core/RequestAPITest.java b/tests/CoreTests/android/core/RequestAPITest.java index d89f5ae98255..94eb23e1b5dc 100644 --- a/tests/CoreTests/android/core/RequestAPITest.java +++ b/tests/CoreTests/android/core/RequestAPITest.java @@ -72,7 +72,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { RequestHandle handle = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", headers, null, - null, 0, false); + null, 0); handle.waitUntilComplete(); fail("expected exception not thrown"); @@ -121,7 +121,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { mTestWebServer.setKeepAlive(false); RequestHandle handle = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", headers, null, - null, 0, false); + null, 0); handle.waitUntilComplete(); } @@ -197,7 +197,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { RequestHandle handle = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", null, testEventHandler, - null, 0, false); + null, 0); Log.d(LOGTAG, "testGet - sent request. Waiting"); handle.waitUntilComplete(); @@ -231,11 +231,11 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { RequestHandle handle0 = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", null, testEventHandler, - null, 0, false); + null, 0); handle0.waitUntilComplete(); RequestHandle handle1 = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", null, testEventHandler2, - null, 0, false); + null, 0); handle1.waitUntilComplete(); /* It's not correct to use same listener for multiple @@ -270,7 +270,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { RequestHandle handle = mRequestQueue.queueRequest( "http://localhost:8080/test1", "HEAD", null, testEventHandler, - null, 0, false); + null, 0); Log.d(LOGTAG, "testHead - sent request waiting"); handle.waitUntilComplete(); @@ -297,7 +297,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { RequestHandle handle = mRequestQueue.queueRequest( "http://localhost:8080/test1", "GET", null, testEventHandler, - null, 0, false); + null, 0); Log.d(LOGTAG, "testChunked - sent request waiting"); handle.waitUntilComplete(); @@ -330,7 +330,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { Log.d(LOGTAG, testName + " start - rq = " + mRequestQueue); RequestHandle requestHandle = mRequestQueue.queueRequest( - "http://localhost:8080/test1", "GET", null, testEventHandler, null, 0, false); + "http://localhost:8080/test1", "GET", null, testEventHandler, null, 0); Log.d(LOGTAG, testName + " - sent request waiting"); requestHandle.waitUntilComplete(); @@ -398,10 +398,10 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { leh2.expectHeaders(); RequestHandle handle0 = mRequestQueue.queueRequest( - "http://localhost:8080/test1", "GET", null, testEventHandler, null, 0, false); + "http://localhost:8080/test1", "GET", null, testEventHandler, null, 0); handle0.waitUntilComplete(); RequestHandle handle1 = mRequestQueue.queueRequest( - "http://localhost:8080/test1", "HEAD", null, testEventHandler, null, 0, false); + "http://localhost:8080/test1", "HEAD", null, testEventHandler, null, 0); Log.d(LOGTAG, "testGetAndHead - sent request. Waiting"); handle1.waitUntilComplete(); @@ -432,7 +432,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { Log.d(LOGTAG, "testPost start - rq = " + mRequestQueue); RequestHandle handle = mRequestQueue.queueRequest( - "http://localhost:8080/test1", "POST", null, testEventHandler, null, 0, false); + "http://localhost:8080/test1", "POST", null, testEventHandler, null, 0); Log.d(LOGTAG, "testPost - sent request waiting"); handle.waitUntilComplete(); @@ -470,7 +470,7 @@ public class RequestAPITest extends AndroidTestCase implements HttpConstants { InputStream bodyProvider = new ByteArrayInputStream(mBody.getBytes()); RequestHandle handle = mRequestQueue.queueRequest( - "http://localhost:8080/test1", "POST", null, testEventHandler, bodyProvider, bodyLength, false); + "http://localhost:8080/test1", "POST", null, testEventHandler, bodyProvider, bodyLength); Log.d(LOGTAG, "testPostWithData - sent request waiting"); handle.waitUntilComplete(); diff --git a/tests/CoreTests/com/android/internal/telephony/gsm/GSMPhoneTest.java b/tests/CoreTests/com/android/internal/telephony/gsm/GSMPhoneTest.java index 710741297613..b96743a2fbc3 100644 --- a/tests/CoreTests/com/android/internal/telephony/gsm/GSMPhoneTest.java +++ b/tests/CoreTests/com/android/internal/telephony/gsm/GSMPhoneTest.java @@ -81,7 +81,7 @@ public class GSMPhoneTest extends AndroidTestCase implements PerformanceTestCase mRadioControl = mGSMTestHandler.getSimulatedCommands(); mHandler = mGSMTestHandler.getHandler(); - mGSMPhone.registerForPhoneStateChanged(mHandler, EVENT_PHONE_STATE_CHANGED, null); + mGSMPhone.registerForPreciseCallStateChanged(mHandler, EVENT_PHONE_STATE_CHANGED, null); mGSMPhone.registerForNewRingingConnection(mHandler, EVENT_RINGING, null); mGSMPhone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null); @@ -109,7 +109,7 @@ public class GSMPhoneTest extends AndroidTestCase implements PerformanceTestCase protected void tearDown() throws Exception { mRadioControl.shutdown(); - mGSMPhone.unregisterForPhoneStateChanged(mHandler); + mGSMPhone.unregisterForPreciseCallStateChanged(mHandler); mGSMPhone.unregisterForNewRingingConnection(mHandler); mGSMPhone.unregisterForDisconnect(mHandler); mGSMPhone.setOnPostDialCharacter(mHandler, 0, null); diff --git a/tests/DpiTest/AndroidManifest.xml b/tests/DpiTest/AndroidManifest.xml index 64ad7beeda34..68ecc6e89934 100644 --- a/tests/DpiTest/AndroidManifest.xml +++ b/tests/DpiTest/AndroidManifest.xml @@ -19,10 +19,15 @@ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="3" /> <supports-screens android:smallScreens="true" /> <application android:label="DpiTest"> - <activity android:name="DpiTestActivity" android:label="DpiTest"> + <activity android:name="DpiTestActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="DpiTestNoCompatActivity" android:label="DpiTestNoCompat"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> diff --git a/tests/DpiTest/res/drawable-nodpi/logonodpi120.png b/tests/DpiTest/res/drawable-nodpi/logonodpi120.png Binary files differnew file mode 100644 index 000000000000..46bbd5bd3d48 --- /dev/null +++ b/tests/DpiTest/res/drawable-nodpi/logonodpi120.png diff --git a/tests/DpiTest/res/drawable-nodpi/logonodpi160.png b/tests/DpiTest/res/drawable-nodpi/logonodpi160.png Binary files differnew file mode 100644 index 000000000000..c23b2ce83b43 --- /dev/null +++ b/tests/DpiTest/res/drawable-nodpi/logonodpi160.png diff --git a/tests/DpiTest/res/drawable-nodpi/logonodpi240.png b/tests/DpiTest/res/drawable-nodpi/logonodpi240.png Binary files differnew file mode 100644 index 000000000000..4d717a86143d --- /dev/null +++ b/tests/DpiTest/res/drawable-nodpi/logonodpi240.png diff --git a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java index 5a9f3f548c7d..8c69305d5760 100644 --- a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java +++ b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java @@ -17,6 +17,8 @@ package com.google.android.test.dpi; import android.app.Activity; +import android.app.ActivityThread; +import android.app.Application; import android.os.Bundle; import android.graphics.BitmapFactory; import android.graphics.Bitmap; @@ -28,8 +30,42 @@ import android.widget.TextView; import android.widget.ScrollView; import android.view.View; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.CompatibilityInfo; +import android.util.DisplayMetrics; public class DpiTestActivity extends Activity { + public DpiTestActivity() { + super(); + init(false); + } + + public DpiTestActivity(boolean noCompat) { + super(); + init(noCompat); + } + + public void init(boolean noCompat) { + try { + // This is all a dirty hack. Don't think a real application should + // be doing it. + Application app = ActivityThread.currentActivityThread().getApplication(); + ApplicationInfo ai = app.getPackageManager().getApplicationInfo( + "com.google.android.test.dpi", + PackageManager.GET_SUPPORTS_DENSITIES); + if (noCompat) { + ai.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS; + ai.supportsDensities = new int[] { ApplicationInfo.ANY_DENSITY }; + app.getResources().setCompatibilityInfo(new CompatibilityInfo(ai)); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("ouch", e); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -73,6 +109,13 @@ public class DpiTestActivity extends Activity { addLabelToRoot(root, "Autoscaled bitmap"); addChildToRoot(root, layout); + layout = new LinearLayout(this); + addResourceDrawable(layout, R.drawable.logonodpi120); + addResourceDrawable(layout, R.drawable.logonodpi160); + addResourceDrawable(layout, R.drawable.logonodpi240); + addLabelToRoot(root, "No-dpi resource drawable"); + addChildToRoot(root, layout); + setContentView(scrollWrap(root)); } @@ -155,7 +198,10 @@ public class DpiTestActivity extends Activity { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - setMeasuredDimension(mBitmap.getScaledWidth(), mBitmap.getScaledHeight()); + final DisplayMetrics metrics = getResources().getDisplayMetrics(); + setMeasuredDimension( + mBitmap.getScaledWidth(metrics), + mBitmap.getScaledHeight(metrics)); } @Override diff --git a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestNoCompatActivity.java b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestNoCompatActivity.java new file mode 100644 index 000000000000..4d25e083e039 --- /dev/null +++ b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestNoCompatActivity.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.dpi; + +public class DpiTestNoCompatActivity extends DpiTestActivity { + public DpiTestNoCompatActivity() { + super(true); + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java index 39bbf16b73b2..ba461973fbfb 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java @@ -73,6 +73,10 @@ public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShel runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis); activity.clearCache(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } dumpMemoryInfo(); // Kill activity diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java index 34c6e0b7f523..48c1e5dba4f4 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -91,7 +91,7 @@ public class TestShellActivity extends Activity implements LayoutTestController } public void clearCache() { - mWebView.clearCache(true); + mWebView.freeMemory(); } @Override @@ -228,8 +228,8 @@ public class TestShellActivity extends Activity implements LayoutTestController @Override public void onLowMemory() { super.onLowMemory(); - Log.e(LOGTAG, "Low memory, kill self"); - System.exit(1); + Log.e(LOGTAG, "Low memory, clearing caches"); + mWebView.freeMemory(); } // Dump the page diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 67af116d372d..14cad2f0b8b9 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -654,9 +654,15 @@ bool AaptGroupEntry::getDensityName(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { - if (out) out->density = 0; + if (out) out->density = ResTable_config::DENSITY_DEFAULT; return true; } + + if (strcmp(name, "nodpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_NONE; + return true; + } + char* c = (char*)name; while (*c >= '0' && *c <= '9') { c++; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index d0a1c46bf644..fd77d5143d9e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -286,8 +286,8 @@ public final class Bridge implements ILayoutBridge { } return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY, - DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, + screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, + DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, themeName, isProjectTheme, projectResources, frameworkResources, customViewLoader, logger); } @@ -304,8 +304,8 @@ public final class Bridge implements ILayoutBridge { Map<String, Map<String, IResourceValue>> frameworkResources, IProjectCallback customViewLoader, ILayoutLog logger) { return computeLayout(layoutDescription, projectKey, - screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY, - DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, + screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, + DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, themeName, isProjectTheme, projectResources, frameworkResources, customViewLoader, logger); } @@ -340,7 +340,7 @@ public final class Bridge implements ILayoutBridge { try { // setup the display Metrics. DisplayMetrics metrics = new DisplayMetrics(); - metrics.density = density / (float) DisplayMetrics.DEFAULT_DENSITY; + metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT; metrics.scaledDensity = metrics.density; metrics.widthPixels = screenWidth; metrics.heightPixels = screenHeight; diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index a2cdcc1d600d..12abce57eafc 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -242,6 +242,7 @@ public class WifiStateTracker extends NetworkStateTracker { private SettingsObserver mSettingsObserver; private boolean mIsScanModeActive; + private boolean mEnableRssiPolling; // Wi-Fi run states: private static final int RUN_STATE_STARTING = 1; @@ -340,6 +341,7 @@ public class WifiStateTracker extends NetworkStateTracker { private void setSupplicantState(SupplicantState state) { mWifiInfo.setSupplicantState(state); updateNetworkInfo(); + checkPollTimer(); } public SupplicantState getSupplicantState() { @@ -354,6 +356,7 @@ public class WifiStateTracker extends NetworkStateTracker { private void setSupplicantState(String stateName) { mWifiInfo.setSupplicantState(stateName); updateNetworkInfo(); + checkPollTimer(); } /** @@ -550,8 +553,10 @@ public class WifiStateTracker extends NetworkStateTracker { * Set the interval timer for polling connection information * that is not delivered asynchronously. */ - private synchronized void setPollTimer () { - if (!hasMessages(EVENT_POLL_INTERVAL)) { + private synchronized void checkPollTimer() { + if (mEnableRssiPolling && + mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED && + !hasMessages(EVENT_POLL_INTERVAL)) { sendEmptyMessageDelayed(EVENT_POLL_INTERVAL, POLL_STATUS_INTERVAL_MSECS); } } @@ -651,6 +656,13 @@ public class WifiStateTracker extends NetworkStateTracker { setBluetoothScanMode(isBluetoothPlaying); } + public void enableRssiPolling(boolean enable) { + if (mEnableRssiPolling != enable) { + mEnableRssiPolling = enable; + checkPollTimer(); + } + } + @Override public void releaseWakeLock() { if (mReleaseWakeLockCallback != null) { @@ -1031,9 +1043,7 @@ public class WifiStateTracker extends NetworkStateTracker { case EVENT_POLL_INTERVAL: if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { requestPolledInfo(mWifiInfo, true); - if (mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED) { - setPollTimer(); - } + checkPollTimer(); } break; @@ -1170,7 +1180,7 @@ public class WifiStateTracker extends NetworkStateTracker { } private void configureInterface() { - setPollTimer(); + checkPollTimer(); mLastSignalLevel = -1; if (!mUseStaticIp) { if (!mHaveIpAddress && !mObtainingIpAddress) { |