diff options
146 files changed, 7224 insertions, 993 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 76b5f7ff6f98..3e3a8553a203 100644 --- a/api/current.xml +++ b/api/current.xml @@ -35638,6 +35638,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 +52689,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 +52773,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 +52847,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 +52902,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 +52995,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 +53225,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 +53294,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 +53364,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 +54628,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 +54985,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 +66162,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 +66214,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 +66634,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" @@ -136961,6 +137225,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" @@ -139010,6 +139318,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" @@ -139238,6 +139568,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" 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/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/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 e70b57091054..54b65271b051 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; @@ -140,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; @@ -322,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 @@ -357,7 +354,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } show(); } - updateUI(); return true; @@ -493,6 +489,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ private void updateUI() { if (mSearchable != null) { + mDecor.setVisibility(View.VISIBLE); updateSearchAutoComplete(); updateSearchButton(); updateSearchAppIcon(); @@ -702,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; @@ -738,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. @@ -991,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 @@ -1002,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. @@ -1039,6 +1042,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchAutoComplete.setSelection(selPoint); mSearchAutoComplete.setListSelection(0); mSearchAutoComplete.clearListSelection(); + mSearchAutoComplete.ensureImeVisible(); + return true; } @@ -1229,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) { @@ -1239,7 +1244,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (handleSpecialIntent(intent)){ return; } - dismiss(); + Log.d(LOG_TAG, "launching " + intent); getContext().startActivity(intent); } @@ -1563,6 +1568,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..325c207222f4 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1534,7 +1534,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 +1599,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 +1622,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 +1639,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 +1697,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 +1706,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/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/Intent.java b/core/java/android/content/Intent.java index 2d231405e8ea..5be8100d3e24 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -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/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 a9aa1ee6b835..2354519d6ad6 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -87,7 +87,7 @@ 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>() { @@ -1386,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 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/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 b2c597f2da5a..b779d59b533a 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -192,7 +192,6 @@ public final class ContactsContract { 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. @@ -601,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> @@ -624,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 { @@ -700,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; @@ -721,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. */ @@ -774,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. */ @@ -794,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. */ @@ -822,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. */ @@ -882,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. 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 13d980d336a0..b780f419504e 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -959,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)) { @@ -968,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() { @@ -1160,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 70c7d7313bae..d3ef5de8634f 100644 --- a/core/java/android/server/search/SearchDialogWrapper.java +++ b/core/java/android/server/search/SearchDialogWrapper.java @@ -45,8 +45,6 @@ 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_DEFAULT; @@ -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/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/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/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 5a7a233c14fa..e6ccd70ab74c 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -550,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 diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 2be74851aa17..da1443c6db3e 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -118,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); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4d345bbae0b7..c1ba690cf9d2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -4918,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 ae509c503b01..09a547ff5e11 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -210,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(); } } @@ -1072,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> @@ -1270,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/Filter.java b/core/java/android/widget/Filter.java index bdecf6242832..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> @@ -90,6 +106,8 @@ public abstract class Filter { thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } + + final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); @@ -102,7 +120,7 @@ public abstract class Filter { mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); - mThreadHandler.sendMessage(message); + mThreadHandler.sendMessageDelayed(message, delay); } } @@ -289,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/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/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_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/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/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/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 9102b40f5ec9..97d55aa0537a 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -29,7 +29,10 @@ namespace android { +typedef int32_t MetadataType; + class Parcel; +template<typename T> class SortedVector; enum player_type { PV_PLAYER = 1, @@ -112,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 d8a677fb8edb..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); } diff --git a/include/media/stagefright/TimeSource.h b/include/media/stagefright/TimeSource.h index f57d8cfd3c88..443673de4692 100644 --- a/include/media/stagefright/TimeSource.h +++ b/include/media/stagefright/TimeSource.h @@ -18,6 +18,8 @@ #define TIME_SOURCE_H_ +#include <stdint.h> + namespace android { class TimeSource { 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/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/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/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 402bce2b0624..59c9476287f0 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -509,28 +509,29 @@ int EventHub::open_device(const char *deviceName) //fprintf(stderr, "could not get device name for %s, %s\n", deviceName, strerror(errno)); name[0] = '\0'; } - 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'; - } - if(ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) { - //fprintf(stderr, "could not get idstring for %s, %s\n", deviceName, strerror(errno)); - idstr[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* name = *iter; - if (strcmp(name, idstr) == 0) { - LOGD("ignoring event id %s driver %s\n", deviceName, name); + 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'; + } + if(ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) { + //fprintf(stderr, "could not get idstring for %s, %s\n", deviceName, strerror(errno)); + idstr[0] = '\0'; + } + int devid = 0; while (devid < mNumDevicesById) { if (mDevicesById[devid].device == NULL) { 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/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 a345ef88b08d..7618435078fb 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.Set; import java.util.HashMap; +import java.util.Set; +import java.util.TimeZone; /** @@ -95,22 +97,33 @@ 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 - private static final int LAST_SYSTEM = 28; + + // 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); - public static final int STRING_VAL = 1; - public static final int INTEGER_VAL = 2; - public static final int LONG_VAL = 3; - public static final int DOUBLE_VAL = 4; - public static final int TIMED_TEXT_VAL = 5; - private static final int LAST_TYPE = 5; + 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 kMetaHeaderSize = 8; // 8 bytes for the size + the marker + private static final int kMetaHeaderSize = 8; // size + marker private static final int kMetaMarker = 0x4d455441; // 'M' 'E' 'T' 'A' private static final int kRecordHeaderSize = 12; // size + id + type @@ -122,21 +135,28 @@ public class Metadata // 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>(); + 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(); } } @@ -260,8 +280,9 @@ public class Metadata final int pin = parcel.dataPosition(); // to roll back in case of errors. final int size = parcel.readInt(); - if (parcel.dataAvail() < size || size < kMetaHeaderSize) { - Log.e(TAG, "Bad size " + size); + // Magic 4 below is for the int32 'size' just read. + if (parcel.dataAvail() + 4 < size || size < kMetaHeaderSize) { + Log.e(TAG, "Bad size " + size + " avail " + parcel.dataAvail() + " position " + pin); parcel.setDataPosition(pin); return false; } @@ -300,44 +321,64 @@ public class Metadata 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 @@ -360,4 +401,16 @@ public class Metadata } 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/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 02327d88f050..5e62f9d8f8e5 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -99,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: @@ -870,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 @@ -881,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() 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 index ad1afbb05bc3..8597275048e7 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -205,4 +205,9 @@ void StagefrightPlayer::setAudioSink(const sp<AudioSink> &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 index f214872c7c64..f93c1f854999 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.h +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -48,6 +48,9 @@ public: 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; diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h index 80d53a8b95d8..339b10851bc6 100644 --- a/media/libmediaplayerservice/TestPlayerStub.h +++ b/media/libmediaplayerservice/TestPlayerStub.h @@ -94,6 +94,10 @@ class TestPlayerStub : public MediaPlayerInterface { 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 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 index 6e7936cf8dd4..5944d9c2bf04 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -4,29 +4,29 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - CachingDataSource.cpp \ + CachingDataSource.cpp \ DataSource.cpp \ - FileSource.cpp \ - HTTPDataSource.cpp \ - HTTPStream.cpp \ - MP3Extractor.cpp \ - MPEG4Extractor.cpp \ - MPEG4Writer.cpp \ - MediaBuffer.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 \ + MetaData.cpp \ MmapSource.cpp \ QComHardwareRenderer.cpp \ - SampleTable.cpp \ - ShoutcastSource.cpp \ + SampleTable.cpp \ + ShoutcastSource.cpp \ SoftwareRenderer.cpp \ SurfaceRenderer.cpp \ TimeSource.cpp \ TimedEventQueue.cpp \ - Utils.cpp \ + Utils.cpp \ AudioPlayer.cpp \ ESDS.cpp \ OMXClient.cpp \ @@ -34,16 +34,20 @@ LOCAL_SRC_FILES:= \ string.cpp LOCAL_C_INCLUDES:= \ - $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ $(TOP)/external/opencore/android LOCAL_SHARED_LIBRARIES := \ libbinder \ libmedia \ - libutils \ + libutils \ libcutils \ libui +ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) + LOCAL_LDLIBS += -lpthread +endif + LOCAL_CFLAGS += -Wno-multichar LOCAL_PRELINK_MODULE:= false diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index 74f37b1d0c45..6b47a3808a0b 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -73,6 +73,8 @@ static bool get_mp3_frame_size( if (bitrate_index == 0 || bitrate_index == 0x0f) { // Disallow "free" bitrate. + + LOGE("We disallow 'free' bitrate for now."); return false; } @@ -174,7 +176,7 @@ static bool Resync( const size_t kMaxFrameSize = 4096; uint8_t *buffer = new uint8_t[kMaxFrameSize]; - off_t pos = *inout_pos; + off_t pos = *inout_pos - kMaxFrameSize; size_t buffer_offset = kMaxFrameSize; size_t buffer_length = kMaxFrameSize; bool valid = false; @@ -184,7 +186,7 @@ static bool Resync( break; } - pos += buffer_length; + pos += buffer_offset; if (pos >= *inout_pos + 128 * 1024) { // Don't scan forever. @@ -217,42 +219,45 @@ static bool Resync( size_t frame_size; int sample_rate, num_channels, bitrate; - if (get_mp3_frame_size(header, &frame_size, + if (!get_mp3_frame_size(header, &frame_size, &sample_rate, &num_channels, &bitrate)) { - LOGV("found possible 1st frame at %ld", pos + buffer_offset); - - // We found what looks like a valid frame, - // now find its successors. + ++buffer_offset; + continue; + } - off_t test_pos = pos + buffer_offset + frame_size; + LOGV("found possible 1st frame at %ld", pos + buffer_offset); - 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); + // We found what looks like a valid frame, + // now find its successors. - LOGV("subsequent header is %08x", test_header); + off_t test_pos = pos + buffer_offset + frame_size; - if ((test_header & kMask) != (header & kMask)) { - valid = false; - break; - } + 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); - size_t test_frame_size; - if (!get_mp3_frame_size(test_header, &test_frame_size)) { - valid = false; - break; - } + LOGV("subsequent header is %08x", test_header); - LOGV("found subsequent frame #%d at %ld", j + 2, test_pos); + if ((test_header & kMask) != (header & kMask)) { + valid = false; + break; + } - test_pos += test_frame_size; + 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) { diff --git a/media/libstagefright/TimeSource.cpp b/media/libstagefright/TimeSource.cpp index 7deb3105f15d..d987fbf1c731 100644 --- a/media/libstagefright/TimeSource.cpp +++ b/media/libstagefright/TimeSource.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <stddef.h> #include <sys/time.h> #include <media/stagefright/TimeSource.h> diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp index 10cf81ab677c..2f8a19f86dd4 100644 --- a/media/libstagefright/TimedEventQueue.cpp +++ b/media/libstagefright/TimedEventQueue.cpp @@ -16,6 +16,7 @@ #undef __STRICT_ANSI__ #define __STDINT_LIMITS +#define __STDC_LIMIT_MACROS #include <stdint.h> #define LOG_TAG "TimedEventQueue" diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index c18f5cea2a29..daaa741ed26c 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -217,6 +217,7 @@ void OMX::threadEntry() { header->nFilledLen = 0; header->nOffset = 0; + header->hMarkTargetComponent = NULL; header->nFlags = 0; NodeMeta *node_meta = static_cast<NodeMeta *>( @@ -238,6 +239,7 @@ void OMX::threadEntry() { 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; 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/unit/MediaPlayerMetadataParserTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java index f51b29e073ea..637ebb861420 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java @@ -21,6 +21,9 @@ 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. @@ -53,15 +56,10 @@ public class MediaPlayerMetadataParserTest extends AndroidTestCase { assertEquals(0, mParcel.dataPosition()); } - // Check parsing of the parcel is successful. Before the - // invocation of the parser a token is inserted. When the parser - // returns, the parcel should be positioned at the token (check it - // does not read too much data). + // Check parsing of the parcel is successful. private void assertParse() throws Exception { - mParcel.writeInt(kToken); mParcel.setDataPosition(0); assertTrue(mMetadata.parse(mParcel)); - assertEquals(kToken, mParcel.readInt()); } // Write the number of bytes from the start of the parcel to the @@ -89,16 +87,6 @@ public class MediaPlayerMetadataParserTest extends AndroidTestCase { mParcel.writeInt(kMarker); } - // 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); - } - // ---------------------------------------------------------------------- // START OF THE TESTS @@ -133,7 +121,9 @@ public class MediaPlayerMetadataParserTest extends AndroidTestCase { assertParse(); } + // ---------------------------------------------------------------------- // RECORDS + // ---------------------------------------------------------------------- // A record header should be at least 12 bytes long @SmallTest @@ -223,4 +213,221 @@ public class MediaPlayerMetadataParserTest extends AndroidTestCase { 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/invoke_mock_media_player.cpp b/media/tests/players/invoke_mock_media_player.cpp index f02298d9ab83..8d575a35b29c 100644 --- a/media/tests/players/invoke_mock_media_player.cpp +++ b/media/tests/players/invoke_mock_media_player.cpp @@ -20,14 +20,17 @@ #include <string.h> -#include "binder/Parcel.h" -#include "media/MediaPlayerInterface.h" -#include "utils/Errors.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; @@ -75,6 +78,9 @@ class Player: public MediaPlayerBase 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); 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 64cdb5b80160..68e3fb727a0e 100644 --- a/packages/TtsService/jni/android_tts_SynthProxy.cpp +++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp @@ -194,6 +194,7 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, if (bufferSize > 0) { prepAudioTrack(pJniData, pForAfter->streamType, rate, format, channel); if (pJniData->mAudioOut) { + pJniData->mAudioOut->start(); pJniData->mAudioOut->write(wav, bufferSize); memset(wav, 0, bufferSize); //LOGV("AudioTrack wrote: %d bytes", bufferSize); @@ -286,6 +287,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; } } @@ -548,7 +550,6 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, if (pSynthData->mAudioOut) { pSynthData->mAudioOut->stop(); - pSynthData->mAudioOut->start(); } afterSynthData_t* pForAfter = new (afterSynthData_t); @@ -607,24 +608,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) { @@ -710,10 +693,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 7c4996e6b2f9..28801f8b50ad 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; @@ -158,6 +160,7 @@ public class TtsService extends Service implements OnCompletionListener { mSpeechQueue = new ArrayList<SpeechItem>(); mPlayer = null; mCurrentSpeechItem = null; + mKillList = new HashMap<SpeechItem, Boolean>(); setDefaultSettings(); } @@ -396,6 +399,7 @@ public class TtsService extends Service implements OnCompletionListener { if ((mCurrentSpeechItem != null) && mCurrentSpeechItem.mCallingApp.equals(callingApp)) { result = nativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); if (mPlayer != null) { try { mPlayer.stop(); @@ -445,6 +449,7 @@ public class TtsService extends Service implements OnCompletionListener { ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || mCurrentSpeechItem.mCallingApp.equals(callingApp))) { result = nativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); if (mPlayer != null) { try { mPlayer.stop(); @@ -546,16 +551,16 @@ public class TtsService extends Service implements OnCompletionListener { 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)){ @@ -574,11 +579,17 @@ 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)); + } + nativeSynth.speak(speechItem.mText, streamType); } - nativeSynth.speak(speechItem.mText, streamType); } catch (InterruptedException e) { Log.e("TTS speakInternalOnly", "tryLock interrupted"); e.printStackTrace(); @@ -616,16 +627,16 @@ public class TtsService extends Service implements OnCompletionListener { 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)){ @@ -637,11 +648,17 @@ 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)); + } + nativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); } - nativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); } catch (InterruptedException e) { Log.e("TTS synthToFileInternalOnly", "tryLock interrupted"); e.printStackTrace(); 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/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/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/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index e41524e4aebd..f04cfd6453a6 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -1898,7 +1898,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo break; case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: animAttr = enter - ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation + ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation; break; } @@ -7281,17 +7281,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--; @@ -7319,6 +7329,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 // ------------------------------------------------------------- @@ -9326,16 +9351,10 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo // width is the screen width {@see AppWindowToken#stepAnimatinoLocked} mWidth = width; } - - @Override - public boolean willChangeTransformationMatrix() { - return true; - } @Override - public boolean willChangeBounds() { - return true; + 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/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index f04d26c30368..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; @@ -42,6 +48,10 @@ 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; @@ -57,6 +67,10 @@ 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; @@ -165,6 +179,23 @@ public class CDMAPhone extends PhoneBase { 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); } @@ -438,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() { @@ -1365,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 a5f9c11a9935..cc456c5f5b19 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java @@ -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"); @@ -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); @@ -679,6 +669,7 @@ public final class CdmaCallTracker extends CallTracker { // is not called here. Instead, conn.onLocalDisconnect() is called. conn.onLocalDisconnect(); phone.notifyPreciseCallStateChanged(); + updatePhoneState(); return; } else { try { @@ -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); diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java index 5183108d8bf4..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; @@ -686,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"); } @@ -1502,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 f3bb5efb033d..7c743141e07a 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java @@ -114,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; } 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/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/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; |