Merge change 7806
* changes:
Fix fountain and more rollo ui work.
diff --git a/api/current.xml b/api/current.xml
index 76b5f7f..bb13212 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"
@@ -65953,6 +65986,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 +66038,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 +66458,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"
diff --git a/cmds/keystore/keystore_get.h b/cmds/keystore/keystore_get.h
index a7fd9a5..7665e81 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 ec7714d..ba6cc32 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1068,6 +1068,23 @@
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 @@
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/IActivityManager.java b/core/java/android/app/IActivityManager.java
index ee1b69b..95b376c 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -262,6 +262,11 @@
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 @@
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/SearchDialog.java b/core/java/android/app/SearchDialog.java
index e70b570..906361c 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -28,7 +28,6 @@
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 @@
// 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 @@
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
@@ -702,7 +699,10 @@
@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 @@
if (DBG_LOG_TIMING) {
dbgLogTiming("onTextChanged()");
}
+ if (mSearchable == null) {
+ return;
+ }
updateWidgetState();
if (!mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, remember it.
@@ -1039,6 +1042,8 @@
mSearchAutoComplete.setSelection(selPoint);
mSearchAutoComplete.setListSelection(0);
mSearchAutoComplete.clearListSelection();
+ mSearchAutoComplete.ensureImeVisible();
+
return true;
}
@@ -1563,6 +1568,9 @@
*/
@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/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 58e66b6..593b7b7 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -23,25 +23,24 @@
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 @@
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 @@
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 @@
mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
mOutsideDrawablesCache = outsideDrawablesCache;
+ mBackgroundsCache = new SparseArray<Drawable.ConstantState>();
mGlobalSearchMode = globalSearchMode;
mStartSpinnerRunnable = new Runnable() {
@@ -119,6 +128,18 @@
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 @@
*/
@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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
// 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 @@
// 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 @@
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 @@
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 26712a1..f1bbede 100755
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -64,11 +64,9 @@
}
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/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 96b93f9..f8316a5 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -52,7 +52,7 @@
}
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/content/Intent.java b/core/java/android/content/Intent.java
index 2d23140..5be8100 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1684,6 +1684,53 @@
@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 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/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index ebe556e..08e3a40 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -38,7 +38,12 @@
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.
@@ -191,7 +196,7 @@
@Override
public String toString() {
return "CompatibilityInfo{scale=" + applicationScale +
- ", compatibility flag=" + mCompatibilityFlags + "}";
+ ", supports screen=" + supportsScreen() + "}";
}
/**
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 091bc17..40d2c86 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -56,7 +56,9 @@
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 @@
mRawImageCallback = null;
mJpegCallback = null;
mPreviewCallback = null;
+ mPostviewCallback = null;
+ mZoomCallback = null;
Looper looper;
if ((looper = Looper.myLooper()) != null) {
@@ -245,13 +249,15 @@
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 @@
}
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 final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg) {
- mShutterCallback = shutter;
- mRawImageCallback = raw;
- mJpegCallback = jpeg;
- native_takePicture();
+ 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();
+ }
+
+ /**
+ * 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/os/Process.java b/core/java/android/os/Process.java
index 4805193..980cff3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -739,7 +739,7 @@
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 0000000..46725d3
--- /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 0000000..e26fac5
--- /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 0000000..4015cb5
--- /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 0000000..e1c4b33
--- /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 0000000..e3985b6
--- /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 0000000..fef9dba
--- /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 0000000..4025f6c
--- /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 0000000..f99b46c
--- /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 0000000..b5e5049
--- /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 0000000..17a138f
--- /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 0000000..634d9f5
--- /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 0000000..7e2be2b
--- /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 0000000..e557219
--- /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 0000000..503c2fb
--- /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 0000000..616aa7763
--- /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 0000000..9fe8b7f
--- /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 0000000..26b8a32
--- /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 0000000..26b8a32
--- /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 b2c597f..b779d59 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -192,7 +192,6 @@
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 @@
}
/**
+ * 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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 1cf7be9..722a7cc 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -104,7 +104,8 @@
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 @@
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/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 38eb4d7..76906b6 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -371,9 +371,7 @@
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) {
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 70e1297..380e5fd 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -25,6 +25,7 @@
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.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 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,53 +636,24 @@
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;
- } else {
- try {
- c = XmlUtils.convertValueToInt(f.mColor, -1);
- } catch (NumberFormatException nfe) {
- // Can't understand the color, so just drop it.
+ 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);
}
- }
-
- if (c != -1) {
- text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
- where, len,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ int c = getHtmlColor(f.mColor);
+ if (c != -1) {
+ text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
+ where, len,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
}
@@ -843,4 +817,47 @@
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/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0e37b26..c41ee32 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -396,7 +396,6 @@
}
/**
-<<<<<<< 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 @@
/**
* 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 13a6e7a..187767b 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,7 +256,7 @@
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);
try {
@@ -291,6 +290,8 @@
public void setWindowType(int type) {
mWindowType = type;
}
+
+ boolean mScaled = false;
private void updateWindow(boolean force) {
if (!mHaveFrame) {
@@ -310,6 +311,9 @@
if (mRequestedWidth <= 0 && mTranslator != null) {
myWidth *= appScale;
myHeight *= appScale;
+ mScaled = true;
+ } else {
+ mScaled = false;
}
getLocationInWindow(mLocation);
@@ -534,6 +538,7 @@
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
+ private int mSaveCount;
public boolean isCreating() {
return mIsCreating;
@@ -628,6 +633,10 @@
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 +659,9 @@
}
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 1e3cdb3..2acf790 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -504,8 +504,12 @@
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 @@
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 e96a15b..ba3bfa7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -986,6 +986,9 @@
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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f9ca8cb..777beed 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -720,7 +720,7 @@
@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 ae509c5..ef15e30 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -210,8 +210,7 @@
if (mDropDownAlwaysVisible
&& mPopup.isShowing()
&& mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED) {
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
- showDropDown();
+ ensureImeVisible();
}
}
@@ -1072,11 +1071,21 @@
/**
* 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>
diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java
index bdecf62..d901540 100644
--- a/core/java/android/widget/Filter.java
+++ b/core/java/android/widget/Filter.java
@@ -46,6 +46,8 @@
private Handler mThreadHandler;
private Handler mResultHandler;
+ private Delayer mDelayer;
+
private final Object mLock = new Object();
/**
@@ -56,6 +58,20 @@
}
/**
+ * 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 @@
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 @@
mThreadHandler.removeMessages(FILTER_TOKEN);
mThreadHandler.removeMessages(FINISH_TOKEN);
- mThreadHandler.sendMessage(message);
+ mThreadHandler.sendMessageDelayed(message, delay);
}
}
@@ -289,4 +307,17 @@
*/
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/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 957b825..002d3db 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -224,7 +224,7 @@
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_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 @@
}
}
- 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 137707f..0c84265 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -23,6 +23,7 @@
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 @@
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 @@
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 @@
decoder->setDitherImage(doDither);
NinePatchPeeker peeker;
- JavaPixelAllocator javaAllocator(env);
+ JavaPixelAllocator javaAllocator(env, reportSizeToVM);
SkBitmap* bitmap = new SkBitmap;
Res_png_9patch dummy9Patch;
@@ -699,6 +706,7 @@
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 6eebbdc..6e159a8 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 @@
};
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 @@
}
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 (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;
+ }
}
- 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 @@
///////////////////////////////////////////////////////////////////////////////
-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 e2dc9ac..16925e4 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -59,7 +59,8 @@
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 @@
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 e71e348..44a9e8c 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -212,8 +212,10 @@
// 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 @@
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 cf3ba7f..bc7f3f5 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -75,6 +75,9 @@
int mStreamType;
AudioTrackJniStorage() {
+ mCallbackData.audioTrack_class = 0;
+ mCallbackData.audioTrack_ref = 0;
+ mStreamType = AudioSystem::DEFAULT;
}
~AudioTrackJniStorage() {
@@ -318,6 +321,8 @@
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 @@
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_util_Process.cpp b/core/jni/android_util_Process.cpp
index 770c755..09a0d70 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -368,7 +368,7 @@
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 @@
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 @@
p++;
if (*p == 0) p--;
}
- mem += atoi(num) * 1024;
+ mem += atoll(num) * 1024;
numFound++;
break;
}
@@ -857,7 +857,7 @@
{"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
new file mode 100644
index 0000000..ad5dce1
--- /dev/null
+++ b/core/res/res/drawable/light_header.9.png
Binary files differ
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 7d235ec..8eda12e 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 be836eb..e3fffb7 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/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 2a39987..33f373d 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -130,6 +130,19 @@
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.
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 9102b40..97d55aa 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 @@
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 d8a677f..40ccc14b 100644
--- a/include/media/PVPlayer.h
+++ b/include/media/PVPlayer.h
@@ -53,6 +53,8 @@
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 f57d8cf..443673d 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/keystore/java/android/security/CertTool.java b/keystore/java/android/security/CertTool.java
index 26d22ae..c96cd4f 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 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 @@
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 @@
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 cc36b84..0db28fd 100644
--- a/keystore/jni/cert.c
+++ b/keystore/jni/cert.c
@@ -136,30 +136,126 @@
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 a9807b1..aaa7602 100644
--- a/keystore/jni/cert.h
+++ b/keystore/jni/cert.h
@@ -41,6 +41,13 @@
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 @@
#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 fabf5cd..1ae8dab 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 @@
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 @@
(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 c3889e9..c371a23 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -366,13 +366,8 @@
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 402bce2..59c9476 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -509,6 +509,20 @@
//fprintf(stderr, "could not get device name for %s, %s\n", deviceName, strerror(errno));
name[0] = '\0';
}
+
+ // check to see if the device is on our excluded list
+ List<String8>::iterator iter = mExcludedDevices.begin();
+ List<String8>::iterator end = mExcludedDevices.end();
+ for ( ; iter != end; iter++) {
+ const char* test = *iter;
+ if (strcmp(name, test) == 0) {
+ LOGI("ignoring event id %s driver %s\n", deviceName, test);
+ close(fd);
+ fd = -1;
+ return -1;
+ }
+ }
+
if(ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) {
//fprintf(stderr, "could not get location for %s, %s\n", deviceName, strerror(errno));
location[0] = '\0';
@@ -518,19 +532,6 @@
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);
- close(fd);
- fd = -1;
- return -1;
- }
- }
-
int devid = 0;
while (devid < mNumDevicesById) {
if (mDevicesById[devid].device == NULL) {
diff --git a/libs/utils/ZipUtils.cpp b/libs/utils/ZipUtils.cpp
index 5df94cb..9138878 100644
--- a/libs/utils/ZipUtils.cpp
+++ b/libs/utils/ZipUtils.cpp
@@ -210,7 +210,7 @@
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 @@
return true;
}
-
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index 3f0c234..9e1a72c 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -184,8 +184,6 @@
// 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 @@
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 @@
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 @@
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/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 84c1a92..d5801f7 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -663,7 +663,7 @@
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 a345ef8..8dbe51a 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -20,10 +20,12 @@
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;
/**
@@ -102,15 +104,20 @@
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 +129,28 @@
// 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();
}
}
@@ -300,44 +314,64 @@
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 +394,16 @@
}
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 02327d8..5e62f9d 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -99,6 +99,8 @@
// 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::getMetadata(
bool update_only, bool apply_filter, Parcel *reply)
{
- status_t status;
- reply->writeInt32(-1); // Placeholder for the return code
+ sp<MediaPlayerBase> p = getPlayer();
+ if (p == 0) return UNKNOWN_ERROR;
- SortedVector<MetadataType> updates;
+ status_t status;
+ // Placeholder for the return code, updated by the caller.
+ reply->writeInt32(-1);
+
+ 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 @@
{
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 83d97fe..30b6a2e 100644
--- a/media/libmediaplayerservice/MidiFile.h
+++ b/media/libmediaplayerservice/MidiFile.h
@@ -46,7 +46,13 @@
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/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h
index 80d53a8..339b108 100644
--- a/media/libmediaplayerservice/TestPlayerStub.h
+++ b/media/libmediaplayerservice/TestPlayerStub.h
@@ -94,6 +94,10 @@
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 4024654..040eb36 100644
--- a/media/libmediaplayerservice/VorbisPlayer.h
+++ b/media/libmediaplayerservice/VorbisPlayer.h
@@ -54,6 +54,10 @@
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 6e7936c..5944d9c 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -4,29 +4,29 @@
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 @@
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 74f37b1..6b47a38 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -73,6 +73,8 @@
if (bitrate_index == 0 || bitrate_index == 0x0f) {
// Disallow "free" bitrate.
+
+ LOGE("We disallow 'free' bitrate for now.");
return false;
}
@@ -174,7 +176,7 @@
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 @@
break;
}
- pos += buffer_length;
+ pos += buffer_offset;
if (pos >= *inout_pos + 128 * 1024) {
// Don't scan forever.
@@ -217,42 +219,45 @@
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);
+ ++buffer_offset;
+ continue;
+ }
- // We found what looks like a valid frame,
- // now find its successors.
+ LOGV("found possible 1st frame at %ld", pos + buffer_offset);
- off_t test_pos = pos + buffer_offset + frame_size;
+ // We found what looks like a valid frame,
+ // now find its successors.
- 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);
+ off_t test_pos = pos + buffer_offset + frame_size;
- LOGV("subsequent header is %08x", test_header);
-
- if ((test_header & kMask) != (header & kMask)) {
- valid = false;
- break;
- }
-
- size_t test_frame_size;
- if (!get_mp3_frame_size(test_header, &test_frame_size)) {
- valid = false;
- break;
- }
-
- LOGV("found subsequent frame #%d at %ld", j + 2, test_pos);
-
- test_pos += test_frame_size;
+ valid = true;
+ for (int j = 0; j < 3; ++j) {
+ uint8_t tmp[4];
+ if (source->read_at(test_pos, tmp, 4) < 4) {
+ valid = false;
+ break;
}
+
+ uint32_t test_header = U32_AT(tmp);
+
+ LOGV("subsequent header is %08x", test_header);
+
+ if ((test_header & kMask) != (header & kMask)) {
+ valid = false;
+ break;
+ }
+
+ size_t test_frame_size;
+ if (!get_mp3_frame_size(test_header, &test_frame_size)) {
+ valid = false;
+ break;
+ }
+
+ LOGV("found subsequent frame #%d at %ld", j + 2, test_pos);
+
+ test_pos += test_frame_size;
}
if (valid) {
diff --git a/media/libstagefright/TimeSource.cpp b/media/libstagefright/TimeSource.cpp
index 7deb310..d987fbf 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 10cf81a..2f8a19f 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 c18f5ce..daaa741 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -217,6 +217,7 @@
header->nFilledLen = 0;
header->nOffset = 0;
+ header->hMarkTargetComponent = NULL;
header->nFlags = 0;
NodeMeta *node_meta = static_cast<NodeMeta *>(
@@ -238,6 +239,7 @@
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 6f0cdfb..fe11878 100644
--- a/media/sdutils/sdutil.cpp
+++ b/media/sdutils/sdutil.cpp
@@ -88,7 +88,7 @@
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 @@
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 f51b29e..576dddd 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.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.
@@ -89,16 +92,6 @@
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 +126,9 @@
assertParse();
}
+ // ----------------------------------------------------------------------
// RECORDS
+ // ----------------------------------------------------------------------
// A record header should be at least 12 bytes long
@SmallTest
@@ -223,4 +218,221 @@
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 f02298d..8d575a3 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 @@
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 33b4c97..734e0cd 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 64cdb5b..099c4d1 100644
--- a/packages/TtsService/jni/android_tts_SynthProxy.cpp
+++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp
@@ -286,6 +286,7 @@
{
if (jniData) {
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
+ env->DeleteGlobalRef(pSynthData->tts_ref);
delete pSynthData;
}
}
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java
index 7c4996e..b3b580c 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -130,6 +130,8 @@
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 @@
mSpeechQueue = new ArrayList<SpeechItem>();
mPlayer = null;
mCurrentSpeechItem = null;
+ mKillList = new HashMap<SpeechItem, Boolean>();
setDefaultSettings();
}
@@ -396,6 +399,7 @@
if ((mCurrentSpeechItem != null) &&
mCurrentSpeechItem.mCallingApp.equals(callingApp)) {
result = nativeSynth.stop();
+ mKillList.put(mCurrentSpeechItem, true);
if (mPlayer != null) {
try {
mPlayer.stop();
@@ -445,6 +449,7 @@
((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) ||
mCurrentSpeechItem.mCallingApp.equals(callingApp))) {
result = nativeSynth.stop();
+ mKillList.put(mCurrentSpeechItem, true);
if (mPlayer != null) {
try {
mPlayer.stop();
@@ -578,7 +583,10 @@
setLanguage("", language, country, variant);
}
}
- nativeSynth.speak(speechItem.mText, streamType);
+ // Only do the synthesis if it has not been killed by a subsequent utterance.
+ if (mKillList.get(speechItem) == null){
+ nativeSynth.speak(speechItem.mText, streamType);
+ }
} catch (InterruptedException e) {
Log.e("TTS speakInternalOnly", "tryLock interrupted");
e.printStackTrace();
@@ -641,7 +649,10 @@
setLanguage("", language, country, variant);
}
}
- nativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
+ // Only do the synthesis if it has not been killed by a subsequent utterance.
+ if (mKillList.get(speechItem) == null){
+ nativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
+ }
} catch (InterruptedException e) {
Log.e("TTS synthToFileInternalOnly", "tryLock interrupted");
e.printStackTrace();
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 493bd09..62b839d 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -74,7 +74,7 @@
private static class ConnectivityThread extends Thread {
private Context mContext;
-
+
private ConnectivityThread(Context context) {
super("ConnectivityThread");
mContext = context;
@@ -89,11 +89,11 @@
}
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 @@
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 @@
mMobileDataStateTracker = new MobileDataStateTracker(context, handler);
mNetTrackers[ConnectivityManager.TYPE_MOBILE] = mMobileDataStateTracker;
-
+
mActiveNetwork = null;
mNumDnsEntries = 0;
@@ -148,11 +149,12 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
}
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 @@
}
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 @@
/**
* 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 @@
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.BACKGROUND_DATA, 1) == 1;
}
-
+
/**
* @see ConnectivityManager#setBackgroundDataSetting(boolean)
*/
@@ -348,22 +356,24 @@
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 @@
}
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 @@
* 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 @@
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 @@
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 @@
} 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 @@
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 @@
*/
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 @@
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 @@
}
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 @@
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 @@
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 2e430c8..3e53585 100644
--- a/services/java/com/android/server/MountListener.java
+++ b/services/java/com/android/server/MountListener.java
@@ -202,6 +202,7 @@
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/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index e41524e..f04cfd6 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -1898,7 +1898,7 @@
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 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 @@
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 @@
// 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 25991f2..e1ca201 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3528,8 +3528,6 @@
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 @@
}
}
- 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 @@
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 f04d26c..bdcea92 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 @@
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.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.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 @@
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 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 @@
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 a5f9c11..cc456c5 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 @@
// triggered by updateParent.
cwConn.updateParent(ringingCall, foregroundCall);
cwConn.onConnectedInOrOut();
+ updatePhoneState();
switchWaitingOrHoldingAndActive();
} else {
throw new CallStateException("phone not ringing");
@@ -531,17 +532,6 @@
// 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 @@
// is not called here. Instead, conn.onLocalDisconnect() is called.
conn.onLocalDisconnect();
phone.notifyPreciseCallStateChanged();
+ updatePhoneState();
return;
} else {
try {
@@ -865,6 +856,7 @@
// 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 5183108..e785709 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 @@
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.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_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 @@
} 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 @@
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 f3bb5ef..7c74314 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 @@
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 0000000..6aabb4c
--- /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 0000000..f0d5ab5
--- /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
similarity index 100%
rename from tests/AndroidTests/res/raw/v21_simple.vcf
rename to 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 27da4f1..027730f 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 @@
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 db523dc..95f6e36 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.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 @@
mSetReporter = setReporter;
mMonitor = !setReporter;
}
-
+
void setMonitor(boolean v) {
mMonitor = v;
}
@@ -148,7 +152,7 @@
}
data.recycle();
}
-
+
if (mMonitor) {
mCount++;
if (mStartState == STATE_START_1) {
@@ -260,7 +264,7 @@
waitForResultOrThrow(5 * 1000, "existing connection to lose service");
getContext().unbindService(conn);
-
+
conn = new TestConnection(true, true);
success = false;
try {
@@ -290,7 +294,7 @@
waitForResultOrThrow(5 * 1000, "existing connection to lose service");
getContext().unbindService(conn);
-
+
conn = new TestConnection(true, true);
success = false;
try {
@@ -318,12 +322,12 @@
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/vcard/PropertyNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
new file mode 100644
index 0000000..0ee74df
--- /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
similarity index 85%
rename from tests/AndroidTests/src/com/android/unit_tests/VCardTests.java
rename to tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java
index b7f562d..af5562ad 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 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 @@
}
}
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 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 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 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 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 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 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 0000000..3eb827b
--- /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 0000000..6d69223
--- /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/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 34c6e0b..90f03dd 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -228,8 +228,8 @@
@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