summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accounts/AccountManagerService.java66
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java258
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java139
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java4
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java7
-rw-r--r--core/java/android/content/ISyncAdapter.aidl8
-rw-r--r--core/java/android/content/SyncAdapter.java6
-rw-r--r--core/java/android/content/SyncContext.java10
-rw-r--r--core/java/android/content/SyncManager.java78
-rw-r--r--core/java/android/net/http/Connection.java14
-rw-r--r--core/java/android/net/http/ConnectionThread.java19
-rw-r--r--core/java/android/net/http/HttpConnection.java3
-rw-r--r--core/java/android/net/http/HttpsConnection.java14
-rw-r--r--core/java/android/net/http/RequestHandle.java25
-rw-r--r--core/java/android/net/http/RequestQueue.java71
-rw-r--r--core/java/android/os/DropBoxEntry.aidl19
-rw-r--r--core/java/android/os/DropBoxEntry.java163
-rw-r--r--core/java/android/os/IDropBox.aidl92
-rw-r--r--core/java/android/pim/vcard/Constants.java107
-rw-r--r--core/java/android/pim/vcard/ContactStruct.java776
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java1327
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java311
-rw-r--r--core/java/android/pim/vcard/VCardDataBuilder.java16
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java63
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java59
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java116
-rw-r--r--core/java/android/preference/Preference.java27
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java83
-rw-r--r--core/java/android/provider/Calendar.java6
-rw-r--r--core/java/android/provider/MediaStore.java94
-rw-r--r--core/java/android/provider/Settings.java34
-rw-r--r--core/java/android/server/BluetoothA2dpService.java95
-rw-r--r--core/java/android/server/BluetoothService.java2
-rw-r--r--core/java/android/text/Layout.java15
-rw-r--r--core/java/android/text/StaticLayout.java9
-rw-r--r--core/java/android/text/style/LeadingMarginSpan.java5
-rw-r--r--core/java/android/view/ViewRoot.java5
-rw-r--r--core/java/android/webkit/BrowserFrame.java61
-rw-r--r--core/java/android/webkit/ByteArrayBuilder.java125
-rw-r--r--core/java/android/webkit/CallbackProxy.java53
-rw-r--r--core/java/android/webkit/HttpDateTime.java6
-rw-r--r--core/java/android/webkit/LoadListener.java68
-rw-r--r--core/java/android/webkit/MimeTypeMap.java3
-rw-r--r--core/java/android/webkit/Network.java26
-rw-r--r--core/java/android/webkit/URLUtil.java8
-rw-r--r--core/java/android/webkit/WebChromeClient.java10
-rw-r--r--core/java/android/webkit/WebSettings.java3
-rw-r--r--core/java/android/webkit/WebTextView.java5
-rw-r--r--core/java/android/webkit/WebView.java384
-rw-r--r--core/java/android/webkit/WebViewCore.java49
-rw-r--r--core/java/android/webkit/WebViewDatabase.java24
-rw-r--r--core/java/android/widget/AbsListView.java1
-rw-r--r--core/java/android/widget/CheckedTextView.java10
-rw-r--r--core/java/android/widget/QuickContactBadge.java34
-rw-r--r--core/java/android/widget/TextView.java2
56 files changed, 3158 insertions, 1862 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 3a11cb3..be74073 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -21,6 +21,8 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.PackageInfo;
@@ -90,11 +92,8 @@ public class AccountManagerService
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
- private static final int MESSAGE_CONNECTED = 7;
- private static final int MESSAGE_DISCONNECTED = 8;
private final AccountAuthenticatorCache mAuthenticatorCache;
- private final AuthenticatorBindHelper mBindHelper;
private final DatabaseHelper mOpenHelper;
private final SimWatcher mSimWatcher;
@@ -220,10 +219,12 @@ public class AccountManagerService
mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
mAuthenticatorCache.setListener(this);
- mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
- MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
- mSimWatcher = new SimWatcher(mContext);
+ if (SystemProperties.getBoolean("ro.config.sim_password_clear", false)) {
+ mSimWatcher = new SimWatcher(mContext);
+ } else {
+ mSimWatcher = null;
+ }
sThis.set(this);
onRegisteredServicesCacheChanged();
@@ -1068,7 +1069,7 @@ public class AccountManagerService
}
private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, ServiceConnection {
IAccountManagerResponse mResponse;
final String mAccountType;
final boolean mExpectActivityLaunch;
@@ -1150,7 +1151,7 @@ public class AccountManagerService
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
}
- if (!mBindHelper.bind(mAccountType, this)) {
+ if (!bindToAuthenticator(mAccountType)) {
Log.d(TAG, "bind attempt failed for " + toDebugString());
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
}
@@ -1159,7 +1160,7 @@ public class AccountManagerService
private void unbind() {
if (mAuthenticator != null) {
mAuthenticator = null;
- mBindHelper.unbind(this);
+ mContext.unbindService(this);
}
}
@@ -1172,7 +1173,7 @@ public class AccountManagerService
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
- public void onConnected(IBinder service) {
+ public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
run();
@@ -1182,9 +1183,7 @@ public class AccountManagerService
}
}
- public abstract void run() throws RemoteException;
-
- public void onDisconnected() {
+ public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1193,6 +1192,8 @@ public class AccountManagerService
}
}
+ public abstract void run() throws RemoteException;
+
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1262,6 +1263,39 @@ public class AccountManagerService
}
}
}
+
+ /**
+ * find the component name for the authenticator and initiate a bind
+ * if no authenticator or the bind fails then return false, otherwise return true
+ */
+ private boolean bindToAuthenticator(String authenticatorType) {
+ AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(authenticatorType));
+ if (authenticatorInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "there is no authenticator for " + authenticatorType
+ + ", bailing out");
+ }
+ return false;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+ intent.setComponent(authenticatorInfo.componentName);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+ }
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+ }
+ return false;
+ }
+
+
+ return true;
+ }
}
private class MessageHandler extends Handler {
@@ -1270,9 +1304,6 @@ public class AccountManagerService
}
public void handleMessage(Message msg) {
- if (mBindHelper.handleMessage(msg)) {
- return;
- }
switch (msg.what) {
case MESSAGE_TIMED_OUT:
Session session = (Session)msg.obj;
@@ -1567,13 +1598,14 @@ public class AccountManagerService
private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
final boolean fromAuthenticator = hasAuthenticatorUid(account.type, callerUid);
final boolean hasExplicitGrants = hasExplicitlyGrantedPermission(account, authTokenType);
+ final boolean inSystemImage = inSystemImage(callerUid);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
+ callerUid + ", account " + account
+ ": is authenticator? " + fromAuthenticator
+ ", has explicit permission? " + hasExplicitGrants);
}
- return fromAuthenticator || hasExplicitGrants || inSystemImage(callerUid);
+ return fromAuthenticator || hasExplicitGrants || inSystemImage;
}
private boolean hasAuthenticatorUid(String accountType, int callingUid) {
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
deleted file mode 100644
index 2ca1f0e..0000000
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.accounts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-/**
- * A helper object that simplifies binding to Account Authenticators. It uses the
- * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
- * allowing the user to bind by account name. It also allows multiple, simultaneous binds
- * to the same authenticator, with each bind call guaranteed to return either
- * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
- * itself succeeds, even if the authenticator is already bound internally.
- * @hide
- */
-public class AuthenticatorBindHelper {
- private static final String TAG = "Accounts";
- private final Handler mHandler;
- private final Context mContext;
- private final int mMessageWhatConnected;
- private final int mMessageWhatDisconnected;
- private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
- private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
- private final AccountAuthenticatorCache mAuthenticatorCache;
-
- public AuthenticatorBindHelper(Context context,
- AccountAuthenticatorCache authenticatorCache, Handler handler,
- int messageWhatConnected, int messageWhatDisconnected) {
- mContext = context;
- mHandler = handler;
- mAuthenticatorCache = authenticatorCache;
- mMessageWhatConnected = messageWhatConnected;
- mMessageWhatDisconnected = messageWhatDisconnected;
- }
-
- public interface Callback {
- void onConnected(IBinder service);
- void onDisconnected();
- }
-
- public boolean bind(String authenticatorType, Callback callback) {
- // if the authenticator is connecting or connected then return true
- synchronized (mServiceConnections) {
- if (mServiceConnections.containsKey(authenticatorType)) {
- MyServiceConnection connection = mServiceConnections.get(authenticatorType);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "service connection already exists for " + authenticatorType);
- }
- mServiceUsers.get(authenticatorType).add(callback);
- if (connection.mService != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is connected, scheduling a connected message for "
- + authenticatorType);
- }
- connection.scheduleCallbackConnectedMessage(callback);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is *not* connected, waiting for for "
- + authenticatorType);
- }
- }
- return true;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no service connection for " + authenticatorType);
- }
-
- // otherwise find the component name for the authenticator and initiate a bind
- // if no authenticator or the bind fails then return false, otherwise return true
- AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
- mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType));
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- MyServiceConnection connection = new MyServiceConnection(authenticatorType);
-
- Intent intent = new Intent();
- intent.setAction("android.accounts.AccountAuthenticator");
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
- mServiceConnections.put(authenticatorType, connection);
- mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
- return true;
- }
- }
-
- public void unbind(Callback callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbinding callback " + callbackToUnbind);
- }
- synchronized (mServiceConnections) {
- for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
- final String authenticatorType = entry.getKey();
- final ArrayList<Callback> serviceUsers = entry.getValue();
- for (Callback callback : serviceUsers) {
- if (callback == callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found callback in service" + authenticatorType);
- }
- serviceUsers.remove(callbackToUnbind);
- if (serviceUsers.isEmpty()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there are no more callbacks for service "
- + authenticatorType + ", unbinding service");
- }
- unbindFromServiceLocked(authenticatorType);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "leaving service " + authenticatorType
- + " around since there are still callbacks using it");
- }
- }
- return;
- }
- }
- }
- Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
- }
- }
-
- /**
- * You must synchronized on mServiceConnections before calling this
- */
- private void unbindFromServiceLocked(String authenticatorType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbindService from " + authenticatorType);
- }
- mContext.unbindService(mServiceConnections.get(authenticatorType));
- mServiceUsers.remove(authenticatorType);
- mServiceConnections.remove(authenticatorType);
- }
-
- private class ConnectedMessagePayload {
- public final IBinder mService;
- public final Callback mCallback;
- public ConnectedMessagePayload(IBinder service, Callback callback) {
- mService = service;
- mCallback = callback;
- }
- }
-
- private class MyServiceConnection implements ServiceConnection {
- private final String mAuthenticatorType;
- private IBinder mService = null;
-
- public MyServiceConnection(String authenticatorType) {
- mAuthenticatorType = authenticatorType;
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is connected
- synchronized (mServiceConnections) {
- mService = service;
- for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became connected, scheduling a connected "
- + "message for " + mAuthenticatorType);
- }
- scheduleCallbackConnectedMessage(callback);
- }
- }
- }
-
- private void scheduleCallbackConnectedMessage(Callback callback) {
- final ConnectedMessagePayload payload =
- new ConnectedMessagePayload(mService, callback);
- mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is disconnected,
- // and unbind from the service.
- synchronized (mServiceConnections) {
- final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
- if (callbackList != null) {
- for (Callback callback : callbackList) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became disconnected, scheduling a "
- + "disconnected message for "
- + mAuthenticatorType);
- }
- mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
- }
- unbindFromServiceLocked(mAuthenticatorType);
- }
- }
- }
- }
-
- boolean handleMessage(Message message) {
- if (message.what == mMessageWhatConnected) {
- ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
- }
- payload.mCallback.onConnected(payload.mService);
- return true;
- } else if (message.what == mMessageWhatDisconnected) {
- Callback callback = (Callback)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
- }
- callback.onDisconnected();
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index e3ed2e9..f4b7258 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,14 +18,16 @@ package android.accounts;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
import android.view.View;
import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.Window;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.graphics.drawable.Drawable;
import com.android.internal.R;
/**
@@ -43,62 +45,68 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
private String mAuthTokenType;
private int mUid;
private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
- getWindow().setContentView(R.layout.grant_credentials_permission);
- mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
- mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
- mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
- final String accountTypeLabel =
- getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
- final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
+ setContentView(R.layout.grant_credentials_permission);
- findViewById(R.id.allow).setOnClickListener(this);
- findViewById(R.id.deny).setOnClickListener(this);
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- TextView messageView = (TextView) getWindow().findViewById(R.id.message);
- String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
- if (authTokenLabel.length() == 0) {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- mAccount.name, accountTypeLabel));
- } else {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- authTokenLabel, mAccount.name, accountTypeLabel));
- }
+ final Bundle extras = getIntent().getExtras();
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final String accountTypeLabel = extras.getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+ final String[] packages = extras.getStringArray(EXTRAS_PACKAGES);
+ final String authTokenLabel = extras.getString(EXTRAS_AUTH_TOKEN_LABEL);
+
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list);
- String[] packageLabels = new String[packages.length];
final PackageManager pm = getPackageManager();
- for (int i = 0; i < packages.length; i++) {
+ for (String pkg : packages) {
+ String packageLabel;
try {
- packageLabels[i] =
- pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
} catch (PackageManager.NameNotFoundException e) {
- packageLabels[i] = packages[i];
+ packageLabel = pkg;
}
+ packagesListView.addView(newPackageView(packageLabel));
+ }
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
+ if (TextUtils.isEmpty(authTokenLabel)) {
+ authTokenTypeView.setVisibility(View.GONE);
+ } else {
+ authTokenTypeView.setText(authTokenLabel);
}
- ((ListView) findViewById(R.id.packages_list)).setAdapter(
- new PackagesArrayAdapter(this, packageLabels));
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
}
public void onClick(View v) {
+ final AccountManagerService accountManagerService = AccountManagerService.getSingleton();
switch (v.getId()) {
- case R.id.allow:
- AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.allow_button:
+ accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid);
Intent result = new Intent();
result.putExtra("retry", true);
setResult(RESULT_OK, result);
setAccountAuthenticatorResult(result.getExtras());
break;
- case R.id.deny:
- AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.deny_button:
+ accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid);
setResult(RESULT_CANCELED);
break;
}
@@ -110,63 +118,20 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
}
/**
- * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
*/
public void finish() {
Intent intent = getIntent();
- AccountAuthenticatorResponse accountAuthenticatorResponse =
- intent.getParcelableExtra(EXTRAS_RESPONSE);
- if (accountAuthenticatorResponse != null) {
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE);
+ if (response != null) {
// send the result bundle back if set, otherwise send an error.
if (mResultBundle != null) {
- accountAuthenticatorResponse.onResult(mResultBundle);
+ response.onResult(mResultBundle);
} else {
- accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
}
}
super.finish();
}
-
- private static class PackagesArrayAdapter extends ArrayAdapter<String> {
- protected LayoutInflater mInflater;
- private static final int mResource = R.layout.simple_list_item_1;
-
- public PackagesArrayAdapter(Context context, String[] items) {
- super(context, mResource, items);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- static class ViewHolder {
- TextView label;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unneccessary calls
- // to findViewById() on each row.
- ViewHolder holder;
-
- // When convertView is not null, we can reuse it directly, there is no need
- // to reinflate it. We only inflate a new View when the convertView supplied
- // by ListView is null.
- if (convertView == null) {
- convertView = mInflater.inflate(mResource, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- holder = new ViewHolder();
- holder.label = (TextView) convertView.findViewById(R.id.text1);
-
- convertView.setTag(holder);
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- holder = (ViewHolder) convertView.getTag();
- }
-
- holder.label.setText(getItem(position));
-
- return convertView;
- }
- }
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ff48583..5b34ef9 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -46,7 +46,7 @@ import java.util.UUID;
*/
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
- private static final boolean DBG = false;
+ private static final boolean DBG = true; //STOPSHIP: Remove excess logging
/**
* Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index eba8715..9866862 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -747,4 +747,8 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
public void writeSyncDataBytes(Account account, byte[] data) {
mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
}
+
+ protected ContentProvider getSyncStateProvider() {
+ return mSyncState.asContentProvider();
+ }
}
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index fb6091a..0db6155 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -21,6 +21,7 @@ import android.os.Bundle;
import android.os.Process;
import android.os.NetStat;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.EventLog;
import java.util.concurrent.atomic.AtomicInteger;
@@ -117,6 +118,12 @@ public abstract class AbstractThreadedSyncAdapter {
}
}
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
/**
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
index 4660527..dd9d14e 100644
--- a/core/java/android/content/ISyncAdapter.aidl
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -44,4 +44,12 @@ oneway interface ISyncAdapter {
* @param syncContext the ISyncContext that was passed to {@link #startSync}
*/
void cancelSync(ISyncContext syncContext);
+
+ /**
+ * Initialize the SyncAdapter for this account and authority.
+ *
+ * @param account the account that should be synced
+ * @param authority the authority that should be synced
+ */
+ void initialize(in Account account, String authority);
}
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
index 88dc332..af1634e 100644
--- a/core/java/android/content/SyncAdapter.java
+++ b/core/java/android/content/SyncAdapter.java
@@ -38,6 +38,12 @@ public abstract class SyncAdapter {
public void cancelSync(ISyncContext syncContext) throws RemoteException {
SyncAdapter.this.cancelSync();
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
Transport mTransport = new Transport();
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 587586d..cc914c0 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -56,7 +56,9 @@ public class SyncContext {
if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
try {
mLastHeartbeatSendTime = now;
- mSyncContext.sendHeartbeat();
+ if (mSyncContext != null) {
+ mSyncContext.sendHeartbeat();
+ }
} catch (RemoteException e) {
// this should never happen
}
@@ -64,13 +66,15 @@ public class SyncContext {
public void onFinished(SyncResult result) {
try {
- mSyncContext.onFinished(result);
+ if (mSyncContext != null) {
+ mSyncContext.onFinished(result);
+ }
} catch (RemoteException e) {
// this should never happen
}
}
public IBinder getSyncContextBinder() {
- return mSyncContext.asBinder();
+ return (mSyncContext == null) ? null : mSyncContext.asBinder();
}
}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 9757ef6..8a529e9 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -544,6 +544,46 @@ class SyncManager implements OnAccountsUpdateListener {
return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
}
+ private void initializeSyncAdapter(Account account, String authority) {
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(syncAdapterType);
+ if (syncAdapterInfo == null) {
+ Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(syncAdapterInfo.componentName);
+ mContext.bindService(intent, new InitializerServiceConnection(account, authority),
+ Context.BIND_AUTO_CREATE);
+ }
+
+ private class InitializerServiceConnection implements ServiceConnection {
+ private final Account mAccount;
+ private final String mAuthority;
+
+ public InitializerServiceConnection(Account account, String authority) {
+ mAccount = account;
+ mAuthority = authority;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
+ } catch (RemoteException e) {
+ // doesn't matter, we will retry again later
+ } finally {
+ mContext.unbindService(this);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mContext.unbindService(this);
+ }
+ }
+
/**
* Returns whether or not sync is enabled. Sync can be enabled by
* setting the system property "ro.config.sync" to the value "yes".
@@ -686,36 +726,34 @@ class SyncManager implements OnAccountsUpdateListener {
continue;
}
- // make this an initialization sync if the isSyncable state is unknown
- Bundle extrasCopy = extras;
- long delayCopy = delay;
+ // initialize the SyncAdapter if the isSyncable state is unknown
if (isSyncable < 0) {
- extrasCopy = new Bundle(extras);
- extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- delayCopy = -1; // expedite this
- } else {
- final boolean syncAutomatically = masterSyncAutomatically
- && mSyncStorageEngine.getSyncAutomatically(account, authority);
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
+ initializeSyncAdapter(account, authority);
+ continue;
+ }
+
+ final boolean syncAutomatically = masterSyncAutomatically
+ && mSyncStorageEngine.getSyncAutomatically(account, authority);
+ boolean syncAllowed =
+ manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
}
+ continue;
}
+
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
- + " delay " + delayCopy
+ + " delay " + delay
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
- + ", extras " + extrasCopy);
+ + ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(account, source, authority, extrasCopy, delayCopy));
+ new SyncOperation(account, source, authority, extras, delay));
}
}
}
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2d39e39..b8e17da 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -94,7 +94,6 @@ abstract class Connection {
*/
private static final String HTTP_CONNECTION = "http.connection";
- RequestQueue.ConnectionManager mConnectionManager;
RequestFeeder mRequestFeeder;
/**
@@ -104,11 +103,9 @@ abstract class Connection {
private byte[] mBuf;
protected Connection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
mContext = context;
mHost = host;
- mConnectionManager = connectionManager;
mRequestFeeder = requestFeeder;
mCanPersist = false;
@@ -124,18 +121,15 @@ abstract class Connection {
* necessary
*/
static Connection getConnection(
- Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
- return new HttpConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
- return new HttpsConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpsConnection(context, host, proxy, requestFeeder);
}
/**
@@ -338,7 +332,7 @@ abstract class Connection {
mRequestFeeder.requeueRequest(tReq);
empty = false;
}
- if (empty) empty = mRequestFeeder.haveRequest(mHost);
+ if (empty) empty = !mRequestFeeder.haveRequest(mHost);
}
return empty;
}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 0b30e58..32191d2 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -108,24 +108,11 @@ class ConnectionThread extends Thread {
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
request.mHost + " " + request );
- HttpHost proxy = mConnectionManager.getProxyHost();
-
- HttpHost host;
- if (false) {
- // Allow https proxy
- host = proxy == null ? request.mHost : proxy;
- } else {
- // Disallow https proxy -- tmob proxy server
- // serves a request loop for https reqs
- host = (proxy == null ||
- request.mHost.getSchemeName().equals("https")) ?
- request.mHost : proxy;
- }
- mConnection = mConnectionManager.getConnection(mContext, host);
+ mConnection = mConnectionManager.getConnection(mContext,
+ request.mHost);
mConnection.processRequests(request);
if (mConnection.getCanPersist()) {
- if (!mConnectionManager.recycleConnection(host,
- mConnection)) {
+ if (!mConnectionManager.recycleConnection(mConnection)) {
mConnection.closeConnection();
}
} else {
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 8b12d0b..6df86bf 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -35,9 +35,8 @@ import org.apache.http.params.HttpConnectionParams;
class HttpConnection extends Connection {
HttpConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
}
/**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 8a69d0d..f735f3d 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -131,13 +131,16 @@ public class HttpsConnection extends Connection {
*/
private boolean mAborted = false;
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
/**
* Contructor for a https connection.
*/
- HttpsConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
}
/**
@@ -159,8 +162,7 @@ public class HttpsConnection extends Connection {
AndroidHttpClientConnection openConnection(Request req) throws IOException {
SSLSocket sslSock = null;
- HttpHost proxyHost = mConnectionManager.getProxyHost();
- if (proxyHost != null) {
+ if (mProxyHost != null) {
// If we have a proxy set, we first send a CONNECT request
// to the proxy; if the proxy returns 200 OK, we negotiate
// a secure connection to the target server via the proxy.
@@ -172,7 +174,7 @@ public class HttpsConnection extends Connection {
Socket proxySock = null;
try {
proxySock = new Socket
- (proxyHost.getHostName(), proxyHost.getPort());
+ (mProxyHost.getHostName(), mProxyHost.getPort());
proxySock.setSoTimeout(60 * 1000);
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 190ae7a..77cd544 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -42,15 +42,13 @@ public class RequestHandle {
private WebAddress mUri;
private String mMethod;
private Map<String, String> mHeaders;
-
private RequestQueue mRequestQueue;
-
private Request mRequest;
-
private InputStream mBodyProvider;
private int mBodyLength;
-
private int mRedirectCount = 0;
+ // Used only with synchronous requests.
+ private Connection mConnection;
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
@@ -81,6 +79,19 @@ public class RequestHandle {
}
/**
+ * Creates a new request session with a given Connection. This connection
+ * is used during a synchronous load to handle this request.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request,
+ Connection conn) {
+ this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+ request);
+ mConnection = conn;
+ }
+
+ /**
* Cancels this request
*/
public void cancel() {
@@ -262,6 +273,12 @@ public class RequestHandle {
mRequest.waitUntilComplete();
}
+ public void processRequest() {
+ if (mConnection != null) {
+ mConnection.processRequests(mRequest);
+ }
+ }
+
/**
* @return Digest-scheme authentication response.
*/
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index 875caa0..84b6487 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -171,16 +171,17 @@ public class RequestQueue implements RequestFeeder {
}
public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
Connection con = mIdleCache.getConnection(host);
if (con == null) {
mTotalConnection++;
- con = Connection.getConnection(
- mContext, host, this, RequestQueue.this);
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
}
return con;
}
- public boolean recycleConnection(HttpHost host, Connection connection) {
- return mIdleCache.cacheConnection(host, connection);
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
}
}
@@ -342,6 +343,66 @@ public class RequestQueue implements RequestFeeder {
req);
}
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+ Request req = new Request(method, host, mProxyHost, uri.mPath,
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
/**
* @return true iff there are any non-active requests pending
*/
@@ -478,6 +539,6 @@ public class RequestQueue implements RequestFeeder {
interface ConnectionManager {
HttpHost getProxyHost();
Connection getConnection(Context context, HttpHost host);
- boolean recycleConnection(HttpHost host, Connection connection);
+ boolean recycleConnection(Connection connection);
}
}
diff --git a/core/java/android/os/DropBoxEntry.aidl b/core/java/android/os/DropBoxEntry.aidl
new file mode 100644
index 0000000..225eee1
--- /dev/null
+++ b/core/java/android/os/DropBoxEntry.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.os;
+
+parcelable DropBoxEntry;
diff --git a/core/java/android/os/DropBoxEntry.java b/core/java/android/os/DropBoxEntry.java
new file mode 100644
index 0000000..e3816a8
--- /dev/null
+++ b/core/java/android/os/DropBoxEntry.java
@@ -0,0 +1,163 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * A single entry retrieved from an {@link IDropBox} implementation.
+ * This may include a reference to a stream, so you must call
+ * {@link #close()} when you are done using it.
+ *
+ * {@pending}
+ */
+public class DropBoxEntry implements Parcelable {
+ private final String mTag;
+ private final long mTimeMillis;
+
+ private final String mText;
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mFlags;
+
+ /** Flag value: Entry's content was deleted to save space. */
+ public static final int IS_EMPTY = 1;
+
+ /** Flag value: Content is human-readable UTF-8 text (possibly compressed). */
+ public static final int IS_TEXT = 2;
+
+ /** Flag value: Content can been decompressed with {@link GZIPOutputStream}. */
+ public static final int IS_GZIPPED = 4;
+
+ /** Create a new DropBoxEntry with the specified contents. */
+ public DropBoxEntry(String tag, long timeMillis, String text) {
+ if (tag == null || text == null) throw new NullPointerException();
+ mTag = tag;
+ mTimeMillis = timeMillis;
+ mText = text;
+ mFileDescriptor = null;
+ mFlags = IS_TEXT;
+ }
+
+ /** Create a new DropBoxEntry with the specified contents. */
+ public DropBoxEntry(String tag, long millis, File data, int flags) throws IOException {
+ if (tag == null) throw new NullPointerException();
+ if (((flags & IS_EMPTY) != 0) != (data == null)) throw new IllegalArgumentException();
+
+ mTag = tag;
+ mTimeMillis = millis;
+ mText = null;
+ mFlags = flags;
+ mFileDescriptor = data == null ? null :
+ ParcelFileDescriptor.open(data, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ /** Internal constructor for CREATOR.createFromParcel(). */
+ private DropBoxEntry(String tag, long millis, Object value, int flags) {
+ if (tag == null) throw new NullPointerException();
+ if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
+
+ mTag = tag;
+ mTimeMillis = millis;
+ mFlags = flags;
+
+ if (value == null) {
+ mText = null;
+ mFileDescriptor = null;
+ } else if (value instanceof String) {
+ if ((flags & IS_TEXT) == 0) throw new IllegalArgumentException();
+ mText = (String) value;
+ mFileDescriptor = null;
+ } else if (value instanceof ParcelFileDescriptor) {
+ mText = null;
+ mFileDescriptor = (ParcelFileDescriptor) value;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /** Close the input stream associated with this entry. */
+ public synchronized void close() {
+ try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+ }
+
+ /** @return the tag originally attached to the entry. */
+ public String getTag() { return mTag; }
+
+ /** @return time when the entry was originally created. */
+ public long getTimeMillis() { return mTimeMillis; }
+
+ /** @return flags describing the content returned by @{link #getInputStream()}. */
+ public int getFlags() { return mFlags & ~IS_GZIPPED; } // getInputStream() decompresses.
+
+ /**
+ * @param maxLength of string to return (will truncate at this length).
+ * @return the uncompressed text contents of the entry, null if the entry is not text.
+ */
+ public String getText(int maxLength) {
+ if (mText != null) return mText.substring(0, Math.min(maxLength, mText.length()));
+ if ((mFlags & IS_TEXT) == 0) return null;
+
+ try {
+ InputStream stream = getInputStream();
+ if (stream == null) return null;
+ char[] buf = new char[maxLength];
+ InputStreamReader reader = new InputStreamReader(stream);
+ return new String(buf, 0, Math.max(0, reader.read(buf)));
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /** @return the uncompressed contents of the entry, or null if the contents were lost */
+ public InputStream getInputStream() throws IOException {
+ if (mText != null) return new ByteArrayInputStream(mText.getBytes("UTF8"));
+ if (mFileDescriptor == null) return null;
+ InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+ return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+ }
+
+ public static final Parcelable.Creator<DropBoxEntry> CREATOR = new Parcelable.Creator() {
+ public DropBoxEntry[] newArray(int size) { return new DropBoxEntry[size]; }
+ public DropBoxEntry createFromParcel(Parcel in) {
+ return new DropBoxEntry(
+ in.readString(), in.readLong(), in.readValue(null), in.readInt());
+ }
+ };
+
+ public int describeContents() {
+ return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mTag);
+ out.writeLong(mTimeMillis);
+ if (mFileDescriptor != null) {
+ out.writeValue(mFileDescriptor);
+ } else {
+ out.writeValue(mText);
+ }
+ out.writeInt(mFlags);
+ }
+}
diff --git a/core/java/android/os/IDropBox.aidl b/core/java/android/os/IDropBox.aidl
new file mode 100644
index 0000000..26294b6
--- /dev/null
+++ b/core/java/android/os/IDropBox.aidl
@@ -0,0 +1,92 @@
+/*
+ * 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.os;
+
+import android.os.DropBoxEntry;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.). The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size.
+ *
+ * <p>This interface is implemented by a system service you can access:
+ *
+ * <pre>IDropBox.Stub.asInterface(ServiceManager.getService("dropbox"));</pre>
+ *
+ * <p>Other system services and debugging tools may scan the drop box to upload
+ * entries for processing.
+ *
+ * {@pending}
+ */
+interface IDropBox {
+ /**
+ * Stores human-readable text. The data may be discarded eventually (or even
+ * immediately) if space is limited, or ignored entirely if the tag has been
+ * blocked (see {@link #isTagEnabled}).
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ */
+ void addText(String tag, String data);
+
+ /**
+ * Stores binary data. The data may be ignored or discarded as with
+ * {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ * @param flags describing the data, defined in {@link DropBoxEntry}
+ */
+ void addData(String tag, in byte[] data, int flags);
+
+ /**
+ * Stores data read from a file descriptor. The data may be ignored or
+ * discarded as with {@link #addText}. You must close your
+ * ParcelFileDescriptor object after calling this method!
+ *
+ * @param tag describing the type of entry being stored
+ * @param data file descriptor to read from
+ * @param flags describing the data, defined in {@link DropBoxEntry}
+ */
+ void addFile(String tag, in ParcelFileDescriptor data, int flags);
+
+ /**
+ * Checks any blacklists (set in system settings) to see whether a certain
+ * tag is allowed. Entries with disabled tags will be dropped immediately,
+ * so you can save the work of actually constructing and sending the data.
+ *
+ * @param tag that would be used in {@link #addText} or {@link #addFile}
+ * @return whether events with that tag would be accepted
+ */
+ boolean isTagEnabled(String tag);
+
+ /**
+ * Gets the next entry from the drop box *after* the specified time.
+ * Requires android.permission.READ_LOGS. You must always call
+ * {@link DropBoxEntry#close()} on the return value!
+ *
+ * @param tag of entry to look for, null for all tags
+ * @param millis time of the last entry seen
+ * @return the next entry, or null if there are no more entries
+ */
+ DropBoxEntry getNextEntry(String tag, long millis);
+
+ // TODO: It may be useful to have some sort of notification mechanism
+ // when data is added to the dropbox, for demand-driven readers --
+ // for now readers need to poll the dropbox to find new data.
+}
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
index ca41ce5..a1c7e10 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/Constants.java
@@ -16,16 +16,47 @@
package android.pim.vcard;
/**
- * Constants used in both composer and parser.
+ * Constants used in both exporter and importer code.
*/
/* package */ class Constants {
- public static final String ATTR_TYPE = "TYPE";
-
public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0";
+
+ // The property names valid both in vCard 2.1 and 3.0.
+ public static final String PROPERTY_BEGIN = "BEGIN";
+ public static final String PROPERTY_VERSION = "VERSION";
+ public static final String PROPERTY_N = "N";
+ public static final String PROPERTY_FN = "FN";
+ public static final String PROPERTY_ADR = "ADR";
+ public static final String PROPERTY_EMAIL = "EMAIL";
+ public static final String PROPERTY_NOTE = "NOTE";
+ public static final String PROPERTY_ORG = "ORG";
+ public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
+ public static final String PROPERTY_TEL = "TEL";
+ public static final String PROPERTY_TITLE = "TITLE";
+ public static final String PROPERTY_ROLE = "ROLE";
+ public static final String PROPERTY_PHOTO = "PHOTO";
+ public static final String PROPERTY_LOGO = "LOGO";
+ public static final String PROPERTY_URL = "URL";
+ public static final String PROPERTY_BDAY = "BDAY"; // Birthday
+ public static final String PROPERTY_END = "END";
+
+ // Valid property names not supported (not appropriately handled) by our vCard importer now.
+ public static final String PROPERTY_REV = "REV";
+ public static final String PROPERTY_AGENT = "AGENT";
+
+ // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+ public static final String PROPERTY_NAME = "NAME";
+ public static final String PROPERTY_NICKNAME = "NICKNAME";
+ public static final String PROPERTY_SORT_STRING = "SORT-STRING";
- // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
+ // De-fact property values expressing phonetic names.
+ public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
+ public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
+ public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
+
+ // Properties both ContactsStruct in Eclair and de-fact vCard extensions
// shown in http://en.wikipedia.org/wiki/VCard support are defined here.
public static final String PROPERTY_X_AIM = "X-AIM";
public static final String PROPERTY_X_MSN = "X-MSN";
@@ -34,12 +65,26 @@ package android.pim.vcard;
public static final String PROPERTY_X_JABBER = "X-JABBER";
public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+ // Properties only ContactsStruct has. We alse use this.
+ public static final String PROPERTY_X_QQ = "X-QQ";
+ public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
+
// Phone number for Skype, available as usual phone.
public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
- // Some device emits this "X-" attribute, which is specifically invalid but should be
- // always properly accepted, and emitted in some special case (for that device/application).
- public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
-
+
+ // Properties for DoCoMo vCard.
+ public static final String PROPERTY_X_CLASS = "X-CLASS";
+ public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
+ public static final String PROPERTY_X_NO = "X-NO";
+ public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
+
+ // For some historical reason, we often use the term "ATTR"/"attribute" especially toward
+ // what is called "param" in both vCard specs, while vCard, while vCard importer side uses
+ // "param".
+ //
+ // TODO: Confusing. Fix it.
+ public static final String ATTR_TYPE = "TYPE";
+
// How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
//
// e.g.
@@ -59,12 +104,19 @@ package android.pim.vcard;
public static final String ATTR_TYPE_VOICE = "VOICE";
public static final String ATTR_TYPE_INTERNET = "INTERNET";
+ // Abbreviation of "prefered" according to vCard 2.1 specification.
+ // We interpret this value as "primary" property during import/export.
+ //
+ // Note: Both vCard specs does anything about the requirement about this attribute,
+ // but there may be some vCard importer which will get confused with more than
+ // one "PREF"s in one property name, while Android accepts them.
public static final String ATTR_TYPE_PREF = "PREF";
// Phone types valid in vCard and known to ContactsContract, but not so common.
public static final String ATTR_TYPE_CAR = "CAR";
public static final String ATTR_TYPE_ISDN = "ISDN";
public static final String ATTR_TYPE_PAGER = "PAGER";
+ public static final String ATTR_TYPE_TLX = "TLX"; // Telex
// Phone types existing in vCard 2.1 but not known to ContactsContract.
// TODO: should make parser make these TYPE_CUSTOM.
@@ -73,22 +125,39 @@ package android.pim.vcard;
public static final String ATTR_TYPE_BBS = "BBS";
public static final String ATTR_TYPE_VIDEO = "VIDEO";
- // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
- // These types are encoded to "X-" attributes when composing vCard for now.
- // Parser passes these even if "X-" is added to the attribute.
- public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
- public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
- // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
- public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
- public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
- public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
- public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
- public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
+ // Attribute for Phones, which are not formally valid in vCard (at least 2.1).
+ // These types are basically encoded to "X-" attributes when composing vCard.
+ // Parser passes these when "X-" is added to the attribute or not.
+ public static final String ATTR_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
+ public static final String ATTR_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+ public static final String ATTR_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+ public static final String ATTR_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+ // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
+ public static final String ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+ // vCard composer translates this type to "VOICE" Just for parsing.
+ public static final String ATTR_PHONE_EXTRA_TYPE_OTHER = "OTHER";
+
+ // Attribute for addresses.
+ public static final String ATTR_ADR_TYPE_PARCEL = "PARCEL";
+ public static final String ATTR_ADR_TYPE_DOM = "DOM";
+ public static final String ATTR_ADR_TYPE_INTL = "INTL";
+
+ // Attribute types not officially valid but used in some vCard exporter.
+ // Do not use in composer side.
+ public static final String ATTR_EXTRA_TYPE_COMPANY = "COMPANY";
// DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
// vCard 3.0.
public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
+ public interface ImportOnly {
+ public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
+ // Some device emits this "X-" attribute for expressing Google Talk,
+ // which is specifically invalid but should be always properly accepted, and emitted
+ // in some special case (for that device/application).
+ public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+ }
+
private Constants() {
}
} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 36e5e23..eb9c48a 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -38,6 +38,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
@@ -46,7 +47,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -55,11 +55,11 @@ import java.util.Map;
*/
public class ContactStruct {
private static final String LOG_TAG = "vcard.ContactStruct";
-
+
// Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
// Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-
+
static {
sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
@@ -68,12 +68,10 @@ public class ContactStruct {
sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(Constants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ Im.PROTOCOL_GOOGLE_TALK);
}
- /**
- * @hide only for testing
- */
static public class PhoneData {
public final int type;
public final String data;
@@ -90,7 +88,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof PhoneData) {
+ if (!(obj instanceof PhoneData)) {
return false;
}
PhoneData phoneData = (PhoneData)obj;
@@ -125,7 +123,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof EmailData) {
+ if (!(obj instanceof EmailData)) {
return false;
}
EmailData emailData = (EmailData)obj;
@@ -202,7 +200,7 @@ public class ContactStruct {
@Override
public boolean equals(Object obj) {
- if (obj instanceof PostalData) {
+ if (!(obj instanceof PostalData)) {
return false;
}
PostalData postalData = (PostalData)obj;
@@ -251,87 +249,117 @@ public class ContactStruct {
}
}
- /**
- * @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;
+ // non-final is Intended: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE".
+ public String companyName;
+ public String departmentName;
+ public String titleName;
// 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,
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
boolean isPrimary) {
this.type = type;
this.companyName = companyName;
- this.positionName = positionName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof OrganizationData) {
+ if (!(obj instanceof OrganizationData)) {
return false;
}
OrganizationData organization = (OrganizationData)obj;
- return (type == organization.type && companyName.equals(organization.companyName) &&
- positionName.equals(organization.positionName) &&
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
isPrimary == organization.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
- type, companyName, positionName, isPrimary);
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
}
}
static public class ImData {
+ public final int protocol;
+ public final String customProtocol;
public final int type;
public final String data;
- public final String label;
public final boolean isPrimary;
- // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
- public ImData(int type, String data, String label, boolean isPrimary) {
+ public ImData(int protocol, String customProtocol, int type,
+ String data, boolean isPrimary) {
+ this.protocol = protocol;
+ this.customProtocol = customProtocol;
this.type = type;
this.data = data;
- this.label = label;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof ImData) {
+ if (!(obj instanceof ImData)) {
return false;
}
ImData imData = (ImData)obj;
- return (type == imData.type && data.equals(imData.data) &&
- label.equals(imData.label) && isPrimary == imData.isPrimary);
+ return (type == imData.type && protocol == imData.protocol
+ && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
+ (imData.customProtocol == null))
+ && (data != null ? data.equals(imData.data) : (imData.data == null))
+ && isPrimary == imData.isPrimary);
}
@Override
public String toString() {
- return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
- type, data, label, isPrimary);
+ return String.format(
+ "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+ type, protocol, customProtocol, data, isPrimary);
}
}
- /**
- * @hide only for testing.
- */
static public class PhotoData {
public static final String FORMAT_FLASH = "SWF";
public final int type;
public final String formatName; // used when type is not defined in ContactsContract.
public final byte[] photoBytes;
+ public final boolean isPrimary;
- public PhotoData(int type, String formatName, byte[] photoBytes) {
+ public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
this.type = type;
this.formatName = formatName;
this.photoBytes = photoBytes;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhotoData)) {
+ return false;
+ }
+ PhotoData photoData = (PhotoData)obj;
+ return (type == photoData.type &&
+ (formatName == null ? (photoData.formatName == null) :
+ formatName.equals(photoData.formatName)) &&
+ (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+ (isPrimary == photoData.isPrimary));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+ type, formatName, photoBytes.length, isPrimary);
}
}
@@ -342,10 +370,6 @@ public class ContactStruct {
private List<String> mPropertyValueList = new ArrayList<String>();
private byte[] mPropertyBytes;
- public Property() {
- clear();
- }
-
public void setPropertyName(final String propertyName) {
mPropertyName = propertyName;
}
@@ -385,6 +409,7 @@ public class ContactStruct {
mPropertyName = null;
mParameterMap.clear();
mPropertyValueList.clear();
+ mPropertyBytes = null;
}
}
@@ -421,17 +446,8 @@ public class ContactStruct {
private final int mVCardType;
private final Account mAccount;
- // Each Column of four properties has ISPRIMARY field
- // (See android.provider.Contacts)
- // If false even after the parsing 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() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
}
public ContactStruct(int vcardType) {
@@ -444,186 +460,6 @@ public class ContactStruct {
}
/**
- * @hide only for testing.
- */
- public ContactStruct(String givenName,
- String familyName,
- String middleName,
- String prefix,
- String suffix,
- String phoneticGivenName,
- String pheneticFamilyName,
- String phoneticMiddleName,
- List<byte[]> photoBytesList,
- List<String> notes,
- List<PhoneData> phoneList,
- List<EmailData> emailList,
- List<PostalData> postalList,
- List<OrganizationData> organizationList,
- List<PhotoData> photoList,
- List<String> websiteList) {
- this(VCardConfig.VCARD_TYPE_DEFAULT);
- mGivenName = givenName;
- mFamilyName = familyName;
- mPrefix = prefix;
- mSuffix = suffix;
- mPhoneticGivenName = givenName;
- mPhoneticFamilyName = familyName;
- mPhoneticMiddleName = middleName;
- mEmailList = emailList;
- mPostalList = postalList;
- mOrganizationList = organizationList;
- mPhotoList = photoList;
- mWebsiteList = websiteList;
- }
-
- // All getter methods should be used carefully, since they may change
- // in the future as of 2009-09-24, on which I cannot be sure this structure
- // is completely consolidated.
- // When we are sure we will no longer change them, we'll be happy to
- // make it complete public (withouth @hide tag)
- //
- // Also note that these getter methods should be used only after
- // all properties being pushed into this object. If not, incorrect
- // value will "be stored in the local cache and" be returned to you.
-
- /**
- * @hide
- */
- public String getFamilyName() {
- return mFamilyName;
- }
-
- /**
- * @hide
- */
- public String getGivenName() {
- return mGivenName;
- }
-
- /**
- * @hide
- */
- public String getMiddleName() {
- return mMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * @hide
- */
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * @hide
- */
- public String getFullName() {
- return mFullName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFamilyName() {
- return mPhoneticFamilyName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticGivenName() {
- return mPhoneticGivenName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticMiddleName() {
- return mPhoneticMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFullName() {
- return mPhoneticFullName;
- }
-
- /**
- * @hide
- */
- public final List<String> getNickNameList() {
- return mNickNameList;
- }
-
- /**
- * @hide
- */
- public String getDisplayName() {
- if (mDisplayName == null) {
- constructDisplayName();
- }
- return mDisplayName;
- }
-
- /**
- * @hide
- */
- public String getBirthday() {
- return mBirthday;
- }
-
- /**
- * @hide
- */
- public final List<PhotoData> getPhotoList() {
- return mPhotoList;
- }
-
- /**
- * @hide
- */
- public final List<String> getNotes() {
- return mNoteList;
- }
-
- /**
- * @hide
- */
- public final List<PhoneData> getPhoneList() {
- return mPhoneList;
- }
-
- /**
- * @hide
- */
- public final List<EmailData> getEmailList() {
- return mEmailList;
- }
-
- /**
- * @hide
- */
- public final List<PostalData> getPostalList() {
- return mPostalList;
- }
-
- /**
- * @hide
- */
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
- }
-
- /**
* Add a phone info to phoneList.
* @param data phone number
* @param type type col of content://contacts/phones
@@ -643,10 +479,24 @@ public class ContactStruct {
}
}
- PhoneData phoneData = new PhoneData(type,
- PhoneNumberUtils.formatNumber(builder.toString()),
- label, isPrimary);
-
+ final String formattedPhoneNumber;
+ {
+ final String rawPhoneNumber = builder.toString();
+ if (VCardConfig.isJapaneseDevice(mVCardType)) {
+ // As of 2009-10-07, there's no formatNumber() which accepts
+ // the second argument and returns String directly.
+ final SpannableStringBuilder tmpBuilder =
+ new SpannableStringBuilder(rawPhoneNumber);
+ PhoneNumberUtils.formatNumber(tmpBuilder, PhoneNumberUtils.FORMAT_JAPAN);
+ formattedPhoneNumber = tmpBuilder.toString();
+ } else {
+ // There's no information available on vCard side. Depend on the default
+ // behavior, which may cause problem in the future when the additional format
+ // rule is supported (e.g. PhoneNumberUtils.FORMAT_KLINGON)
+ formattedPhoneNumber = PhoneNumberUtils.formatNumber(rawPhoneNumber);
+ }
+ }
+ PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary);
mPhoneList.add(phoneData);
}
@@ -666,24 +516,122 @@ public class ContactStruct {
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
if (mPostalList == null) {
- mPostalList = new ArrayList<PostalData>();
+ mPostalList = new ArrayList<PostalData>(0);
}
mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
}
- private void addOrganization(int type, final String companyName,
- final String positionName, boolean isPrimary) {
+ /**
+ * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * {@link #handleTitleValue(String)}.
+ */
+ private void addNewOrganization(int type, final String companyName,
+ final String departmentName,
+ final String titleName, boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
- mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
}
+
+ private static final List<String> sEmptyList = new ArrayList<String>(0);
- private void addIm(int type, String data, String label, boolean isPrimary) {
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * OrganizationData objects, this input data are attached to the last one which does not
+ * have valid values (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose title is set to null.
+ */
+ private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ if (orgList == null) {
+ orgList = sEmptyList;
+ }
+ final String companyName;
+ final String departmentName;
+ final int size = orgList.size();
+ switch (size) {
+ case 0: {
+ companyName = "";
+ departmentName = null;
+ break;
+ }
+ case 1: {
+ companyName = orgList.get(0);
+ departmentName = null;
+ break;
+ }
+ default: { // More than 1.
+ companyName = orgList.get(0);
+ // We're not sure which is the correct string for department.
+ // In order to keep all the data, concatinate the rest of elements.
+ StringBuilder builder = new StringBuilder();
+ for (int i = 1; i < size; i++) {
+ if (i > 1) {
+ builder.append(' ');
+ }
+ builder.append(orgList.get(i));
+ }
+ departmentName = builder.toString();
+ }
+ }
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" title which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+ // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+ if (organizationData.companyName == null &&
+ organizationData.departmentName == null) {
+ // Probably the "TITLE" property comes before the "ORG" property via
+ // handleTitleLine().
+ organizationData.companyName = companyName;
+ organizationData.departmentName = departmentName;
+ organizationData.isPrimary = isPrimary;
+ return;
+ }
+ }
+ // No OrganizatioData is available. Create another one, with "null" title, which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ }
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ /**
+ * Set "title" value to the appropriate data. If there's more than one
+ * OrganizationData objects, this input is attached to the last one which does not
+ * have valid title value (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * set to null.
+ */
+ private void handleTitleValue(final String title) {
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ if (organizationData.titleName == null) {
+ organizationData.titleName = title;
+ return;
+ }
+ }
+ // No Organization is available. Create another one, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ }
+
+ private void addIm(int protocol, String customProtocol, int type,
+ String propValue, boolean isPrimary) {
if (mImList == null) {
mImList = new ArrayList<ImData>();
}
- mImList.add(new ImData(type, data, label, isPrimary));
+ mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
}
private void addNote(final String note) {
@@ -693,43 +641,14 @@ public class ContactStruct {
mNoteList.add(note);
}
- private void addPhotoBytes(String formatName, byte[] photoBytes) {
+ private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
if (mPhotoList == null) {
mPhotoList = new ArrayList<PhotoData>(1);
}
- final PhotoData photoData = new PhotoData(0, null, photoBytes);
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
mPhotoList.add(photoData);
}
- /**
- * 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(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
- "", null, false);
- size = 1;
- }
- OrganizationData lastData = mOrganizationList.get(size - 1);
- lastData.positionName = positionValue;
- }
-
@SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) {
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -755,7 +674,7 @@ public class ContactStruct {
mFamilyName = elems.get(0);
}
}
-
+
/**
* Some Japanese mobile phones use this field for phonetic name,
* since vCard 2.1 does not have "SORT-STRING" type.
@@ -796,28 +715,36 @@ public class ContactStruct {
}
final String propValue = listToString(propValueList).trim();
- if (propName.equals("VERSION")) {
+ if (propName.equals(Constants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
- } else if (propName.equals("FN")) {
+ } else if (propName.equals(Constants.PROPERTY_FN)) {
mFullName = propValue;
- } else if (propName.equals("NAME") && mFullName == null) {
+ } else if (propName.equals(Constants.PROPERTY_NAME) && mFullName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
mFullName = propValue;
- } else if (propName.equals("N")) {
+ } else if (propName.equals(Constants.PROPERTY_N)) {
handleNProperty(propValueList);
- } else if (propName.equals("SORT-STRING")) {
+ } else if (propName.equals(Constants.PROPERTY_NICKNAME)) {
mPhoneticFullName = propValue;
- } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+ } else if (propName.equals(Constants.PROPERTY_NICKNAME) ||
+ propName.equals(Constants.ImportOnly.PROPERTY_X_NICKNAME)) {
addNickName(propValue);
- } else if (propName.equals("SOUND")) {
+ } else if (propName.equals(Constants.PROPERTY_SOUND)) {
Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
- handlePhoneticNameFromSound(propValueList);
+ // As of 2009-10-08, Parser side does not split a property value into separated
+ // values using ';' (in other words, propValueList.size() == 1),
+ // which is correct behavior from the view of vCard 2.1.
+ // But we want it to be separated, so do the separation here.
+ final List<String> phoneticNameList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
}
- } else if (propName.equals("ADR")) {
+ } else if (propName.equals(Constants.PROPERTY_ADR)) {
boolean valuesAreAllEmpty = true;
for (String value : propValueList) {
if (value.length() > 0) {
@@ -836,23 +763,21 @@ public class ContactStruct {
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
- // Only first "PREF" is considered.
- mPrefIsSet_Address = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
label = "";
} else if (typeString.equals(Constants.ATTR_TYPE_WORK) ||
- typeString.equalsIgnoreCase("COMPANY")) {
+ typeString.equalsIgnoreCase(Constants.ATTR_EXTRA_TYPE_COMPANY)) {
// "COMPANY" seems emitted by Windows Mobile, which is not
// specifically supported by vCard 2.1. We assume this is same
// as "WORK".
type = StructuredPostal.TYPE_WORK;
label = "";
- } else if (typeString.equals("PARCEL") ||
- typeString.equals("DOM") ||
- typeString.equals("INTL")) {
+ } else if (typeString.equals(Constants.ATTR_ADR_TYPE_PARCEL) ||
+ typeString.equals(Constants.ATTR_ADR_TYPE_DOM) ||
+ typeString.equals(Constants.ATTR_ADR_TYPE_INTL)) {
// We do not have any appropriate way to store this information.
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -871,7 +796,7 @@ public class ContactStruct {
}
addPostal(type, propValueList, label, isPrimary);
- } else if (propName.equals("EMAIL")) {
+ } else if (propName.equals(Constants.PROPERTY_EMAIL)) {
int type = -1;
String label = null;
boolean isPrimary = false;
@@ -879,9 +804,7 @@ public class ContactStruct {
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
type = Email.TYPE_HOME;
@@ -905,50 +828,47 @@ public class ContactStruct {
type = Email.TYPE_OTHER;
}
addEmail(type, propValue, label, isPrimary);
- } else if (propName.equals("ORG")) {
+ } else if (propName.equals(Constants.PROPERTY_ORG)) {
// vCard specification does not specify other types.
- int type = Organization.TYPE_WORK;
+ final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
-
Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
- // vCard specification officially does not have PREF in ORG.
- // This is just for safety.
- mPrefIsSet_Organization = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
}
}
}
-
- 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")) {
- String formatName = null;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- formatName = typeCollection.iterator().next();
- }
+ handleOrgValue(type, propValueList, isPrimary);
+ } else if (propName.equals(Constants.PROPERTY_TITLE)) {
+ handleTitleValue(propValue);
+ } else if (propName.equals(Constants.PROPERTY_ROLE)) {
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
+ } else if (propName.equals(Constants.PROPERTY_PHOTO) ||
+ propName.equals(Constants.PROPERTY_LOGO)) {
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
// Currently we do not have appropriate example for testing this case.
} else {
- addPhotoBytes(formatName, propBytes);
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (Constants.ATTR_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
}
- } else if (propName.equals("TEL")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ } else if (propName.equals(Constants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
final int type;
final String label;
if (typeObject instanceof Integer) {
@@ -960,9 +880,7 @@ public class ContactStruct {
}
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
@@ -975,47 +893,48 @@ public class ContactStruct {
int type = Phone.TYPE_OTHER;
final String label = null;
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
addPhone(type, propValue, label, isPrimary);
- } else if (sImMap.containsKey(propName)){
- int type = sImMap.get(propName);
+ } else if (sImMap.containsKey(propName)) {
+ final int protocol = sImMap.get(propName);
boolean isPrimary = false;
+ int type = -1;
final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
- type = Phone.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
- type = Phone.TYPE_WORK;
+ } else if (type < 0) {
+ if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
+ type = Im.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
+ type = Im.TYPE_WORK;
+ }
}
}
}
if (type < 0) {
type = Phone.TYPE_HOME;
}
- addIm(type, propValue, null, isPrimary);
- } else if (propName.equals("NOTE")) {
+ addIm(protocol, null, type, propValue, isPrimary);
+ } else if (propName.equals(Constants.PROPERTY_NOTE)) {
addNote(propValue);
- } else if (propName.equals("URL")) {
+ } else if (propName.equals(Constants.PROPERTY_URL)) {
if (mWebsiteList == null) {
mWebsiteList = new ArrayList<String>(1);
}
mWebsiteList.add(propValue);
- } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
- } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
mPhoneticMiddleName = propValue;
- } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
+ } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
mPhoneticFamilyName = propValue;
- } else if (propName.equals("BDAY")) {
+ } else if (propName.equals(Constants.PROPERTY_BDAY)) {
mBirthday = propValue;
/*} else if (propName.equals("REV")) {
// Revision of this VCard entry. I think we can ignore this.
@@ -1048,7 +967,10 @@ public class ContactStruct {
* Construct the display name. The constructed data must not be null.
*/
private void constructDisplayName() {
- if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFullName)) {
+ mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
StringBuilder builder = new StringBuilder();
List<String> nameList;
switch (VCardConfig.getNameOrderType(mVCardType)) {
@@ -1079,8 +1001,6 @@ public class ContactStruct {
}
}
mDisplayName = builder.toString();
- } else if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
} else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1103,25 +1023,10 @@ public class ContactStruct {
*/
public void consolidateFields() {
constructDisplayName();
-
+
if (mPhoneticFullName != null) {
mPhoneticFullName = mPhoneticFullName.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 && mPostalList != null && mPostalList.size() > 0) {
- mPostalList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
- mEmailList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
- mOrganizationList.get(0).isPrimary = true;
- }
}
// From GoogleSource.java in Contacts app.
@@ -1181,22 +1086,16 @@ public class ContactStruct {
}
if (mNickNameList != null && mNickNameList.size() > 0) {
- boolean first = true;
for (String nickName : mNickNameList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
builder.withValue(Nickname.NAME, nickName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
- }
operationList.add(builder.build());
}
}
-
+
if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1108,34 @@ public class ContactStruct {
}
builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ builder.withValue(Phone.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mOrganizationList != null) {
- boolean first = true;
for (OrganizationData organizationData : mOrganizationList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
- // Currently, we do not use TYPE_CUSTOM.
builder.withValue(Organization.TYPE, organizationData.type);
- builder.withValue(Organization.COMPANY, organizationData.companyName);
- builder.withValue(Organization.TITLE, organizationData.positionName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ if (organizationData.companyName != null) {
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ }
+ if (organizationData.departmentName != null) {
+ builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+ }
+ if (organizationData.titleName != null) {
+ builder.withValue(Organization.TITLE, organizationData.titleName);
+ }
+ if (organizationData.isPrimary) {
+ builder.withValue(Organization.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mEmailList != null) {
for (EmailData emailData : mEmailList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1265,12 +1168,11 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-
builder.withValue(Im.TYPE, imData.type);
- if (imData.type == Im.TYPE_CUSTOM) {
- builder.withValue(Im.LABEL, imData.label);
+ builder.withValue(Im.PROTOCOL, imData.protocol);
+ if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+ builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
}
- builder.withValue(Im.DATA, imData.data);
if (imData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
@@ -1282,22 +1184,19 @@ public class ContactStruct {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
builder.withValue(Note.NOTE, note);
operationList.add(builder.build());
}
}
-
+
if (mPhotoList != null) {
- boolean first = true;
for (PhotoData photoData : mPhotoList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
builder.withValue(Photo.PHOTO, photoData.photoBytes);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
@@ -1310,12 +1209,12 @@ public class ContactStruct {
builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
builder.withValue(Website.URL, website);
// There's no information about the type of URL in vCard.
- // We use TYPE_HOME for safety.
- builder.withValue(Website.TYPE, Website.TYPE_HOME);
+ // We use TYPE_HOMEPAGE for safety.
+ builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
operationList.add(builder.build());
}
}
-
+
if (!TextUtils.isEmpty(mBirthday)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1364,4 +1263,99 @@ public class ContactStruct {
return "";
}
}
+
+ // All getter methods should be used carefully, since they may change
+ // in the future as of 2009-10-05, on which I cannot be sure this structure
+ // is completely consolidated.
+ //
+ // Also note that these getter methods should be used only after
+ // all properties being pushed into this object. If not, incorrect
+ // value will "be stored in the local cache and" be returned to you.
+
+ public String getFamilyName() {
+ return mFamilyName;
+ }
+
+ public String getGivenName() {
+ return mGivenName;
+ }
+
+ public String getMiddleName() {
+ return mMiddleName;
+ }
+
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public String getFullName() {
+ return mFullName;
+ }
+
+ public String getPhoneticFamilyName() {
+ return mPhoneticFamilyName;
+ }
+
+ public String getPhoneticGivenName() {
+ return mPhoneticGivenName;
+ }
+
+ public String getPhoneticMiddleName() {
+ return mPhoneticMiddleName;
+ }
+
+ public String getPhoneticFullName() {
+ return mPhoneticFullName;
+ }
+
+ public final List<String> getNickNameList() {
+ return mNickNameList;
+ }
+
+ public String getBirthday() {
+ return mBirthday;
+ }
+
+ public final List<String> getNotes() {
+ return mNoteList;
+ }
+
+ public final List<PhoneData> getPhoneList() {
+ return mPhoneList;
+ }
+
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
+ }
+
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
+ }
+
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
+ }
+
+ public final List<ImData> getImList() {
+ return mImList;
+ }
+
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
+ }
+
+ public final List<String> getWebsiteList() {
+ return mWebsiteList;
+ }
+
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index c4711f8..c5afb73 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -72,19 +72,38 @@ import java.util.Set;
* Usually, this class should be used like this.
* </p>
*
- * <pre class="prettyprint"> VCardComposer composer = null; try { composer = new
- * VCardComposer(context); composer.addHandler(composer.new
- * HandlerForOutputStream(outputStream)); if (!composer.init()) { // Do
- * something handling the situation. return; } while (!composer.isAfterLast()) {
- * if (mCanceled) { // Assume a user may cancel this operation during the
- * export. return; } if (!composer.createOneEntry()) { // Do something handling
- * the error situation. return; } } } finally { if (composer != null) {
- * composer.terminate(); } } </pre>
+ * <pre class="prettyprint">VCardComposer composer = null;
+ * try {
+ * composer = new VCardComposer(context);
+ * composer.addHandler(
+ * composer.new HandlerForOutputStream(outputStream));
+ * if (!composer.init()) {
+ * // Do something handling the situation.
+ * return;
+ * }
+ * while (!composer.isAfterLast()) {
+ * if (mCanceled) {
+ * // Assume a user may cancel this operation during the export.
+ * return;
+ * }
+ * if (!composer.createOneEntry()) {
+ * // Do something handling the error situation.
+ * return;
+ * }
+ * }
+ * } finally {
+ * if (composer != null) {
+ * composer.terminate();
+ * }
+ * } </pre>
*/
public class VCardComposer {
private static final String LOG_TAG = "vcard.VCardComposer";
- private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+ // TODO: Should be configurable?
+ public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+ public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+ public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -95,28 +114,83 @@ public class VCardComposer {
public static final String FAILURE_REASON_NOT_INITIALIZED =
"The vCard composer object is not correctly initialized";
+ /** Should be visible only from developers... (no need to translate, hopefully) */
+ public static final String FAILURE_REASON_UNSUPPORTED_URI =
+ "The Uri vCard composer received is not supported by the composer.";
+
public static final String NO_ERROR = "No error";
+ public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+ // Property for call log entry
+ private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
+ private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
+ private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
+ private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
+
+ private static final String VCARD_DATA_VCARD = "VCARD";
+ private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+ private static final String VCARD_ATTR_SEPARATOR = ";";
+ private static final String VCARD_COL_SEPARATOR = "\r\n";
+ private static final String VCARD_DATA_SEPARATOR = ":";
+ private static final String VCARD_ITEM_SEPARATOR = ";";
+ private static final String VCARD_WS = " ";
+ private static final String VCARD_ATTR_EQUAL = "=";
+
+ private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
+
+ private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+ private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+
+ /**
+ * Special URI for testing.
+ */
+ public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
+ public static final Uri VCARD_TEST_AUTHORITY_URI =
+ Uri.parse("content://" + VCARD_TEST_AUTHORITY);
+ public static final Uri CONTACTS_TEST_CONTENT_URI =
+ Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
+
private static final Uri sDataRequestUri;
+ private static final Map<Integer, String> sImMap;
+
+ /**
+ * See the comment in {@link VCardConfig#FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES}.
+ */
+ private static final Set<String> sPrimaryPropertyNameSet;
static {
Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
sDataRequestUri = builder.build();
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ // Google talk is a special case.
+
+ // TODO: incomplete. Implement properly
+ sPrimaryPropertyNameSet = new HashSet<String>();
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_N);
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_FN);
+ sPrimaryPropertyNameSet.add(Constants.PROPERTY_SOUND);
}
public static interface OneEntryHandler {
public boolean onInit(Context context);
-
public boolean onEntryCreated(String vcard);
-
public void onTerminate();
}
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one
- * by one.
+ * An useful example handler, which emits VCard String to outputstream one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {{@link #onTerminate()}.
@@ -211,65 +285,6 @@ public class VCardComposer {
}
}
- public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
- private static final String VCARD_PROPERTY_ADR = "ADR";
- private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
- private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
- private static final String VCARD_PROPERTY_END = "END";
- private static final String VCARD_PROPERTY_NAME = "N";
- private static final String VCARD_PROPERTY_FULL_NAME = "FN";
- private static final String VCARD_PROPERTY_NOTE = "NOTE";
- private static final String VCARD_PROPERTY_ORG = "ORG";
- private static final String VCARD_PROPERTY_SOUND = "SOUND";
- private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
- private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
- private static final String VCARD_PROPERTY_TEL = "TEL";
- private static final String VCARD_PROPERTY_TITLE = "TITLE";
- private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
- private static final String VCARD_PROPERTY_VERSION = "VERSION";
- private static final String VCARD_PROPERTY_URL = "URL";
- private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
- private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
- // Android specific properties
- // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
- private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
- // Property for call log entry
- private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
- private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
- private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
- private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
- // Properties for DoCoMo vCard.
- private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
- private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
- private static final String VCARD_PROPERTY_X_NO = "X-NO";
- private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_ATTR_SEPARATOR = ";";
- private static final String VCARD_COL_SEPARATOR = "\r\n";
- private static final String VCARD_DATA_SEPARATOR = ":";
- private static final String VCARD_ITEM_SEPARATOR = ";";
- private static final String VCARD_WS = " ";
- private static final String VCARD_ATTR_EQUAL = "=";
-
- // Type strings are now in VCardConstants.java.
-
- private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
- private static final String SHIFT_JIS = "SHIFT_JIS";
-
private final Context mContext;
private final int mVCardType;
private final boolean mCareHandlerErrors;
@@ -286,7 +301,9 @@ public class VCardComposer {
private final boolean mUsesDefactProperty;
private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
- private final boolean mUsesQPToPrimaryProperties;
+ private final boolean mAppendTypeParamName;
+ private final boolean mRefrainsQPToPrimaryProperties;
+ private final boolean mNeedsToConvertPhoneticString;
private Cursor mCursor;
private int mIdColumn;
@@ -298,20 +315,7 @@ public class VCardComposer {
private String mErrorReason = NO_ERROR;
- private static final Map<Integer, String> sImMap;
-
- static {
- sImMap = new HashMap<Integer, String>();
- sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
- }
-
- private boolean mIsCallLogComposer = false;
+ private boolean mIsCallLogComposer;
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
@@ -332,30 +336,24 @@ public class VCardComposer {
private static final String FLAG_TIMEZONE_UTC = "Z";
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
}
- public VCardComposer(Context context, String vcardTypeStr,
- boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
- careHandlerErrors, false);
+ public VCardComposer(Context context, int vcardType) {
+ this(context, vcardType, true);
}
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
- this(context, vcardType, careHandlerErrors, false);
+ public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
+ this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
}
/**
* Construct for supporting call log entry vCard composing.
- *
- * @param isCallLogComposer true if this composer is for creating Call Log vCard.
*/
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
- boolean isCallLogComposer) {
+ public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
- mIsCallLogComposer = isCallLogComposer;
mContentResolver = context.getContentResolver();
mIsV30 = VCardConfig.isV30(vcardType);
@@ -370,7 +368,9 @@ public class VCardComposer {
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
- mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
+ mRefrainsQPToPrimaryProperties = VCardConfig.refrainsQPToPrimaryProperties(vcardType);
+ mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+ mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) {
@@ -389,50 +389,32 @@ public class VCardComposer {
}
/**
- * This static function is to compose vCard for phone own number
- */
- public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
- String phoneNumber, boolean vcardVer21) {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (!vcardVer21) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- boolean needCharset = false;
- if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
- needCharset = true;
- }
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
- String label = Integer.toString(phonetype);
- appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
- return builder.toString();
- }
-
- /**
* Must call before {{@link #init()}.
*/
public void addHandler(OneEntryHandler handler) {
mHandlerList.add(handler);
}
+ /**
+ * @return Returns true when initialization is successful and all the other
+ * methods are available. Returns false otherwise.
+ */
public boolean init() {
return init(null, null);
}
+ public boolean init(final String selection, final String[] selectionArgs) {
+ return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
+ }
+
/**
- * @return Returns true when initialization is successful and all the other
- * methods are available. Returns false otherwise.
+ * Note that this is unstable interface, may be deleted in the future.
*/
- public boolean init(final String selection, final String[] selectionArgs) {
+ public boolean init(final Uri contentUri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (contentUri == null) {
+ return false;
+ }
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -451,13 +433,19 @@ public class VCardComposer {
}
}
- if (mIsCallLogComposer) {
- mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
- selection, selectionArgs, null);
+ final String[] projection;
+ if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
+ projection = sCallLogProjection;
+ mIsCallLogComposer = true;
+ } else if (Contacts.CONTENT_URI.equals(contentUri) ||
+ CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+ projection = sContactsProjection;
} else {
- mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
- selection, selectionArgs, null);
+ mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
+ return false;
}
+ mCursor = mContentResolver.query(
+ contentUri, projection, selection, selectionArgs, sortOrder);
if (mCursor == null) {
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -534,89 +522,6 @@ public class VCardComposer {
return true;
}
- /**
- * Format according to RFC 2445 DATETIME type.
- * The format is: ("%Y%m%dT%H%M%SZ").
- */
- private final String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- String date = startDate.format2445();
- return date + FLAG_TIMEZONE_UTC;
- }
-
- /**
- * Try to append the property line for a call history time stamp field if possible.
- * Do nothing if the call log type gotton from the database is invalid.
- */
- private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
- // Extension for call history as defined in
- // in the Specification for Ic Mobile Communcation - ver 1.1,
- // Oct 2000. This is used to send the details of the call
- // history - missed, incoming, outgoing along with date and time
- // to the requesting device (For example, transferring phone book
- // when connected over bluetooth)
- //
- // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
- final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- final String callLogTypeStr;
- switch (callLogType) {
- case Calls.INCOMING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
- break;
- }
- case Calls.OUTGOING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
- break;
- }
- case Calls.MISSED_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
- break;
- }
- default: {
- Log.w(LOG_TAG, "Call log type not correct.");
- return;
- }
- }
-
- final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.append(VCARD_PROPERTY_X_TIMESTAMP);
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, callLogTypeStr);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(toRfc2455Format(dateAsLong));
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private String createOneCallLogEntryInternal() {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
- String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
- if (TextUtils.isEmpty(name)) {
- name = mCursor.getString(NUMBER_COLUMN_INDEX);
- }
- final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
-
- String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
- String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
- if (TextUtils.isEmpty(label)) {
- label = Integer.toString(type);
- }
- appendVCardTelephoneLine(builder, type, label, number);
- tryAppendCallHistoryTimeStampField(builder);
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
- return builder.toString();
- }
-
private String createOneEntryInternal(final String contactId) {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
@@ -633,8 +538,7 @@ public class VCardComposer {
dataExists = entityIterator.hasNext();
while (entityIterator.hasNext()) {
Entity entity = entityIterator.next();
- for (NamedContentValues namedContentValues : entity
- .getSubValues()) {
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
ContentValues contentValues = namedContentValues.values;
String key = contentValues.getAsString(Data.MIMETYPE);
if (key != null) {
@@ -663,11 +567,11 @@ public class VCardComposer {
}
final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
} else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
}
appendStructuredNames(builder, contentValuesListMap);
@@ -681,16 +585,16 @@ public class VCardComposer {
appendOrganizations(builder, contentValuesListMap);
appendPhotos(builder, contentValuesListMap);
appendNotes(builder, contentValuesListMap);
- // TODO: GroupMembership
+ // TODO: GroupMembership, Relation, Event other than birthday.
if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendVCardLine(builder, Constants.PROPERTY_X_REDUCTION, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_NO, "");
+ appendVCardLine(builder, Constants.PROPERTY_X_DCM_HMN_MODE, "");
}
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
return builder.toString();
}
@@ -748,11 +652,11 @@ public class VCardComposer {
if (contentValuesList != null && contentValuesList.size() > 0) {
appendStructuredNamesInternal(builder, contentValuesList);
} else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
} else if (mIsV30) {
// vCard 3.0 requires "N" and "FN" properties.
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
+ appendVCardLine(builder, Constants.PROPERTY_FN, "");
}
}
@@ -762,21 +666,38 @@ public class VCardComposer {
// may get confused with multiple "N", "FN", etc. properties, though it is valid in
// vCard spec.
ContentValues primaryContentValues = null;
+ ContentValues subprimaryContentValues = null;
for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null){
+ continue;
+ }
Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
- if (isSuperPrimary != null && isSuperPrimary != 0) {
+ if (isSuperPrimary != null && isSuperPrimary > 0) {
// We choose "super primary" ContentValues.
primaryContentValues = contentValues;
break;
- } else if (primaryContentValues == null && contentValues != null) {
- // We choose the first ContentValues if "super primary" ContentValues does not exist.
- primaryContentValues = contentValues;
+ } else if (primaryContentValues == null) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ Integer primary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (primary != null && primary > 0) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null) {
+ subprimaryContentValues = contentValues;
+ }
}
}
if (primaryContentValues == null) {
- Log.e(LOG_TAG, "All ContentValues given from database is empty.");
- primaryContentValues = new ContentValues();
+ if (subprimaryContentValues != null) {
+ // We choose the first ContentValues if any "primary" ContentValues does not exist.
+ primaryContentValues = subprimaryContentValues;
+ } else {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
}
final String familyName = primaryContentValues
@@ -800,7 +721,7 @@ public class VCardComposer {
final String encodedSuffix;
final boolean reallyUseQuotedPrintableToName =
- (mUsesQPToPrimaryProperties &&
+ (!mRefrainsQPToPrimaryProperties &&
!(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
@@ -822,7 +743,7 @@ public class VCardComposer {
}
// N property. This order is specified by vCard spec and does not depend on countries.
- builder.append(VCARD_PROPERTY_NAME);
+ builder.append(Constants.PROPERTY_N);
if (shouldAppendCharsetAttribute(Arrays.asList(
familyName, givenName, middleName, prefix, suffix))) {
builder.append(VCARD_ATTR_SEPARATOR);
@@ -845,20 +766,20 @@ public class VCardComposer {
builder.append(encodedSuffix);
builder.append(VCARD_COL_SEPARATOR);
- final String fullname = VCardUtils.constructNameFromElements(
+ final String formattedName = VCardUtils.constructNameFromElements(
VCardConfig.getNameOrderType(mVCardType),
encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
final boolean reallyUseQuotedPrintableToFullname =
- mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname);
+ !mRefrainsQPToPrimaryProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
final String encodedFullname =
reallyUseQuotedPrintableToFullname ?
- encodeQuotedPrintable(fullname) :
- escapeCharacters(fullname);
+ encodeQuotedPrintable(formattedName) :
+ escapeCharacters(formattedName);
// FN property
- builder.append(VCARD_PROPERTY_FULL_NAME);
+ builder.append(Constants.PROPERTY_FN);
if (shouldAppendCharsetAttribute(encodedFullname)) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -872,14 +793,14 @@ public class VCardComposer {
builder.append(VCARD_COL_SEPARATOR);
} else if (!TextUtils.isEmpty(displayName)) {
final boolean reallyUseQuotedPrintableToDisplayName =
- (mUsesQPToPrimaryProperties &&
+ (!mRefrainsQPToPrimaryProperties &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
final String encodedDisplayName =
reallyUseQuotedPrintableToDisplayName ?
encodeQuotedPrintable(displayName) :
escapeCharacters(displayName);
- builder.append(VCARD_PROPERTY_NAME);
+ builder.append(Constants.PROPERTY_N);
if (shouldAppendCharsetAttribute(encodedDisplayName)) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -895,38 +816,54 @@ public class VCardComposer {
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_ITEM_SEPARATOR);
builder.append(VCARD_COL_SEPARATOR);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ if (mIsV30) {
+ builder.append(Constants.PROPERTY_FN);
+ // TODO: Not allowed formally...
+ if (shouldAppendCharsetAttribute(encodedDisplayName)) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedDisplayName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
} else if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+ // vCard 3.0 specification requires these fields.
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
+ appendVCardLine(builder, Constants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendVCardLine(builder, Constants.PROPERTY_N, "");
}
- String phoneticFamilyName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- String phoneticMiddleName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- String phoneticGivenName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName) &&
- TextUtils.isEmpty(phoneticGivenName))) { // if not empty
- if (mIsJapaneseMobilePhone) {
- phoneticFamilyName = VCardUtils
- .toHalfWidthString(phoneticFamilyName);
- phoneticMiddleName = VCardUtils
- .toHalfWidthString(phoneticMiddleName);
- phoneticGivenName = VCardUtils
- .toHalfWidthString(phoneticGivenName);
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ String tmpPhoneticFamilyName =
+ primaryContentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ String tmpPhoneticMiddleName =
+ primaryContentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ String tmpPhoneticGivenName =
+ primaryContentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (mNeedsToConvertPhoneticString) {
+ phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+ phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+ phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+ } else {
+ phoneticFamilyName = tmpPhoneticFamilyName;
+ phoneticMiddleName = tmpPhoneticMiddleName;
+ phoneticGivenName = tmpPhoneticGivenName;
}
+ }
+ if (!(TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName)
+ && TextUtils.isEmpty(phoneticGivenName))) {
if (mIsV30) {
final String sortString = VCardUtils
.constructNameFromElements(mVCardType,
- phoneticFamilyName,
- phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_SORT_STRING);
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ builder.append(Constants.PROPERTY_SORT_STRING);
// Do not need to care about QP, since vCard 3.0 does not allow it.
final String encodedSortString = escapeCharacters(sortString);
@@ -937,25 +874,26 @@ public class VCardComposer {
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedSortString);
builder.append(VCARD_COL_SEPARATOR);
- } else {
+ } else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing
// phonetic name in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way since it is supported by
+ // We chose to use DoCoMo's way when the device is Japanese one
+ // since it is supported by
// a lot of Japanese mobile phones. This is "X-" property, so
// any parser hopefully would not get confused with this.
- builder.append(VCARD_PROPERTY_SOUND);
+ builder.append(Constants.PROPERTY_SOUND);
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(Constants.ATTR_TYPE_X_IRMC_N);
boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticFamilyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticMiddleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticGivenName)));
+ (!mRefrainsQPToPrimaryProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
final String encodedPhoneticFamilyName;
final String encodedPhoneticMiddleName;
@@ -987,7 +925,7 @@ public class VCardComposer {
builder.append(VCARD_COL_SEPARATOR);
}
} else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_SOUND);
+ builder.append(Constants.PROPERTY_SOUND);
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(Constants.ATTR_TYPE_X_IRMC_N);
builder.append(VCARD_DATA_SEPARATOR);
@@ -1001,15 +939,14 @@ public class VCardComposer {
if (mUsesDefactProperty) {
if (!TextUtils.isEmpty(phoneticGivenName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName);
final String encodedPhoneticGivenName;
if (reallyUseQuotedPrintable) {
encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
} else {
encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
+ builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME);
if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -1024,15 +961,14 @@ public class VCardComposer {
}
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName);
final String encodedPhoneticMiddleName;
if (reallyUseQuotedPrintable) {
encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
} else {
encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -1047,15 +983,14 @@ public class VCardComposer {
}
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName);
final String encodedPhoneticFamilyName;
if (reallyUseQuotedPrintable) {
encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
} else {
encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
}
- builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
+ builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME);
if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -1075,46 +1010,48 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Nickname.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- final String propertyNickname;
- if (mIsV30) {
- propertyNickname = VCARD_PROPERTY_NICKNAME;
- } else if (mUsesAndroidProperty) {
- propertyNickname = VCARD_PROPERTY_X_NICKNAME;
- } else {
- // There's no way to add this field.
- return;
- }
+ if (contentValuesList == null) {
+ return;
+ }
- for (ContentValues contentValues : contentValuesList) {
- final String nickname = contentValues.getAsString(Nickname.NAME);
- if (TextUtils.isEmpty(nickname)) {
- continue;
- }
+ final String propertyNickname;
+ if (mIsV30) {
+ propertyNickname = Constants.PROPERTY_NICKNAME;
+ /*} else if (mUsesAndroidProperty) {
+ propertyNickname = VCARD_PROPERTY_X_NICKNAME;*/
+ } else {
+ // There's no way to add this field.
+ return;
+ }
- final String encodedNickname;
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
- if (reallyUseQuotedPrintable) {
- encodedNickname = encodeQuotedPrintable(nickname);
- } else {
- encodedNickname = escapeCharacters(nickname);
- }
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
- builder.append(propertyNickname);
- if (shouldAppendCharsetAttribute(propertyNickname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedNickname);
- builder.append(VCARD_COL_SEPARATOR);
+ final String encodedNickname;
+ final boolean reallyUseQuotedPrintable =
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
+ if (reallyUseQuotedPrintable) {
+ encodedNickname = encodeQuotedPrintable(nickname);
+ } else {
+ encodedNickname = escapeCharacters(nickname);
+ }
+
+ builder.append(propertyNickname);
+ if (shouldAppendCharsetAttribute(propertyNickname)) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+ if (reallyUseQuotedPrintable) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(VCARD_ATTR_ENCODING_QP);
}
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedNickname);
+ builder.append(VCARD_COL_SEPARATOR);
}
}
@@ -1128,6 +1065,9 @@ public class VCardComposer {
for (ContentValues contentValues : contentValuesList) {
final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
final String label = contentValues.getAsString(Phone.LABEL);
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
String phoneNumber = contentValues.getAsString(Phone.NUMBER);
if (phoneNumber != null) {
phoneNumber = phoneNumber.trim();
@@ -1136,18 +1076,18 @@ public class VCardComposer {
continue;
}
phoneLineExists = true;
- int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
+ int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
// TODO: Premature, since this allows two phone numbers which are
// same from the view of phone number format (e.g. "100" v.s. "1-0-0")
if (!phoneSet.contains(phoneNumber)) {
phoneSet.add(phoneNumber);
- appendVCardTelephoneLine(builder, type, label, phoneNumber);
+ appendVCardTelephoneLine(builder, type, label, phoneNumber, isPrimary);
}
}
}
if (!phoneLineExists && mIsDoCoMo) {
- appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
+ appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "", false);
}
}
@@ -1155,14 +1095,11 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Email.CONTENT_ITEM_TYPE);
+
boolean emailAddressExists = false;
if (contentValuesList != null) {
- Set<String> addressSet = new HashSet<String>();
+ final Set<String> addressSet = new HashSet<String>();
for (ContentValues contentValues : contentValuesList) {
- Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
- final int type = (typeAsObject != null ?
- typeAsObject : Email.TYPE_OTHER);
- final String label = contentValues.getAsString(Email.LABEL);
String emailAddress = contentValues.getAsString(Email.DATA);
if (emailAddress != null) {
emailAddress = emailAddress.trim();
@@ -1170,16 +1107,23 @@ public class VCardComposer {
if (TextUtils.isEmpty(emailAddress)) {
continue;
}
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_EMAIL_TYPE);
+ final String label = contentValues.getAsString(Email.LABEL);
+ Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
emailAddressExists = true;
if (!addressSet.contains(emailAddress)) {
addressSet.add(emailAddress);
- appendVCardEmailLine(builder, type, label, emailAddress);
+ appendVCardEmailLine(builder, type, label, emailAddress, isPrimary);
}
}
}
if (!emailAddressExists && mIsDoCoMo) {
- appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
+ appendVCardEmailLine(builder, Email.TYPE_HOME, "", "", false);
}
}
@@ -1194,7 +1138,7 @@ public class VCardComposer {
appendPostalsForGeneric(builder, contentValuesList);
}
} else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_ADR);
+ builder.append(Constants.PROPERTY_ADR);
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(Constants.ATTR_TYPE_HOME);
builder.append(VCARD_DATA_SEPARATOR);
@@ -1236,7 +1180,10 @@ public class VCardComposer {
final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
final String label = contentValues.getAsString(StructuredPostal.LABEL);
if (type == preferedType) {
- appendVCardPostalLine(builder, type, label, contentValues);
+ // Note: Not sure why we need to emit "empty" line even when actual
+ // data does not exist. There may be some reason or may not.
+ // We keep safer side since the previous implementation did so.
+ appendVCardPostalLine(builder, type, label, contentValues, true, true);
return true;
}
}
@@ -1246,11 +1193,18 @@ public class VCardComposer {
private void appendPostalsForGeneric(final StringBuilder builder,
final List<ContentValues> contentValuesList) {
for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type != null) {
- appendVCardPostalLine(builder, type, label, contentValues);
+ if (contentValues == null) {
+ continue;
}
+ final Integer typeAsObject = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_POSTAL_TYPE);
+ final String label = contentValues.getAsString(StructuredPostal.LABEL);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ appendVCardPostalLine(builder, type, label, contentValues, isPrimary, false);
}
}
@@ -1258,24 +1212,63 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Im.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
- String data = contentValues.getAsString(Im.DATA);
- if (data != null) {
- data = data.trim();
- }
- if (TextUtils.isEmpty(data)) {
- continue;
- }
-
- if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
- if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
- appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
+ if (contentValuesList == null) {
+ return;
+ }
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+ if (protocolAsObject == null) {
+ continue;
+ }
+ final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+ if (propertyName == null) {
+ continue;
+ }
+ String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
+ final String typeAsString;
+ {
+ final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+ switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+ case Im.TYPE_HOME: {
+ typeAsString = Constants.ATTR_TYPE_HOME;
+ break;
+ }
+ case Im.TYPE_WORK: {
+ typeAsString = Constants.ATTR_TYPE_WORK;
+ break;
+ }
+ case Im.TYPE_CUSTOM: {
+ final String label = contentValues.getAsString(Im.LABEL);
+ typeAsString = (label != null ? "X-" + label : null);
+ break;
+ }
+ case Im.TYPE_OTHER: // Ignore
+ default: {
+ typeAsString = null;
+ break;
}
- // TODO: add "X-GOOGLE TALK" case...
}
}
+
+ List<String> attributeList = new ArrayList<String>();
+ if (!TextUtils.isEmpty(typeAsString)) {
+ attributeList.add(typeAsString);
+ }
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ attributeList.add(Constants.ATTR_TYPE_PREF);
+ }
+
+ appendVCardLineWithCharsetAndQPDetection(
+ builder, propertyName, attributeList, data);
}
}
@@ -1283,39 +1276,83 @@ public class VCardComposer {
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
.get(Website.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String website = contentValues.getAsString(Website.URL);
- if (website != null) {
- website = website.trim();
- }
- if (!TextUtils.isEmpty(website)) {
- appendVCardLine(builder, VCARD_PROPERTY_URL, website);
- }
+ if (contentValuesList == null) {
+ return;
+ }
+ for (ContentValues contentValues : contentValuesList) {
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+ // Note: vCard 3.0 does not allow any attribute addition toward "URL"
+ // property, while there's no document in vCard 2.1.
+ //
+ // TODO: Should we allow adding it when appropriate?
+ // (Actually, we drop some data. Using "group.X-URL-TYPE" or something
+ // may help)
+ if (!TextUtils.isEmpty(website)) {
+ appendVCardLine(builder, Constants.PROPERTY_URL, website);
}
}
}
+ /**
+ * Theoretically, there must be only one birthday for each vCard entry.
+ * Also, we are afraid of some importer's parse error during its import.
+ * We emit only one birthday entry even when there are more than one.
+ */
private void appendBirthday(final StringBuilder builder,
final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Event.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- Integer eventType = contentValuesList.get(0).getAsInteger(Event.TYPE);
- if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
- return;
+ final List<ContentValues> contentValuesList =
+ contentValuesListMap.get(Event.CONTENT_ITEM_TYPE);
+ if (contentValuesList == null) {
+ return;
+ }
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
}
- // Theoretically, there must be only one birthday for each vCard data and
- // we are afraid of some parse error occuring in some devices, so
- // we emit only one birthday entry for now.
- String birthday = contentValuesList.get(0).getAsString(Event.START_DATE);
- if (birthday != null) {
- birthday = birthday.trim();
+ final Integer eventType = contentValues.getAsInteger(Event.TYPE);
+ if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
+ continue;
+ }
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
+ }
+ final Integer isSuperPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+ (isSuperPrimaryAsInteger > 0) : false);
+ if (isSuperPrimary) {
+ // "super primary" birthday should the prefered one.
+ primaryBirthday = birthdayCandidate;
+ break;
}
- if (!TextUtils.isEmpty(birthday)) {
- appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ // We don't break here since "super primary" birthday may exist later.
+ primaryBirthday = birthdayCandidate;
+ } else if (secondaryBirthday == null) {
+ // First entry is set to the "secondary" candidate.
+ secondaryBirthday = birthdayCandidate;
}
}
+
+ final String birthday;
+ if (primaryBirthday != null) {
+ birthday = primaryBirthday.trim();
+ } else if (secondaryBirthday != null){
+ birthday = secondaryBirthday.trim();
+ } else {
+ return;
+ }
+ appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_BDAY, birthday);
}
private void appendOrganizations(final StringBuilder builder,
@@ -1324,25 +1361,37 @@ public class VCardComposer {
.get(Organization.CONTENT_ITEM_TYPE);
if (contentValuesList != null) {
for (ContentValues contentValues : contentValuesList) {
- String company = contentValues
- .getAsString(Organization.COMPANY);
+ String company = contentValues.getAsString(Organization.COMPANY);
if (company != null) {
company = company.trim();
}
- String title = contentValues
- .getAsString(Organization.TITLE);
+ String department = contentValues.getAsString(Organization.DEPARTMENT);
+ if (department != null) {
+ department = department.trim();
+ }
+ String title = contentValues.getAsString(Organization.TITLE);
if (title != null) {
title = title.trim();
}
+ StringBuilder orgBuilder = new StringBuilder();
if (!TextUtils.isEmpty(company)) {
- appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
- !VCardUtils.containsOnlyPrintableAscii(company),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
+ orgBuilder.append(company);
+ }
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
}
+ final String orgline = orgBuilder.toString();
+ appendVCardLine(builder, Constants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mUsesQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
if (!TextUtils.isEmpty(title)) {
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
+ appendVCardLine(builder, Constants.PROPERTY_TITLE, title,
!VCardUtils.containsOnlyPrintableAscii(title),
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
@@ -1369,11 +1418,9 @@ public class VCardComposer {
photoType = "GIF";
} else if (data.length >= 4 && data[0] == (byte) 0x89
&& data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
- // Note: vCard 2.1 officially does not support PNG, but we
- // may have it
- // and using X- word like "X-PNG" may not let importers know
- // it is
- // PNG. So we use the String "PNG" as is...
+ // Note: vCard 2.1 officially does not support PNG, but we may
+ // have it and using X- word like "X-PNG" may not let importers
+ // know it is PNG. So we use the String "PNG" as is...
photoType = "PNG";
} else if (data.length >= 2 && data[0] == (byte) 0xff
&& data[1] == (byte) 0xd8) {
@@ -1420,7 +1467,7 @@ public class VCardComposer {
final boolean reallyUseQuotedPrintable =
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
shouldAppendCharsetInfo, reallyUseQuotedPrintable);
} else {
for (ContentValues contentValues : contentValuesList) {
@@ -1431,7 +1478,7 @@ public class VCardComposer {
final boolean reallyUseQuotedPrintable =
(mUsesQuotedPrintable &&
!VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+ appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
shouldAppendCharsetInfo, reallyUseQuotedPrintable);
}
}
@@ -1517,7 +1564,7 @@ public class VCardComposer {
private void appendVCardPhotoLine(final StringBuilder builder,
final String encodedData, final String photoType) {
StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(VCARD_PROPERTY_PHOTO);
+ tmpBuilder.append(Constants.PROPERTY_PHOTO);
tmpBuilder.append(VCARD_ATTR_SEPARATOR);
if (mIsV30) {
tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
@@ -1547,59 +1594,126 @@ public class VCardComposer {
builder.append(VCARD_COL_SEPARATOR);
}
- private void appendVCardPostalLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- final ContentValues contentValues) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
+ private class PostalStruct {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ public PostalStruct(final boolean reallyUseQuotedPrintable,
+ final boolean appendCharset, final String addressData) {
+ this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+ this.appendCharset = appendCharset;
+ this.addressData = addressData;
+ }
+ }
- // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
- // There may be some reason or may not be any. We keep safer side.
- // TODO: investigate this.
- boolean dataExists = false;
+ /**
+ * @return null when there's no information available to construct the data.
+ */
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ boolean reallyUseQuotedPrintable = false;
+ boolean appendCharset = false;
+
+ boolean dataArrayExists = false;
String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
- boolean actuallyUseQuotedPrintable = false;
- boolean shouldAppendCharset = false;
for (String data : dataArray) {
if (!TextUtils.isEmpty(data)) {
- dataExists = true;
- if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
- shouldAppendCharset = true;
+ dataArrayExists = true;
+ if (!appendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
+ appendCharset = true;
}
if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
- actuallyUseQuotedPrintable = true;
+ reallyUseQuotedPrintable = true;
break;
}
}
}
- int length = dataArray.length;
- for (int i = 0; i < length; i++) {
- String data = dataArray[i];
- if (!TextUtils.isEmpty(data)) {
- if (actuallyUseQuotedPrintable) {
- dataArray[i] = encodeQuotedPrintable(data);
+ if (dataArrayExists) {
+ StringBuffer addressBuffer = new StringBuffer();
+ boolean first = true;
+ for (String data : dataArray) {
+ if (first) {
+ first = false;
} else {
- dataArray[i] = escapeCharacters(data);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(data)) {
+ if (reallyUseQuotedPrintable) {
+ addressBuffer.append(encodeQuotedPrintable(data));
+ } else {
+ addressBuffer.append(escapeCharacters(data));
+ }
}
}
+ return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
+ addressBuffer.toString());
}
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = StructuredPostal.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
+ String formattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (!TextUtils.isEmpty(formattedAddress)) {
+ reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyPrintableAscii(formattedAddress);
+ appendCharset =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedAddress);
+ if (reallyUseQuotedPrintable) {
+ formattedAddress = encodeQuotedPrintable(formattedAddress);
+ } else {
+ formattedAddress = escapeCharacters(formattedAddress);
+ }
+ // We use the second value ("Extended Address").
+ //
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ StringBuffer addressBuffer = new StringBuffer();
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(formattedAddress);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ }
+ return null; // There's no data available.
+ }
+
+ private void appendVCardPostalLine(final StringBuilder builder,
+ final int type, final String label, final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitLineEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressData = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressData = postalStruct.addressData;
+ }
}
- String typeAsString = null;
- switch (typeAsPrimitive) {
+ List<String> attributeList = new ArrayList<String>();
+ if (isPrimary) {
+ attributeList.add(Constants.ATTR_TYPE_PREF);
+ }
+ switch (type) {
case StructuredPostal.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
+ attributeList.add(Constants.ATTR_TYPE_HOME);
break;
}
case StructuredPostal.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
+ attributeList.add(Constants.ATTR_TYPE_WORK);
break;
}
case StructuredPostal.TYPE_CUSTOM: {
@@ -1609,9 +1723,7 @@ public class VCardComposer {
// ("IANA-token" in the vCard 3.0 is unclear...)
// Just for safety, we add "X-" at the beggining of each label.
// Also checks the label obeys with vCard 3.0 spec.
- builder.append("X-");
- builder.append(label);
- builder.append(VCARD_DATA_SEPARATOR);
+ attributeList.add("X-" + label);
}
break;
}
@@ -1619,82 +1731,56 @@ public class VCardComposer {
break;
}
default: {
- Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
break;
}
}
- // Attribute(s).
+ // Actual data construction starts from here.
+ // TODO: add a new version of appendVCardLine() for this purpose.
+
+ builder.append(Constants.PROPERTY_ADR);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ // Attributes
{
boolean shouldAppendAttrSeparator = false;
- if (typeAsString != null) {
- appendTypeAttribute(builder, typeAsString);
+ if (!attributeList.isEmpty()) {
+ appendTypeAttributes(builder, attributeList);
shouldAppendAttrSeparator = true;
}
- if (dataExists) {
- if (shouldAppendCharset) {
- // Strictly, vCard 3.0 does not allow exporters to emit charset information,
- // but we will add it since the information should be useful for importers,
- //
- // Assume no parser does not emit error with this attribute in vCard 3.0.
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(mVCardAttributeCharset);
- shouldAppendAttrSeparator = true;
+ if (appendCharset) {
+ // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+ // but we will add it since the information should be useful for importers,
+ //
+ // Assume no parser does not emit error with this attribute in vCard 3.0.
+ if (shouldAppendAttrSeparator) {
+ builder.append(VCARD_ATTR_SEPARATOR);
}
+ builder.append(mVCardAttributeCharset);
+ shouldAppendAttrSeparator = true;
+ }
- if (actuallyUseQuotedPrintable) {
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(VCARD_ATTR_ENCODING_QP);
- shouldAppendAttrSeparator = true;
+ if (reallyUseQuotedPrintable) {
+ if (shouldAppendAttrSeparator) {
+ builder.append(VCARD_ATTR_SEPARATOR);
}
+ builder.append(VCARD_ATTR_ENCODING_QP);
+ shouldAppendAttrSeparator = true;
}
}
- // Property values.
-
builder.append(VCARD_DATA_SEPARATOR);
- if (dataExists) {
- // The elements in dataArray are already encoded to quoted printable
- // if needed.
- // See above.
- //
- // TODO: in vCard 3.0, one line may become too huge. Fix this.
- builder.append(dataArray[0]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[1]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[2]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[3]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[4]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[5]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[6]);
- }
+ builder.append(addressData);
builder.append(VCARD_COL_SEPARATOR);
}
private void appendVCardEmailLine(final StringBuilder builder,
- final Integer typeAsObject, final String label, final String data) {
- builder.append(VCARD_PROPERTY_EMAIL);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Email.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
+ final int type, final String label,
+ final String rawData, final boolean isPrimary) {
final String typeAsString;
- switch (typeAsPrimitive) {
+ switch (type) {
case Email.TYPE_CUSTOM: {
// For backward compatibility.
// Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
@@ -1706,7 +1792,7 @@ public class VCardComposer {
&& VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
typeAsString = "X-" + label;
} else {
- typeAsString = DEFAULT_EMAIL_TYPE;
+ typeAsString = null;
}
break;
}
@@ -1719,7 +1805,7 @@ public class VCardComposer {
break;
}
case Email.TYPE_OTHER: {
- typeAsString = DEFAULT_EMAIL_TYPE;
+ typeAsString = null;
break;
}
case Email.TYPE_MOBILE: {
@@ -1727,23 +1813,28 @@ public class VCardComposer {
break;
}
default: {
- Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
- typeAsString = DEFAULT_EMAIL_TYPE;
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
break;
}
}
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, typeAsString);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
+ final List<String> attributeList = new ArrayList<String>();
+ if (isPrimary) {
+ attributeList.add(Constants.ATTR_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ attributeList.add(typeAsString);
+ }
+
+ appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_EMAIL,
+ attributeList, rawData);
}
private void appendVCardTelephoneLine(final StringBuilder builder,
final Integer typeAsObject, final String label,
- String encodedData) {
- builder.append(VCARD_PROPERTY_TEL);
+ final String encodedData, boolean isPrimary) {
+ builder.append(Constants.PROPERTY_TEL);
builder.append(VCARD_ATTR_SEPARATOR);
final int typeAsPrimitive;
@@ -1753,53 +1844,102 @@ public class VCardComposer {
typeAsPrimitive = typeAsObject;
}
+ ArrayList<String> attributeList = new ArrayList<String>();
switch (typeAsPrimitive) {
case Phone.TYPE_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
+ attributeList.addAll(
+ Arrays.asList(Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
break;
case Phone.TYPE_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
+ attributeList.addAll(
+ Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
break;
case Phone.TYPE_FAX_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
+ attributeList.addAll(
+ Arrays.asList(Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
break;
case Phone.TYPE_FAX_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
+ attributeList.addAll(
+ Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
break;
case Phone.TYPE_MOBILE:
- builder.append(Constants.ATTR_TYPE_CELL);
+ attributeList.add(Constants.ATTR_TYPE_CELL);
break;
case Phone.TYPE_PAGER:
if (mIsDoCoMo) {
// Not sure about the reason, but previous implementation had
// used "VOICE" instead of "PAGER"
- // Also, refrain from using appendType() so that "TYPE=" is never be appended.
- builder.append(Constants.ATTR_TYPE_VOICE);
+ attributeList.add(Constants.ATTR_TYPE_VOICE);
} else {
- appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
+ attributeList.add(Constants.ATTR_TYPE_PAGER);
}
break;
case Phone.TYPE_OTHER:
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+ attributeList.add(Constants.ATTR_TYPE_VOICE);
+ break;
+ case Phone.TYPE_CAR:
+ attributeList.add(Constants.ATTR_TYPE_CAR);
+ break;
+ case Phone.TYPE_COMPANY_MAIN:
+ // There's no relevant field in vCard (at least 2.1).
+ attributeList.add(Constants.ATTR_TYPE_WORK);
+ isPrimary = true;
+ break;
+ case Phone.TYPE_ISDN:
+ attributeList.add(Constants.ATTR_TYPE_ISDN);
+ break;
+ case Phone.TYPE_MAIN:
+ isPrimary = true;
+ break;
+ case Phone.TYPE_OTHER_FAX:
+ attributeList.add(Constants.ATTR_TYPE_FAX);
+ break;
+ case Phone.TYPE_TELEX:
+ attributeList.add(Constants.ATTR_TYPE_TLX);
+ break;
+ case Phone.TYPE_WORK_MOBILE:
+ attributeList.addAll(
+ Arrays.asList(Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_CELL));
+ break;
+ case Phone.TYPE_WORK_PAGER:
+ attributeList.add(Constants.ATTR_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ attributeList.add(Constants.ATTR_TYPE_VOICE);
+ } else {
+ attributeList.add(Constants.ATTR_TYPE_PAGER);
+ }
+ break;
+ case Phone.TYPE_MMS:
+ attributeList.add(Constants.ATTR_TYPE_MSG);
break;
case Phone.TYPE_CUSTOM:
if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
&& VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- appendTypeAttribute(builder, "X-" + label);
+ // Note: Strictly, vCard 2.1 does not allow "X-" attribute without
+ // "TYPE=" string.
+ attributeList.add("X-" + label);
} else {
// Just ignore the custom type.
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+ attributeList.add(Constants.ATTR_TYPE_VOICE);
}
break;
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
default:
- appendUncommonPhoneType(builder, typeAsPrimitive);
break;
}
+ if (isPrimary) {
+ attributeList.add(Constants.ATTR_TYPE_PREF);
+ }
+
+ if (attributeList.isEmpty()) {
+ appendUncommonPhoneType(builder, typeAsPrimitive);
+ } else {
+ appendTypeAttributes(builder, attributeList);
+ }
+
builder.append(VCARD_DATA_SEPARATOR);
builder.append(encodedData);
builder.append(VCARD_COL_SEPARATOR);
@@ -1823,15 +1963,43 @@ public class VCardComposer {
}
}
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName, final String rawData) {
+ appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawData);
+ }
+
+ private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+ final String propertyName,
+ final List<String> attributeList, final String rawData) {
+ final boolean needCharset =
+ (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawData));
+ final boolean reallyUseQuotedPrintable =
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData);
+ appendVCardLine(builder, propertyName, attributeList,
+ rawData, needCharset, reallyUseQuotedPrintable);
+ }
+
private void appendVCardLine(final StringBuilder builder,
final String propertyName, final String rawData) {
appendVCardLine(builder, propertyName, rawData, false, false);
}
private void appendVCardLine(final StringBuilder builder,
- final String field, final String rawData, final boolean needCharset,
+ final String propertyName, final String rawData, final boolean needCharset,
+ boolean needQuotedPrintable) {
+ appendVCardLine(builder, propertyName, null, rawData, needCharset, needQuotedPrintable);
+ }
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName,
+ final List<String> attributeList,
+ final String rawData, final boolean needCharset,
boolean needQuotedPrintable) {
- builder.append(field);
+ builder.append(propertyName);
+ if (attributeList != null && attributeList.size() > 0) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ appendTypeAttributes(builder, attributeList);
+ }
if (needCharset) {
builder.append(VCARD_ATTR_SEPARATOR);
builder.append(mVCardAttributeCharset);
@@ -1853,6 +2021,9 @@ public class VCardComposer {
builder.append(VCARD_COL_SEPARATOR);
}
+ /**
+ * VCARD_ATTR_SEPARATOR must be appended before this method being called.
+ */
private void appendTypeAttributes(final StringBuilder builder,
final List<String> types) {
// We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
@@ -1868,9 +2039,15 @@ public class VCardComposer {
}
}
+ /**
+ * VCARD_ATTR_SEPARATOR must be appended before this method being called.
+ */
private void appendTypeAttribute(final StringBuilder builder, final String type) {
+ // Refrain from using appendType() so that "TYPE=" is not be appended when the
+ // device is DoCoMo's (just for safety).
+ //
// Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
- if (mIsV30) {
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL);
}
builder.append(type);
@@ -1961,4 +2138,116 @@ public class VCardComposer {
return tmpBuilder.toString();
}
+
+ //// The methods bellow are for call log history ////
+
+ /**
+ * This static function is to compose vCard for phone own number
+ */
+ public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+ String phoneNumber, boolean vcardVer21) {
+ final StringBuilder builder = new StringBuilder();
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (!vcardVer21) {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+ } else {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+ }
+
+ boolean needCharset = false;
+ if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+ needCharset = true;
+ }
+ appendVCardLine(builder, Constants.PROPERTY_FN, phoneName, needCharset, false);
+ appendVCardLine(builder, Constants.PROPERTY_N, phoneName, needCharset, false);
+
+ String label = Integer.toString(phonetype);
+ appendVCardTelephoneLine(builder, phonetype, label, phoneNumber, false);
+
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+
+ return builder.toString();
+ }
+
+ /**
+ * Format according to RFC 2445 DATETIME type.
+ * The format is: ("%Y%m%dT%H%M%SZ").
+ */
+ private final String toRfc2455Format(final long millSecs) {
+ Time startDate = new Time();
+ startDate.set(millSecs);
+ String date = startDate.format2445();
+ return date + FLAG_TIMEZONE_UTC;
+ }
+
+ /**
+ * Try to append the property line for a call history time stamp field if possible.
+ * Do nothing if the call log type gotton from the database is invalid.
+ */
+ private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
+ // Extension for call history as defined in
+ // in the Specification for Ic Mobile Communcation - ver 1.1,
+ // Oct 2000. This is used to send the details of the call
+ // history - missed, incoming, outgoing along with date and time
+ // to the requesting device (For example, transferring phone book
+ // when connected over bluetooth)
+ //
+ // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
+ final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
+ final String callLogTypeStr;
+ switch (callLogType) {
+ case Calls.INCOMING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
+ break;
+ }
+ case Calls.OUTGOING_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
+ break;
+ }
+ case Calls.MISSED_TYPE: {
+ callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
+ break;
+ }
+ default: {
+ Log.w(LOG_TAG, "Call log type not correct.");
+ return;
+ }
+ }
+
+ final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+ builder.append(VCARD_PROPERTY_X_TIMESTAMP);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ appendTypeAttribute(builder, callLogTypeStr);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(toRfc2455Format(dateAsLong));
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ private String createOneCallLogEntryInternal() {
+ final StringBuilder builder = new StringBuilder();
+ appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+ } else {
+ appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+ }
+ String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+ if (TextUtils.isEmpty(name)) {
+ name = mCursor.getString(NUMBER_COLUMN_INDEX);
+ }
+ final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+ appendVCardLine(builder, Constants.PROPERTY_FN, name, needCharset, false);
+ appendVCardLine(builder, Constants.PROPERTY_N, name, needCharset, false);
+
+ String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+ int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+ if (TextUtils.isEmpty(label)) {
+ label = Integer.toString(type);
+ }
+ appendVCardTelephoneLine(builder, type, label, number, false);
+ tryAppendCallHistoryTimeStampField(builder);
+ appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+ return builder.toString();
+ }
}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..9581c74 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,14 +15,20 @@
*/
package android.pim.vcard;
+import android.util.Log;
+
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* The class representing VCard related configurations. Useful static methods are not in this class
* but in VCardUtils.
*/
public class VCardConfig {
+ private static final String LOG_TAG = "vcard.VCardConfig";
+
// TODO: may be better to make the instance of this available and stop using static methods and
// one integer.
@@ -41,8 +47,8 @@ public class VCardConfig {
// TODO: make the other codes use this flag
public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
- private static final int FLAG_V21 = 0;
- private static final int FLAG_V30 = 1;
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
@@ -95,96 +101,187 @@ public class VCardConfig {
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * The flag indicating the vCard composer use Quoted-Printable toward even "primary" types.
- * In this context, "primary" types means "N", "FN", etc. which are usually "not" encoded
- * into Quoted-Printable format in external exporters.
- * This flag is useful when some target importer does not accept "primary" property values
- * without Quoted-Printable encoding.
+ * <P>
+ * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
+ * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
+ * </P>
+ * <P>
+ * We actually cannot define what is the "primary" property. Note that this is NOT defined
+ * in vCard specification either. Also be aware that it is NOT related to "primary" notion
+ * used in {@link android.provider.ContactsContract}.
+ * This notion is just for vCard composition in Android.
+ * </P>
+ * <P>
+ * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
+ * do NOT use Quoted-Printable encoding toward some properties like "N", "FN", etc. even when
+ * their values contain non-ascii or/and CR/LF, while they use the encoding in the other
+ * properties like "ADR", "ORG", etc.
+ * <P>
+ * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
+ * not used in such fields.
+ * </P>
+ * <P>
+ * This flag is useful when some target importer you are going to focus on does not accept
+ * such "primary" property values with Quoted-Printable encoding.
+ * </P>
+ * <P>
+ * Again, we should not use this flag at all for complying vCard 2.1 spec.
+ * </P>
+ * <P>
+ * We will change the behavior around this flag in the future, after understanding the other
+ * real vCard cases around this problem. Please use this flag with extreme caution even when
+ * needed.
+ * </P>
+ * <P>
+ * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+ * kind of problem (hopefully).
+ * </P>
+ */
+ public static final int FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
+
+ /**
+ * <P>
+ * The flag indicating that phonetic name related fields must be converted to
+ * appropriate form. Note that "appropriate" is not defined in any vCard specification.
+ * This is Android-specific.
+ * </P>
+ * <P>
+ * One typical (and currently sole) example where we need this flag is the time when
+ * we need to emit Japanese phonetic names into vCard entries. The property values
+ * should be encoded into half-width katakana when the target importer is Japanese mobile
+ * phones', which are probably not able to parse full-width hiragana/katakana for
+ * historical reasons, while the vCard importers embedded to softwares for PC should be
+ * able to parse them as we expect.
+ * </P>
+ */
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+
+ /**
+ * <P>
+ * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string every time
+ * possible. The default behavior does not emit it and is valid, while adding "TYPE="
+ * is also valid. In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in
+ * vCard 3.0 specification.
+ *
+ * If you are targeting to some importer which cannot accept type attributes (params)
+ * without "TYPE=" string (which should be rare though), please use this flag.
*
- * @hide Temporaly made public. We don't strictly define "primary", so we may change the
- * behavior around this flag in the future. Do not use this flag without any reason.
+ * XXX: Really rare?
+ *
+ * e.g. int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
*/
- public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-
- // VCard types
+ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+ //// The followings are VCard types available from importer/exporter. ////
/**
+ * <P>
* General vCard format with the version 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used.
- *
+ * When composing a vCard entry, the US convension will be used toward formatting
+ * some values
+ * </P>
+ * <P>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
- * while in Japan, it should be "Prefix Family Middle Given Suffix".
+ * while it should be "Prefix Family Middle Given Suffix" in Japan.
+ * </P>
*/
- public static final int VCARD_TYPE_V21_GENERIC =
+ public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
(FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
/**
+ * <P>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- *
- * Note that this type is not fully implemented, so probably some bugs remain both in
- * parsing and composing.
- *
- * TODO: implement this type correctly.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_GENERIC =
+ public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
/**
- * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+ * <P>
+ * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+ * </P>
*/
- public static final int VCARD_TYPE_V21_EUROPE =
+ public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
(FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
/**
+ * <P>
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_EUROPE =
+ public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
(FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-
- /**
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- */
- public static final int VCARD_TYPE_V21_JAPANESE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
-
/**
- * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+
+ /**
+ * <P>
+ * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+ * parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
/**
+ * <P>
* vCard format for miscellaneous Japanese devices, using Shift_Jis for
* parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_JAPANESE =
+ public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
/**
- * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
@@ -193,38 +290,72 @@ public class VCardConfig {
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
/**
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
- * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
- * No Android-specific property nor defact property is included.
+ * <P>
+ * The vCard 2.1 based format which (partially) considers the convention in Japanese
+ * mobile phones, where phonetic names are translated to half-width katakana if
+ * possible, etc.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS |
+ FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES);
+
+ public static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+ /**
+ * <P>
+ * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * </p>
+ * <P>
+ * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+ * No Android-specific property nor defact property is included. The "Primary" properties
+ * are NOT encoded to Quoted-Printable.
+ * </P>
*/
public static final int VCARD_TYPE_DOCOMO =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+ (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
- private static final Map<String, Integer> VCARD_TYPES_MAP;
+ private static final Map<String, Integer> sVCardTypeMap;
+ private static final Set<Integer> sJapaneseMobileTypeSet;
static {
- VCARD_TYPES_MAP = new HashMap<String, Integer>();
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+ sVCardTypeMap = new HashMap<String, Integer>();
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+
+ sJapaneseMobileTypeSet = new HashSet<Integer>();
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
public static int getVCardTypeFromString(String vcardTypeString) {
String loweredKey = vcardTypeString.toLowerCase();
- if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
- return VCARD_TYPES_MAP.get(loweredKey);
+ if (sVCardTypeMap.containsKey(loweredKey)) {
+ return sVCardTypeMap.get(loweredKey);
} else {
// XXX: should return the value indicating the input is invalid?
+ Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
return VCARD_TYPE_DEFAULT;
}
}
@@ -237,22 +368,6 @@ public class VCardConfig {
return !isV30(vcardType);
}
- public static boolean isDoCoMo(int vcardType) {
- return ((vcardType & FLAG_DOCOMO) != 0);
- }
-
- /**
- * @return true if the device is Japanese and some Japanese convension is
- * applied to creating "formatted" something like FORMATTED_ADDRESS.
- */
- public static boolean isJapaneseDevice(int vcardType) {
- return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
- (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_DOCOMO));
- }
-
public static boolean usesUtf8(int vcardType) {
return ((vcardType & FLAG_CHARSET_UTF8) != 0);
}
@@ -261,17 +376,6 @@ public class VCardConfig {
return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
}
- /**
- * @return true when Japanese phonetic string must be converted to a string
- * containing only half-width katakana. This method exists since Japanese mobile
- * phones usually use only half-width katakana for expressing phonetic names and
- * some devices are not ready for parsing other phonetic strings like hiragana and
- * full-width katakana.
- */
- public static boolean needsToConvertPhoneticString(int vcardType) {
- return (vcardType == VCARD_TYPE_DOCOMO);
- }
-
public static int getNameOrderType(int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
@@ -284,20 +388,37 @@ public class VCardConfig {
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
}
- public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
- return vcardType == VCARD_TYPE_DOCOMO;
- }
-
public static boolean showPerformanceLog() {
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
+ public static boolean refrainsQPToPrimaryProperties(int vcardType) {
+ return (!usesQuotedPrintable(vcardType) ||
+ ((vcardType & FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES) != 0));
+ }
+
+ public static boolean appendTypeParamName(int vcardType) {
+ return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ }
+
/**
- * @hide
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
*/
- public static boolean usesQPToPrimaryProperties(int vcardType) {
- return (usesQuotedPrintable(vcardType) &&
- ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0));
+ public static boolean isJapaneseDevice(int vcardType) {
+ return sJapaneseMobileTypeSet.contains(vcardType);
+ }
+
+ public static boolean needsToConvertPhoneticString(int vcardType) {
+ return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
+ public static boolean isDoCoMo(int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
}
private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java
index d2026d0..76ad482 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardDataBuilder.java
@@ -69,7 +69,7 @@ public class VCardDataBuilder implements VCardBuilder {
private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
public VCardDataBuilder() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+ this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
}
/**
@@ -86,7 +86,7 @@ public class VCardDataBuilder implements VCardBuilder {
boolean strictLineBreakParsing, int vcardType, Account account) {
this(null, charset, strictLineBreakParsing, vcardType, account);
}
-
+
/**
* @hide
*/
@@ -127,6 +127,18 @@ public class VCardDataBuilder implements VCardBuilder {
}
/**
+ * Called when the parse failed between startRecord() and endRecord().
+ * Currently it happens only when the vCard format is 3.0.
+ * (VCardVersionException is thrown by VCardParser_V21 and this object is reused by
+ * VCardParser_V30. At that time, startRecord() is called twice before endRecord() is called.)
+ * TODO: Should this be in VCardBuilder interface?
+ */
+ public void clear() {
+ mCurrentContactStruct = null;
+ mCurrentProperty = new ContactStruct.Property();
+ }
+
+ /**
* Assume that VCard is not nested. In other words, this code does not accept
*/
public void startRecord(String type) {
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..b3ff8fa 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -144,10 +144,14 @@ public class VCardParser_V21 extends VCardParser {
}
}
- protected String getVersion() {
- return "2.1";
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
}
-
+
+ protected String getVersionString() {
+ return Constants.VERSION_V21;
+ }
+
/**
* @return true when the propertyName is a valid property name.
*/
@@ -356,7 +360,7 @@ public class VCardParser_V21 extends VCardParser {
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
@@ -392,9 +396,10 @@ public class VCardParser_V21 extends VCardParser {
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
+ propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
@@ -520,11 +525,19 @@ public class VCardParser_V21 extends VCardParser {
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
- handleType(strArray[0]);
+ handleParamWithoutName(strArray[0]);
}
}
/**
+ * vCard 3.0 parser may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /**
* ptypeval = knowntype / "X-" word
*/
protected void handleType(String ptypeval) {
@@ -761,32 +774,11 @@ public class VCardParser_V21 extends VCardParser {
}
if (mBuilder != null) {
- 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 = maybeUnescapeCharacter(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);
+ mBuilder.propertyValues(VCardUtils.constructListFromValue(
+ propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
-
+
/**
* vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
*
@@ -819,12 +811,16 @@ public class VCardParser_V21 extends VCardParser {
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 maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(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.
@@ -844,6 +840,9 @@ public class VCardParser_V21 extends VCardParser {
@Override
public boolean parse(InputStream is, String charset, VCardBuilder builder)
throws IOException, VCardException {
+ if (charset == null) {
+ charset = VCardConfig.DEFAULT_CHARSET;
+ }
final InputStreamReader tmpReader = new InputStreamReader(is, charset);
if (VCardConfig.showPerformanceLog()) {
mReader = new CustomBufferedReader(tmpReader);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..86e7625 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -46,14 +46,42 @@ public class VCardParser_V30 extends VCardParser_V21 {
private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
private String mPreviousLine;
-
+
private boolean mEmittedAgentWarning = false;
-
+
+ /**
+ * True when the caller wants the parser to be strict about the input.
+ * Currently this is only for testing.
+ */
+ private final boolean mStrictParsing;
+
+ public VCardParser_V30() {
+ super();
+ mStrictParsing = false;
+ }
+
+ /**
+ * @param strictParsing when true, this object throws VCardException when the vcard is not
+ * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
+ * is not fully yet for being used with this flag and may not notice invalid line(s).
+ *
+ * @hide currently only for testing!
+ */
+ public VCardParser_V30(boolean strictParsing) {
+ super();
+ mStrictParsing = strictParsing;
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
+ }
+
@Override
- protected String getVersion() {
+ protected String getVersionString() {
return Constants.VERSION_V30;
}
-
+
@Override
protected boolean isValidPropertyName(String propertyName) {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
@@ -199,7 +227,16 @@ public class VCardParser_V30 extends VCardParser_V21 {
// TODO: fix this.
super.handleAnyParam(paramName, paramValue);
}
-
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ if (mStrictParsing) {
+ throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
+ } else {
+ super.handleParamWithoutName(paramValue);
+ }
+ }
+
/**
* vCard 3.0 defines
*
@@ -284,6 +321,10 @@ public class VCardParser_V30 extends VCardParser_V21 {
*/
@Override
protected String maybeUnescapeText(String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(String text) {
StringBuilder builder = new StringBuilder();
int length = text.length();
for (int i = 0; i < length; i++) {
@@ -299,15 +340,19 @@ public class VCardParser_V30 extends VCardParser_V21 {
builder.append(ch);
}
}
- return builder.toString();
+ return builder.toString();
}
@Override
protected String maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
- }
+ }
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..376327c 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -18,13 +18,16 @@ package android.pim.vcard;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.text.TextUtils;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,44 +44,56 @@ public class VCardUtils {
// vCard and current (as of 2009-08-07) Contacts structure.
private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
private static final Set<String> sPhoneTypesSetUnknownToContacts;
-
- private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
-
+
+ private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+
+ private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+
static {
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
- sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
+ sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
- sKnownPhoneTypesMap_StoI.put(
- Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_CALLBACK, Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(
+ Constants.ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_ASSISTANT,
+ Phone.TYPE_ASSISTANT);
sPhoneTypesSetUnknownToContacts = new HashSet<String>();
sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+
+ sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK, Constants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, Constants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, Constants.PROPERTY_X_NETMEETING);
}
-
+
public static String getPhoneAttributeString(Integer type) {
return sKnownPhoneTypesMap_ItoS.get(type);
}
-
+
/**
* Returns Interger when the given types can be parsed as known type. Returns String object
* when not, which should be set to label.
@@ -100,7 +115,7 @@ public class VCardUtils {
if (typeString.startsWith("X-") && type < 0) {
typeString = typeString.substring(2);
}
- Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+ Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
if (tmp != null) {
type = tmp;
} else if (type < 0) {
@@ -133,7 +148,11 @@ public class VCardUtils {
return type;
}
}
-
+
+ public static String getPropertyNameForIm(int protocol) {
+ return sKnownImPropNameMap_ItoS.get(protocol);
+ }
+
public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
// TODO: check the following.
// - it may violate vCard spec
@@ -187,7 +206,10 @@ public class VCardUtils {
}
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
- // Extended address is dropped since there's no relevant entry in ContactsContract.
+ // TODO: Japanese phone seems to use this field for expressing all the address including
+ // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
+ // better than dropping them all.
+ builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
builder.withValue(StructuredPostal.STREET, postalData.street);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
@@ -200,12 +222,12 @@ public class VCardUtils {
builder.withValue(Data.IS_PRIMARY, 1);
}
}
-
+
/**
* Returns String[] containing address information based on vCard spec
* (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
* All String objects are non-null ("" is used when the relevant data is empty).
- *
+ *
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method. See also
* {{@link #insertStructuredPostalDataUsingContactsStruct(int,
@@ -213,13 +235,20 @@ public class VCardUtils {
* android.pim.vcard.ContactStruct.PostalData)}
*/
public static String[] getVCardPostalElements(ContentValues contentValues) {
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
String[] dataArray = new String[7];
dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
if (dataArray[0] == null) {
dataArray[0] = "";
}
- // Extended addr. There's no relevant data in ContactsContract.
- dataArray[1] = "";
+ // We keep all the data in StructuredPostal, presuming NEIGHBORHOOD is
+ // similar to "Extended Address".
+ dataArray[1] = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+ if (dataArray[1] == null) {
+ dataArray[1] = "";
+ }
dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
if (dataArray[2] == null) {
dataArray[2] = "";
@@ -282,7 +311,36 @@ public class VCardUtils {
}
return builder.toString();
}
-
+
+ public static List<String> constructListFromValue(final String value,
+ final boolean isV30) {
+ final List<String> list = new ArrayList<String>();
+ 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);
+ final String unescapedString =
+ (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+ VCardParser_V21.unescapeCharacter(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());
+ return list;
+ }
+
public static boolean containsOnlyPrintableAscii(String str) {
if (TextUtils.isEmpty(str)) {
return true;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference);
- if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
- a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
- // This preference has a custom layout defined (not one taken from
- // the default style)
- mHasSpecifiedLayout = true;
- }
- a.recycle();
-
- a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
- defStyle, 0);
+ com.android.internal.R.styleable.Preference, defStyle, 0);
for (int i = a.getIndexCount(); i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
@@ -252,6 +242,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
}
a.recycle();
+
+ if (!getClass().getName().startsWith("android.preference")) {
+ // For subclasses not in this package, assume the worst and don't cache views
+ mHasSpecifiedLayout = true;
+ }
}
/**
@@ -332,11 +327,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setWidgetLayoutResource(int)
*/
public void setLayoutResource(int layoutResId) {
-
- if (!mHasSpecifiedLayout) {
+ if (layoutResId != mLayoutResId) {
+ // Layout changed
mHasSpecifiedLayout = true;
}
-
+
mLayoutResId = layoutResId;
}
@@ -360,6 +355,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setLayoutResource(int)
*/
public void setWidgetLayoutResource(int widgetLayoutResId) {
+ if (widgetLayoutResId != mWidgetLayoutResId) {
+ // Layout changed
+ mHasSpecifiedLayout = true;
+ }
mWidgetLayoutResId = widgetLayoutResId;
}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
* count once--when the adapter is being set). We will not recycle views for
* Preference subclasses seen after the count has been returned.
*/
- private List<String> mPreferenceClassNames;
+ private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+ private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
/**
* Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
};
+ private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+ private int resId;
+ private int widgetResId;
+ private String name;
+
+ public int compareTo(PreferenceLayout other) {
+ int compareNames = name.compareTo(other.name);
+ if (compareNames == 0) {
+ if (resId == other.resId) {
+ if (widgetResId == other.widgetResId) {
+ return 0;
+ } else {
+ return widgetResId - other.widgetResId;
+ }
+ } else {
+ return resId - other.resId;
+ }
+ } else {
+ return compareNames;
+ }
+ }
+ }
+
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
// If this group gets or loses any children, let us know
mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-
+
mPreferenceList = new ArrayList<Preference>();
- mPreferenceClassNames = new ArrayList<String>();
-
+ mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
syncMyPreferences();
}
@@ -102,7 +127,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
if (mIsSyncing) {
return;
}
-
+
mIsSyncing = true;
}
@@ -128,7 +153,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
preferences.add(preference);
- if (!mHasReturnedViewTypeCount) {
+ if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
addPreferenceClassName(preference);
}
@@ -143,15 +168,28 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
}
+ /**
+ * Creates a string that includes the preference name, layout id and widget layout id.
+ * If a particular preference type uses 2 different resources, they will be treated as
+ * different view types.
+ */
+ private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+ PreferenceLayout pl = in != null? in : new PreferenceLayout();
+ pl.name = preference.getClass().getName();
+ pl.resId = preference.getLayoutResource();
+ pl.widgetResId = preference.getWidgetLayoutResource();
+ return pl;
+ }
+
private void addPreferenceClassName(Preference preference) {
- final String name = preference.getClass().getName();
- int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-
+ final PreferenceLayout pl = createPreferenceLayout(preference, null);
+ int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
// Only insert if it doesn't exist (when it is negative).
if (insertPos < 0) {
// Convert to insert index
insertPos = insertPos * -1 - 1;
- mPreferenceClassNames.add(insertPos, name);
+ mPreferenceLayouts.add(insertPos, pl);
}
}
@@ -171,19 +209,15 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
public View getView(int position, View convertView, ViewGroup parent) {
final Preference preference = this.getItem(position);
-
- if (preference.hasSpecifiedLayout()) {
- // If the preference had specified a layout (as opposed to the
- // default), don't use convert views.
+ // Build a PreferenceLayout to compare with known ones that are cacheable.
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ // If it's not one of the cached ones, set the convertView to null so that
+ // the layout gets re-created by the Preference.
+ if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
convertView = null;
- } else {
- // TODO: better way of doing this
- final String name = preference.getClass().getName();
- if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
- convertView = null;
- }
}
-
+
return preference.getView(convertView, parent);
}
@@ -225,8 +259,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
return IGNORE_ITEM_VIEW_TYPE;
}
- final String name = preference.getClass().getName();
- int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
if (viewType < 0) {
// This is a class that was seen after we returned the count, so
// don't recycle it.
@@ -242,7 +277,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
mHasReturnedViewTypeCount = true;
}
- return Math.max(1, mPreferenceClassNames.size());
+ return Math.max(1, mPreferenceLayouts.size());
}
}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..faa63af 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -514,6 +514,12 @@ public final class Calendar {
* <P>Type: String</P>
*/
public static final String OWNER_ACCOUNT = "ownerAccount";
+
+ /**
+ * Whether the row has been deleted. A deleted row should be ignored.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
}
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..cd71682 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -238,6 +238,7 @@ public final class MediaStore {
private static final int FULL_SCREEN_KIND = 2;
private static final int MICRO_KIND = 3;
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+ static final int DEFAULT_GROUP_ID = 0;
/**
* This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +247,14 @@ public final class MediaStore {
*
* @param cr ContentResolver
* @param origId original image or video id. use -1 to cancel all requests.
+ * @param groupId the same groupId used in getThumbnail
* @param baseUri the base URI of requested thumbnails
*/
- static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+ long groupId) {
Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
Cursor c = null;
try {
c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,9 +275,10 @@ public final class MediaStore {
* @param kind could be MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @param baseUri the base URI of requested thumbnails
+ * @param groupId the id of group to which this request belongs
* @return Bitmap bitmap of specified thumbnail kind
*/
- static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
Bitmap bitmap = null;
String filePath = null;
@@ -297,7 +302,8 @@ public final class MediaStore {
Cursor c = null;
try {
Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
c = cr.query(blockingUri, PROJECTION, null, null, null);
// This happens when original image/video doesn't exist.
if (c == null) return null;
@@ -354,7 +360,7 @@ public final class MediaStore {
}
if (isVideo) {
bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
- if (kind == MICRO_KIND) {
+ if (kind == MICRO_KIND && bitmap != null) {
bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
@@ -669,7 +675,8 @@ public final class MediaStore {
* @param origId original image id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
}
/**
@@ -685,7 +692,39 @@ public final class MediaStore {
*/
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, false);
+ }
+
+ /**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original image id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, false);
}
@@ -1598,7 +1637,26 @@ public final class MediaStore {
* @param origId original video id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, true);
}
/**
@@ -1607,18 +1665,32 @@ public final class MediaStore {
*
* @param cr ContentResolver used to dispatch queries to MediaProvider.
* @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @return A Bitmap instance. It could be null if the original image associated with
* origId doesn't exist or memory is not enough.
*/
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
- BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, true);
}
/**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original video id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
* Get the content:// style URI for the image media table on the
* given volume.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb3dc16..456181c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3110,6 +3110,13 @@ public final class Settings {
= "google_login_generic_auth_service";
/**
+ * Duration in milliseconds after setup at which market does not reconcile applications
+ * which are installed during restore.
+ */
+ public static final String VENDING_RESTORE_WINDOW_MS = "vending_restore_window_ms";
+
+
+ /**
* Frequency in milliseconds at which we should sync the locally installed Vending Machine
* content with the server.
*/
@@ -3623,7 +3630,6 @@ public final class Settings {
*/
public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
"search_per_source_concurrent_query_limit";
-
/**
* Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
* on application crashes and ANRs. If this is disabled, the crash/ANR dialog
@@ -3638,6 +3644,32 @@ public final class Settings {
public static final String LAST_KMSG_KB = "last_kmsg_kb";
/**
+ * Maximum age of entries kept by {@link android.os.IDropBox}.
+ */
+ public static final String DROPBOX_AGE_SECONDS =
+ "dropbox_age_seconds";
+ /**
+ * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ */
+ public static final String DROPBOX_QUOTA_KB =
+ "dropbox_quota_kb";
+ /**
+ * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ */
+ public static final String DROPBOX_QUOTA_PERCENT =
+ "dropbox_quota_percent";
+ /**
+ * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ */
+ public static final String DROPBOX_RESERVE_PERCENT =
+ "dropbox_reserve_percent";
+ /**
+ * Prefix for per-tag dropbox disable/enable settings.
+ */
+ public static final String DROPBOX_TAG_PREFIX =
+ "dropbox:";
+
+ /**
* @deprecated
* @hide
*/
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 9a2d6d9..ec3b2ff 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -72,8 +72,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private final AudioManager mAudioManager;
private final BluetoothService mBluetoothService;
private final BluetoothAdapter mAdapter;
- private boolean mSuspending;
- private boolean mResuming;
+ private int mTargetA2dpState;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -151,8 +150,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (mBluetoothService.isEnabled())
onBluetoothEnable();
- mSuspending = false;
- mResuming = false;
+ mTargetA2dpState = -1;
}
@Override
@@ -341,10 +339,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
public synchronized boolean suspendSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- if (DBG) log("suspendSink(" + device + "), mSuspending: "+mSuspending+", mResuming: "+mResuming);
- if (mSuspending) {
- return true;
- }
+ if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
if (device == null || mAudioDevices == null) {
return false;
}
@@ -353,28 +348,15 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (path == null || state == null) {
return false;
}
- switch (state.intValue()) {
- case BluetoothA2dp.STATE_CONNECTED:
- if (mResuming) {
- mSuspending = true;
- }
- return true;
- case BluetoothA2dp.STATE_PLAYING:
- mAudioManager.setParameters("A2dpSuspended=true");
- mSuspending = suspendSinkNative(path);
- return mSuspending;
- default:
- return false;
- }
+
+ mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
+ return checkSinkSuspendState(state.intValue());
}
public synchronized boolean resumeSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- if (DBG) log("resumeSink(" + device + "), mResuming: "+mResuming+", mSuspending: "+mSuspending);
- if (mResuming) {
- return true;
- }
+ if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
if (device == null || mAudioDevices == null) {
return false;
}
@@ -383,19 +365,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (path == null || state == null) {
return false;
}
- switch (state.intValue()) {
- case BluetoothA2dp.STATE_PLAYING:
- if (mSuspending) {
- mResuming = true;
- }
- return true;
- case BluetoothA2dp.STATE_CONNECTED:
- mResuming = resumeSinkNative(path);
- mAudioManager.setParameters("A2dpSuspended=false");
- return mResuming;
- default:
- return false;
- }
+ mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
+ return checkSinkSuspendState(state.intValue());
}
public synchronized BluetoothDevice[] getConnectedSinks() {
@@ -458,10 +429,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
- if (state == BluetoothA2dp.STATE_DISCONNECTED) {
- mSuspending = false;
- mResuming = false;
- }
if (state != prevState) {
if (state == BluetoothA2dp.STATE_DISCONNECTED ||
state == BluetoothA2dp.STATE_DISCONNECTING) {
@@ -477,28 +444,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
mAudioDevices.put(device, state);
- if (state == BluetoothA2dp.STATE_CONNECTED && prevState == BluetoothA2dp.STATE_PLAYING) {
- if (DBG) log("handleSinkStateChange() STATE_PLAYING -> STATE_CONNECTED: mSuspending: "
- +mSuspending+", mResuming: "+mResuming);
- if (mSuspending) {
- mSuspending = false;
- if (mResuming) {
- mResuming = false;
- resumeSink(device);
- }
- }
- }
- if (state == BluetoothA2dp.STATE_PLAYING && prevState == BluetoothA2dp.STATE_CONNECTED) {
- if (DBG) log("handleSinkStateChange() STATE_CONNECTED -> STATE_PLAYING: mSuspending: "
- +mSuspending+", mResuming: "+mResuming);
-
- if (mResuming) {
- mResuming = false;
- if (mSuspending) {
- mSuspending = false;
- suspendSink(device);
- }
- }
+ checkSinkSuspendState(state);
+ mTargetA2dpState = -1;
+
+ if (state == BluetoothA2dp.STATE_CONNECTING) {
+ mAudioManager.setParameters("A2dpSuspended=false");
}
Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
@@ -527,6 +477,23 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return sinks;
}
+ private boolean checkSinkSuspendState(int state) {
+ boolean result = true;
+
+ if (state != mTargetA2dpState) {
+ if (state == BluetoothA2dp.STATE_PLAYING &&
+ mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
+ mAudioManager.setParameters("A2dpSuspended=true");
+ } else if (state == BluetoothA2dp.STATE_CONNECTED &&
+ mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
+ mAudioManager.setParameters("A2dpSuspended=false");
+ } else {
+ result = false;
+ }
+ }
+ return result;
+ }
+
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mAudioDevices.isEmpty()) return;
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 67b30a9..0db29a4 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -61,7 +61,7 @@ import java.util.Map;
public class BluetoothService extends IBluetooth.Stub {
private static final String TAG = "BluetoothService";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private int mNativeData;
private BluetoothEventLoop mEventLoop;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a92800d..afc6864 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -294,7 +294,12 @@ public abstract class Layout {
lbaseline, lbottom, buf,
start, end, par, this);
- left += margin.getLeadingMargin(par);
+ boolean useMargin = par;
+ if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
+ useMargin = count > i;
+ }
+ left += margin.getLeadingMargin(useMargin);
}
}
}
@@ -1293,7 +1298,13 @@ public abstract class Layout {
LeadingMarginSpan.class);
for (int i = 0; i < spans.length; i++) {
- left += spans[i].getLeadingMargin(par);
+ boolean margin = par;
+ LeadingMarginSpan span = spans[i];
+ if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
+ margin = count >= line;
+ }
+ left += span.getLeadingMargin(margin);
}
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f0a5ffd..fbf1261 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -161,6 +161,7 @@ extends Layout
else
end++;
+ int firstWidthLineCount = 1;
int firstwidth = outerwidth;
int restwidth = outerwidth;
@@ -171,8 +172,12 @@ extends Layout
sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
+ if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ }
}
chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
@@ -750,7 +755,9 @@ extends Layout
fitascent = fitdescent = fittop = fitbottom = 0;
okascent = okdescent = oktop = okbottom = 0;
- width = restwidth;
+ if (--firstWidthLineCount <= 0) {
+ width = restwidth;
+ }
}
}
}
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 8e212e3..cb55329 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -33,6 +33,11 @@ extends ParagraphStyle
CharSequence text, int start, int end,
boolean first, Layout layout);
+
+ public interface LeadingMarginSpan2 extends LeadingMarginSpan, WrapTogetherSpan {
+ public int getLeadingMarginLineCount();
+ };
+
public static class Standard implements LeadingMarginSpan, ParcelableSpan {
private final int mFirst, mRest;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 3b83044..5c2b3c9 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -68,6 +68,7 @@ public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
+ private static final boolean SHOW_FPS = false;
@SuppressWarnings({"ConstantConditionalExpression"})
private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
/** @noinspection PointlessBooleanExpression*/
@@ -1244,7 +1245,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@@ -1356,7 +1357,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9456ae1..d1db35e 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -19,17 +19,21 @@ package android.webkit;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.AssetManager;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ParseException;
+import android.net.Uri;
import android.net.WebAddress;
import android.net.http.SslCertificate;
import android.os.Handler;
import android.os.Message;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.TypedValue;
import junit.framework.Assert;
+import java.io.InputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@@ -463,6 +467,63 @@ class BrowserFrame extends Handler {
}
/**
+ * Called by JNI. Given a URI, find the associated file and return its size
+ * @param uri A String representing the URI of the desired file.
+ * @return int The size of the given file.
+ */
+ private int getFileSize(String uri) {
+ int size = 0;
+ Cursor cursor = mContext.getContentResolver().query(Uri.parse(uri),
+ new String[] { OpenableColumns.SIZE },
+ null,
+ null,
+ null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ size = cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
+ * copy the resource into buffer.
+ * @param uri A String representing the URI of the desired file.
+ * @param buffer The byte array to copy the data into.
+ * @param offset The offet into buffer to place the data.
+ * @param expectSize The size that the buffer has allocated for this file.
+ * @return int The size of the given file, or zero if it fails.
+ */
+ private int getFile(String uri, byte[] buffer, int offset,
+ int expectedSize) {
+ int size = 0;
+ try {
+ InputStream stream = mContext.getContentResolver()
+ .openInputStream(Uri.parse(uri));
+ size = stream.available();
+ if (size <= expectedSize && buffer != null
+ && buffer.length - offset >= size) {
+ stream.read(buffer, offset, size);
+ } else {
+ size = 0;
+ }
+ stream.close();
+ } catch (java.io.FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException:" + e);
+ size = 0;
+ } catch (java.io.IOException e2) {
+ Log.e(LOGTAG, "IOException: " + e2);
+ size = 0;
+ }
+ return size;
+ }
+
+ /**
* Start loading a resource.
* @param loaderHandle The native ResourceLoader that is the target of the
* data.
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 145411c..334526b 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -16,6 +16,8 @@
package android.webkit;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -23,47 +25,37 @@ import java.util.ListIterator;
them back out. It does not optimize for returning the result in a
single array, though this is supported in the API. It is fastest
if the retrieval can be done via iterating through chunks.
-
- Things to add:
- - consider dynamically increasing our min_capacity,
- as we see mTotalSize increase
*/
class ByteArrayBuilder {
private static final int DEFAULT_CAPACITY = 8192;
- private LinkedList<Chunk> mChunks;
-
- /** free pool */
- private LinkedList<Chunk> mPool;
+ // Global pool of chunks to be used by other ByteArrayBuilders.
+ private static final LinkedList<SoftReference<Chunk>> sPool =
+ new LinkedList<SoftReference<Chunk>>();
+ // Reference queue for processing gc'd entries.
+ private static final ReferenceQueue<Chunk> sQueue =
+ new ReferenceQueue<Chunk>();
- private int mMinCapacity;
+ private LinkedList<Chunk> mChunks;
public ByteArrayBuilder() {
- init(0);
- }
-
- public ByteArrayBuilder(int minCapacity) {
- init(minCapacity);
- }
-
- private void init(int minCapacity) {
mChunks = new LinkedList<Chunk>();
- mPool = new LinkedList<Chunk>();
-
- if (minCapacity <= 0) {
- minCapacity = DEFAULT_CAPACITY;
- }
- mMinCapacity = minCapacity;
- }
-
- public void append(byte[] array) {
- append(array, 0, array.length);
}
public synchronized void append(byte[] array, int offset, int length) {
while (length > 0) {
- Chunk c = appendChunk(length);
+ Chunk c = null;
+ if (mChunks.isEmpty()) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ } else {
+ c = mChunks.getLast();
+ if (c.mLength == c.mArray.length) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ }
+ }
int amount = Math.min(length, c.mArray.length - c.mLength);
System.arraycopy(array, offset, c.mArray, c.mLength, amount);
c.mLength += amount;
@@ -75,7 +67,7 @@ class ByteArrayBuilder {
/**
* The fastest way to retrieve the data is to iterate through the
* chunks. This returns the first chunk. Note: this pulls the
- * chunk out of the queue. The caller must call releaseChunk() to
+ * chunk out of the queue. The caller must call Chunk.release() to
* dispose of it.
*/
public synchronized Chunk getFirstChunk() {
@@ -83,23 +75,11 @@ class ByteArrayBuilder {
return mChunks.removeFirst();
}
- /**
- * recycles chunk
- */
- public synchronized void releaseChunk(Chunk c) {
- c.mLength = 0;
- mPool.addLast(c);
- }
-
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return mChunks.isEmpty();
}
- public int size() {
- return mChunks.size();
- }
-
- public int getByteSize() {
+ public synchronized int getByteSize() {
int total = 0;
ListIterator<Chunk> it = mChunks.listIterator(0);
while (it.hasNext()) {
@@ -112,37 +92,40 @@ class ByteArrayBuilder {
public synchronized void clear() {
Chunk c = getFirstChunk();
while (c != null) {
- releaseChunk(c);
+ c.release();
c = getFirstChunk();
}
}
- private Chunk appendChunk(int length) {
- if (length < mMinCapacity) {
- length = mMinCapacity;
- }
-
- Chunk c;
- if (mChunks.isEmpty()) {
- c = obtainChunk(length);
- } else {
- c = mChunks.getLast();
- if (c.mLength == c.mArray.length) {
- c = obtainChunk(length);
+ // Must be called with lock held on sPool.
+ private void processPoolLocked() {
+ while (true) {
+ SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
+ if (entry == null) {
+ break;
}
+ sPool.remove(entry);
}
- return c;
}
private Chunk obtainChunk(int length) {
- Chunk c;
- if (mPool.isEmpty()) {
- c = new Chunk(length);
- } else {
- c = mPool.removeFirst();
+ // Correct a small length.
+ if (length < DEFAULT_CAPACITY) {
+ length = DEFAULT_CAPACITY;
+ }
+ synchronized (sPool) {
+ // Process any queued references and remove them from the pool.
+ processPoolLocked();
+ if (!sPool.isEmpty()) {
+ Chunk c = sPool.removeFirst().get();
+ // The first item may have been queued after processPoolLocked
+ // so check for null.
+ if (c != null) {
+ return c;
+ }
+ }
+ return new Chunk(length);
}
- mChunks.addLast(c);
- return c;
}
public static class Chunk {
@@ -153,5 +136,19 @@ class ByteArrayBuilder {
mArray = new byte[length];
mLength = 0;
}
+
+ /**
+ * Release the chunk and make it available for reuse.
+ */
+ public void release() {
+ mLength = 0;
+ synchronized (sPool) {
+ // Add the chunk back to the pool as a SoftReference so it can
+ // be gc'd if needed.
+ sPool.offer(new SoftReference<Chunk>(this, sQueue));
+ sPool.notifyAll();
+ }
+ }
+
}
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 8d55247..f9dec7f 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -107,6 +107,7 @@ class CallbackProxy extends Handler {
private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
private static final int RECEIVED_TOUCH_ICON_URL = 132;
private static final int GET_VISITED_HISTORY = 133;
+ private static final int OPEN_FILE_CHOOSER = 134;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
@@ -149,6 +150,16 @@ class CallbackProxy extends Handler {
}
/**
+ * Get the WebViewClient.
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ /**
* Set the WebChromeClient.
* @param client An implementation of WebChromeClient.
*/
@@ -662,6 +673,12 @@ class CallbackProxy extends Handler {
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
+
+ case OPEN_FILE_CHOOSER:
+ if (mWebChromeClient != null) {
+ mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+ }
+ break;
}
}
@@ -1348,4 +1365,40 @@ class CallbackProxy extends Handler {
msg.obj = callback;
sendMessage(msg);
}
+
+ private class UploadFile implements ValueCallback<Uri> {
+ private Uri mValue;
+ public void onReceiveValue(Uri value) {
+ mValue = value;
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+ public Uri getResult() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Called by WebViewCore to open a file chooser.
+ */
+ /* package */ Uri openFileChooser() {
+ if (mWebChromeClient == null) {
+ return null;
+ }
+ Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
+ UploadFile uploadFile = new UploadFile();
+ myMessage.obj = uploadFile;
+ synchronized (this) {
+ sendMessage(myMessage);
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG,
+ "Caught exception while waiting for openFileChooser");
+ Log.e(LOGTAG, Log.getStackTraceString(e));
+ }
+ }
+ return uploadFile.getResult();
+ }
}
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index 2f46f2b..042953c 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -50,13 +50,15 @@ public final class HttpDateTime {
* Wdy Mon DD HH:MM:SS YYYY GMT
*
* HH can be H if the first digit is zero.
+ *
+ * Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP =
- "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP =
- "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+ "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 4c17f99..5c0ce3c 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -78,7 +78,7 @@ class LoadListener extends Handler implements EventHandler {
private static int sNativeLoaderCount;
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+ private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
private String mUrl;
private WebAddress mUri;
@@ -522,17 +522,18 @@ class LoadListener extends Handler implements EventHandler {
* IMPORTANT: as this is called from network thread, can't call native
* directly
* XXX: Unlike the other network thread methods, this method can do the
- * work of decoding the data and appending it to the data builder because
- * mDataBuilder is a thread-safe structure.
+ * work of decoding the data and appending it to the data builder.
*/
public void data(byte[] data, int length) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.data(): url: " + url());
}
- // Synchronize on mData because commitLoad may write mData to WebCore
- // and we don't want to replace mData or mDataLength at the same time
- // as a write.
+ // The reason isEmpty() and append() need to synchronized together is
+ // because it is possible for getFirstChunk() to be called multiple
+ // times between isEmpty() and append(). This could cause commitLoad()
+ // to finish before processing the newly appended data and no message
+ // will be sent.
boolean sendMessage = false;
synchronized (mDataBuilder) {
sendMessage = mDataBuilder.isEmpty();
@@ -1009,28 +1010,34 @@ class LoadListener extends Handler implements EventHandler {
if (mIsMainPageLoader) {
String type = sCertificateTypeMap.get(mMimeType);
if (type != null) {
- // In the case of downloading certificate, we will save it to
- // the KeyStore and stop the current loading so that it will not
- // generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
+ // This must be synchronized so that no more data can be added
+ // after getByteSize returns.
+ synchronized (mDataBuilder) {
+ // In the case of downloading certificate, we will save it
+ // to the KeyStore and stop the current loading so that it
+ // will not generate a new history page
+ byte[] cert = new byte[mDataBuilder.getByteSize()];
+ int offset = 0;
+ while (true) {
+ ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
+ if (c == null) break;
+
+ if (c.mLength != 0) {
+ System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
+ offset += c.mLength;
+ }
+ c.release();
}
- mDataBuilder.releaseChunk(c);
+ CertTool.addCertificate(mContext, type, cert);
+ mBrowserFrame.stopLoading();
+ return;
}
- CertTool.addCertificate(mContext, type, cert);
- mBrowserFrame.stopLoading();
- return;
}
}
- // Give the data to WebKit now
+ // Give the data to WebKit now. We don't have to synchronize on
+ // mDataBuilder here because pulling each chunk removes it from the
+ // internal list so it cannot be modified.
PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
while (true) {
@@ -1047,7 +1054,7 @@ class LoadListener extends Handler implements EventHandler {
}
nativeAddData(c.mArray, c.mLength);
}
- mDataBuilder.releaseChunk(c);
+ c.release();
checker.responseAlert("res nativeAddData");
}
}
@@ -1210,8 +1217,17 @@ class LoadListener extends Handler implements EventHandler {
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(mUrl, mStatusCode,
- mRequestHeaders);
+ try {
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
+ mRequestHeaders);
+ } catch(RuntimeException e) {
+ Log.e(LOGTAG, e.getMessage());
+ // Signal a bad url error if we could not load the
+ // redirection.
+ handleError(EventHandler.ERROR_BAD_URL,
+ mContext.getString(R.string.httpErrorBadUrl));
+ return;
+ }
} else {
// If the original request came from the cache, there is no
// RequestHandle, we have to create a new one through
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index fffba1b..84a8a3c 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -431,6 +431,8 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("text/calendar", "icz");
sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
sMimeTypeMap.loadEntry("text/css", "css");
+ sMimeTypeMap.loadEntry("text/html", "htm");
+ sMimeTypeMap.loadEntry("text/html", "html");
sMimeTypeMap.loadEntry("text/h323", "323");
sMimeTypeMap.loadEntry("text/iuls", "uls");
sMimeTypeMap.loadEntry("text/mathml", "mml");
@@ -481,6 +483,7 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("video/dv", "dif");
sMimeTypeMap.loadEntry("video/dv", "dv");
sMimeTypeMap.loadEntry("video/fli", "fli");
+ sMimeTypeMap.loadEntry("video/m4v", "m4v");
sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
sMimeTypeMap.loadEntry("video/mpeg", "mpg");
sMimeTypeMap.loadEntry("video/mpeg", "mpe");
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index af0cb1e..b53e404 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -180,20 +180,24 @@ class Network {
}
RequestQueue q = mRequestQueue;
+ RequestHandle handle = null;
if (loader.isSynchronous()) {
- q = new RequestQueue(loader.getContext(), 1);
- }
-
- RequestHandle handle = q.queueRequest(
- url, loader.getWebAddress(), method, headers, loader,
- bodyProvider, bodyLength);
- loader.attachRequestHandle(handle);
-
- if (loader.isSynchronous()) {
- handle.waitUntilComplete();
+ handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
+ method, headers, loader, bodyProvider, bodyLength);
+ loader.attachRequestHandle(handle);
+ handle.processRequest();
loader.loadSynchronousMessages();
- q.shutdown();
+ } else {
+ handle = q.queueRequest(url, loader.getWebAddress(), method,
+ headers, loader, bodyProvider, bodyLength);
+ // FIXME: Although this is probably a rare condition, normal network
+ // requests are processed in a separate thread. This means that it
+ // is possible to process part of the request before setting the
+ // request handle on the loader. We should probably refactor this to
+ // ensure the handle is attached before processing begins.
+ loader.attachRequestHandle(handle);
}
+
return true;
}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 232ed36..211e5e4 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -367,19 +367,23 @@ public final class URLUtil {
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+ Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/*
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
+ * Note that RFC 2616 specifies the filename value must be double-quoted.
+ * Unfortunately some servers do not quote the value so to maintain
+ * consistent behaviour with other browsers, we allow unquoted values too.
*/
static String parseContentDisposition(String contentDisposition) {
try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
- return m.group(1);
+ return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 7f5b862..ae4f7c2 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Message;
import android.view.View;
@@ -302,4 +303,13 @@ public class WebChromeClient {
public void getVisitedHistory(ValueCallback<String[]> callback) {
}
+ /**
+ * Tell the client to open a file chooser.
+ * @param uploadFile A ValueCallback to set the URI of the file to upload.
+ * onReceiveValue must be called to wake up the thread.
+ * @hide
+ */
+ public void openFileChooser(ValueCallback<Uri> uploadFile) {
+ uploadFile.onReceiveValue(null);
+ }
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 4fedec9..79d8c03 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1007,7 +1007,8 @@ public class WebSettings {
* should never be null.
*/
public synchronized void setGeolocationDatabasePath(String databasePath) {
- if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+ if (databasePath != null
+ && !databasePath.equals(mGeolocationDatabasePath)) {
mGeolocationDatabasePath = databasePath;
postSync();
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index e0d41c2..0e5e8b5 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -138,7 +138,10 @@ import java.util.ArrayList;
}
if (!isArrowKey && !mOkayForFocusNotToMatch
&& mWebView.nativeFocusNodePointer() != mNodePointer) {
- mWebView.nativeClearCursor();
+ if (mWebView.nativeCursorNodePointer() == mNodePointer) {
+ // remove cursor so character doesn't go back to this view
+ mWebView.nativeClearCursor();
+ }
// Do not call remove() here, which hides the soft keyboard. If
// the soft keyboard is being displayed, the user will still want
// it there.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index fe91229..6510146 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -64,7 +64,9 @@ import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
@@ -82,6 +84,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import junit.framework.Assert;
+
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -390,6 +394,8 @@ public class WebView extends AbsoluteLayout
private static final int LONG_PRESS_TIMEOUT = 1000;
// needed to avoid flinging after a pause of no movement
private static final int MIN_FLING_TIME = 250;
+ // draw unfiltered after drag is held without movement
+ private static final int MOTIONLESS_TIME = 100;
// The time that the Zoom Controls are visible before fading away
private static final long ZOOM_CONTROLS_TIMEOUT =
ViewConfiguration.getZoomControlsTimeout();
@@ -427,6 +433,10 @@ public class WebView extends AbsoluteLayout
private Scroller mScroller;
private boolean mWrapContent;
+ private static final int MOTIONLESS_FALSE = 0;
+ private static final int MOTIONLESS_PENDING = 1;
+ private static final int MOTIONLESS_TRUE = 2;
+ private int mHeldMotionless;
/**
* Private message ids
@@ -438,6 +448,8 @@ public class WebView extends AbsoluteLayout
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
private static final int RESUME_WEBCORE_UPDATE = 7;
+ private static final int DRAG_HELD_MOTIONLESS = 8;
+ private static final int AWAKEN_SCROLL_BARS = 9;
//! arg1=x, arg2=y
static final int SCROLL_TO_MSG_ID = 10;
@@ -468,9 +480,9 @@ public class WebView extends AbsoluteLayout
"SWITCH_TO_LONGPRESS", // = 4;
"RELEASE_SINGLE_TAP", // = 5;
"REQUEST_FORM_DATA", // = 6;
- "SWITCH_TO_CLICK", // = 7;
- "RESUME_WEBCORE_UPDATE", // = 8;
- "9",
+ "RESUME_WEBCORE_UPDATE", // = 7;
+ "DRAG_HELD_MOTIONLESS", // = 8;
+ "AWAKEN_SCROLL_BARS", // = 9;
"SCROLL_TO_MSG_ID", // = 10;
"SCROLL_BY_MSG_ID", // = 11;
"SPAWN_SCROLL_TO_MSG_ID", // = 12;
@@ -535,11 +547,10 @@ public class WebView extends AbsoluteLayout
private boolean mUserScroll = false;
private int mSnapScrollMode = SNAP_NONE;
- private static final int SNAP_NONE = 1;
- private static final int SNAP_X = 2;
- private static final int SNAP_Y = 3;
- private static final int SNAP_X_LOCK = 4;
- private static final int SNAP_Y_LOCK = 5;
+ private static final int SNAP_NONE = 0;
+ private static final int SNAP_LOCK = 1; // not a separate state
+ private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
+ private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
private boolean mSnapPositive;
// Used to match key downs and key ups
@@ -1713,6 +1724,7 @@ public class WebView extends AbsoluteLayout
* as the data member with "url" as key. The result can be null.
*/
public void requestImageRef(Message msg) {
+ if (0 == mNativeClass) return; // client isn't initialized
int contentX = viewToContentX((int) mLastTouchX + mScrollX);
int contentY = viewToContentY((int) mLastTouchY + mScrollY);
String ref = nativeImageURI(contentX, contentY);
@@ -2331,6 +2343,7 @@ public class WebView extends AbsoluteLayout
* @param forward Direction to search.
*/
public void findNext(boolean forward) {
+ if (0 == mNativeClass) return; // client isn't initialized
nativeFindNext(forward);
}
@@ -2341,6 +2354,7 @@ public class WebView extends AbsoluteLayout
* that were found.
*/
public int findAll(String find) {
+ if (0 == mNativeClass) return 0; // client isn't initialized
if (mFindIsUp == false) {
recordNewContentSize(mContentWidth, mContentHeight + mFindHeight,
false);
@@ -2605,12 +2619,12 @@ public class WebView extends AbsoluteLayout
if (mHeightCanMeasure) {
if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else if (mWidthCanMeasure) {
if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else {
@@ -2630,6 +2644,16 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Gets the WebViewClient
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mCallbackProxy.getWebViewClient();
+ }
+
+ /**
* Register the interface to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current handler.
@@ -2836,6 +2860,21 @@ public class WebView extends AbsoluteLayout
*/
private boolean mNeedToAdjustWebTextView;
+ private boolean didUpdateTextViewBounds(boolean allowIntersect) {
+ Rect contentBounds = nativeFocusCandidateNodeBounds();
+ Rect vBox = contentToViewRect(contentBounds);
+ Rect visibleRect = new Rect();
+ calcOurVisibleRect(visibleRect);
+ if (allowIntersect ? Rect.intersects(visibleRect, vBox) :
+ visibleRect.contains(vBox)) {
+ mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
+ vBox.height());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private void drawCoreAndCursorRing(Canvas canvas, int color,
boolean drawCursorRing) {
if (mDrawHistory) {
@@ -2845,8 +2884,22 @@ public class WebView extends AbsoluteLayout
}
boolean animateZoom = mZoomScale != 0;
- boolean animateScroll = !mScroller.isFinished()
- || mVelocityTracker != null;
+ boolean animateScroll = (!mScroller.isFinished()
+ || mVelocityTracker != null)
+ && (mTouchMode != TOUCH_DRAG_MODE ||
+ mHeldMotionless != MOTIONLESS_TRUE);
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mHeldMotionless == MOTIONLESS_PENDING) {
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_FALSE;
+ }
+ if (mHeldMotionless == MOTIONLESS_FALSE) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+ mHeldMotionless = MOTIONLESS_PENDING;
+ }
+ }
if (animateZoom) {
float zoomScale;
int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
@@ -2863,19 +2916,13 @@ public class WebView extends AbsoluteLayout
invalidate();
if (mNeedToAdjustWebTextView) {
mNeedToAdjustWebTextView = false;
- Rect contentBounds = nativeFocusCandidateNodeBounds();
- Rect vBox = contentToViewRect(contentBounds);
- Rect visibleRect = new Rect();
- calcOurVisibleRect(visibleRect);
- if (visibleRect.contains(vBox)) {
- // As a result of the zoom, the textfield is now on
- // screen. Place the WebTextView in its new place,
- // accounting for our new scroll/zoom values.
+ // As a result of the zoom, the textfield is now on
+ // screen. Place the WebTextView in its new place,
+ // accounting for our new scroll/zoom values.
+ if (didUpdateTextViewBounds(false)) {
mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
contentToViewDimension(
nativeFocusCandidateTextSize()));
- mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
- vBox.height());
// If it is a password field, start drawing the
// WebTextView once again.
if (nativeFocusCandidateIsPassword()) {
@@ -2951,6 +2998,10 @@ public class WebView extends AbsoluteLayout
if (mFindIsUp && !animateScroll) {
nativeDrawMatches(canvas);
}
+ if (mFocusSizeChanged) {
+ mFocusSizeChanged = false;
+ didUpdateTextViewBounds(true);
+ }
}
// draw history
@@ -3244,6 +3295,15 @@ public class WebView extends AbsoluteLayout
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
// always handle the navigation keys in the UI thread
switchOutDrawHistory();
+ if (mShiftIsPressed) {
+ int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+ ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
+ int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
+ -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
+ int multiplier = event.getRepeatCount() + 1;
+ moveSelection(xRate * multiplier, yRate * multiplier);
+ return true;
+ }
if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
playSoundEffect(keyCodeToSoundsEffect(keyCode));
return true;
@@ -3255,6 +3315,9 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
+ if (mShiftIsPressed) {
+ return true; // discard press if copy in progress
+ }
mGotCenterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
@@ -3304,7 +3367,7 @@ public class WebView extends AbsoluteLayout
}
}
- if (nativeCursorIsPlugin()) {
+ if (nativeFocusCandidateIsPlugin()) {
nativeUpdatePluginReceivesEvents();
invalidate();
} else if (nativeCursorIsTextInput()) {
@@ -3316,7 +3379,9 @@ public class WebView extends AbsoluteLayout
// our view system's notion of focus
rebuildWebTextView();
// Now we need to pass the event to it
- return mWebTextView.onKeyDown(keyCode, event);
+ if (inEditingMode()) {
+ return mWebTextView.onKeyDown(keyCode, event);
+ }
} else if (nativeHasFocusNode()) {
// In this case, the cursor is not on a text input, but the focus
// might be. Check it, and if so, hand over to the WebTextView.
@@ -3387,7 +3452,12 @@ public class WebView extends AbsoluteLayout
mGotCenterDown = false;
if (mShiftIsPressed) {
- return false;
+ if (mExtendSelection) {
+ commitCopy();
+ } else {
+ mExtendSelection = true;
+ }
+ return true; // discard press if copy in progress
}
// perform the single click
@@ -3431,6 +3501,7 @@ public class WebView extends AbsoluteLayout
* @hide
*/
public void emulateShiftHeld() {
+ if (0 == mNativeClass) return; // client isn't initialized
mExtendSelection = false;
mShiftIsPressed = true;
nativeHideCursor();
@@ -3594,6 +3665,24 @@ public class WebView extends AbsoluteLayout
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = super.setFrame(left, top, right, bottom);
+ if (!changed && mHeightCanMeasure) {
+ // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
+ // in WebViewCore after we get the first layout. We do call
+ // requestLayout() when we get contentSizeChanged(). But the View
+ // system won't call onSizeChanged if the dimension is not changed.
+ // In this case, we need to call sendViewSizeZoom() explicitly to
+ // notify the WebKit about the new dimensions.
+ sendViewSizeZoom();
+ }
+ return changed;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
@@ -3702,8 +3791,10 @@ public class WebView extends AbsoluteLayout
mLastSentTouchTime = eventTime;
}
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
+ float fDeltaX = mLastTouchX - x;
+ float fDeltaY = mLastTouchY - y;
+ int deltaX = (int) fDeltaX;
+ int deltaY = (int) fDeltaY;
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -3829,12 +3920,21 @@ public class WebView extends AbsoluteLayout
// do pan
int newScrollX = pinLocX(mScrollX + deltaX);
- deltaX = newScrollX - mScrollX;
+ int newDeltaX = newScrollX - mScrollX;
+ if (deltaX != newDeltaX) {
+ deltaX = newDeltaX;
+ fDeltaX = (float) newDeltaX;
+ }
int newScrollY = pinLocY(mScrollY + deltaY);
- deltaY = newScrollY - mScrollY;
+ int newDeltaY = newScrollY - mScrollY;
+ if (deltaY != newDeltaY) {
+ deltaY = newDeltaY;
+ fDeltaY = (float) newDeltaY;
+ }
boolean done = false;
- if (deltaX == 0 && deltaY == 0) {
- done = true;
+ boolean keepScrollBarsVisible = false;
+ if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
+ keepScrollBarsVisible = done = true;
} else {
if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
int ax = Math.abs(deltaX);
@@ -3846,56 +3946,47 @@ public class WebView extends AbsoluteLayout
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
- ((mSnapPositive &&
- deltaX < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaX > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_X_LOCK;
+ if (ax > MAX_SLOPE_FOR_DIAG * ay &&
+ (mSnapPositive
+ ? deltaX < -mMinLockSnapReverseDistance
+ : deltaX > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
} else {
// radical change means getting out of snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+ if (ax > MAX_SLOPE_FOR_DIAG * ay
&& ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
- ((mSnapPositive &&
- deltaY < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaY > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_Y_LOCK;
+ if (ay > MAX_SLOPE_FOR_DIAG * ax &&
+ (mSnapPositive
+ ? deltaY < -mMinLockSnapReverseDistance
+ : deltaY > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
}
}
-
- if (mSnapScrollMode == SNAP_X
- || mSnapScrollMode == SNAP_X_LOCK) {
- if (deltaX == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
+ if (mSnapScrollMode != SNAP_NONE) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
+ deltaY = 0;
} else {
- scrollBy(deltaX, 0);
+ deltaX = 0;
}
- mLastTouchX = x;
- } else if (mSnapScrollMode == SNAP_Y
- || mSnapScrollMode == SNAP_Y_LOCK) {
- if (deltaY == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
- } else {
- scrollBy(0, deltaY);
+ }
+ if ((deltaX | deltaY) != 0) {
+ scrollBy(deltaX, deltaY);
+ if (deltaX != 0) {
+ mLastTouchX = x;
}
- mLastTouchY = y;
+ if (deltaY != 0) {
+ mLastTouchY = y;
+ }
+ mHeldMotionless = MOTIONLESS_FALSE;
} else {
- scrollBy(deltaX, deltaY);
- mLastTouchX = x;
- mLastTouchY = y;
+ // keep the scrollbar on the screen even there is no
+ // scroll
+ keepScrollBarsVisible = true;
}
mLastTouchTime = eventTime;
mUserScroll = true;
@@ -3914,13 +4005,17 @@ public class WebView extends AbsoluteLayout
}
}
- if (done) {
+ if (keepScrollBarsVisible) {
+ if (mHeldMotionless != MOTIONLESS_TRUE) {
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ }
// keep the scrollbar on the screen even there is no scroll
awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
false);
// return false to indicate that we can't pan out of the
// view space
- return false;
+ return !done;
}
break;
}
@@ -3973,6 +4068,9 @@ public class WebView extends AbsoluteLayout
break;
}
case TOUCH_DRAG_MODE:
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
// redraw in high-quality, as we're done dragging
invalidate();
// if the user waits a while w/o moving before the
@@ -4012,6 +4110,9 @@ public class WebView extends AbsoluteLayout
}
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
mTouchMode = TOUCH_DONE_MODE;
nativeHideCursor();
break;
@@ -4038,6 +4139,7 @@ public class WebView extends AbsoluteLayout
private static final int SELECT_CURSOR_OFFSET = 16;
private int mSelectX = 0;
private int mSelectY = 0;
+ private boolean mFocusSizeChanged = false;
private boolean mShiftIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
@@ -4143,8 +4245,8 @@ public class WebView extends AbsoluteLayout
return;
int width = getViewWidth();
int height = getViewHeight();
- mSelectX += scaleTrackballX(xRate, width);
- mSelectY += scaleTrackballY(yRate, height);
+ mSelectX += xRate;
+ mSelectY += yRate;
int maxX = width + mScrollX;
int maxY = height + mScrollY;
mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
@@ -4226,8 +4328,11 @@ public class WebView extends AbsoluteLayout
}
float xRate = mTrackballRemainsX * 1000 / elapsed;
float yRate = mTrackballRemainsY * 1000 / elapsed;
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeight();
if (mShiftIsPressed) {
- moveSelection(xRate, yRate);
+ moveSelection(scaleTrackballX(xRate, viewWidth),
+ scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
return;
}
@@ -4241,8 +4346,8 @@ public class WebView extends AbsoluteLayout
+ " mTrackballRemainsX=" + mTrackballRemainsX
+ " mTrackballRemainsY=" + mTrackballRemainsY);
}
- int width = mContentWidth - getViewWidth();
- int height = mContentHeight - getViewHeight();
+ int width = mContentWidth - viewWidth;
+ int height = mContentHeight - viewHeight;
if (width < 0) width = 0;
if (height < 0) height = 0;
ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
@@ -4317,7 +4422,7 @@ public class WebView extends AbsoluteLayout
int vy = (int) mVelocityTracker.getYVelocity();
if (mSnapScrollMode != SNAP_NONE) {
- if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
vy = 0;
} else {
vx = 0;
@@ -4597,9 +4702,6 @@ public class WebView extends AbsoluteLayout
}
int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
- // In case the soft keyboard has been dismissed, bring it back up.
- InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
- 0);
if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
nativeMotionUp(x, y, mNavSlop);
}
@@ -4684,8 +4786,12 @@ public class WebView extends AbsoluteLayout
// called by JNI
private void sendPluginState(int state) {
WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
- psd.mFrame = nativeCursorFramePointer();
- psd.mNode = nativeCursorNodePointer();
+ psd.mFrame = nativeFocusCandidateFramePointer();
+ psd.mNode = nativeFocusCandidatePointer();
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "sendPluginState frame=" + psd.mFrame
+ + " node=" + psd.mNode);
+ }
psd.mState = state;
mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
}
@@ -4876,9 +4982,10 @@ public class WebView extends AbsoluteLayout
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- if (DebugFlags.WEB_VIEW) {
+ // exclude INVAL_RECT_MSG_ID since it is frequently output
+ if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
- > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
+ > REQUEST_KEYBOARD ? Integer.toString(msg.what)
: HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
}
if (mWebViewCore == null) {
@@ -5040,6 +5147,9 @@ public class WebView extends AbsoluteLayout
/ mZoomOverviewWidth, false);
}
}
+ if (draw.mFocusSizeChanged && inEditingMode()) {
+ mFocusSizeChanged = true;
+ }
break;
}
case WEBCORE_INITIALIZED_MSG_ID:
@@ -5166,9 +5276,29 @@ public class WebView extends AbsoluteLayout
hideSoftKeyboard();
} else {
displaySoftKeyboard(false);
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "REQUEST_KEYBOARD"
+ + " focusCandidateIsPlugin="
+ + nativeFocusCandidateIsPlugin());
+ }
}
break;
+ case DRAG_HELD_MOTIONLESS:
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ // fall through to keep scrollbars awake
+
+ case AWAKEN_SCROLL_BARS:
+ if (mTouchMode == TOUCH_DRAG_MODE
+ && mHeldMotionless == MOTIONLESS_TRUE) {
+ awakenScrollBars(ViewConfiguration
+ .getScrollDefaultDelay(), false);
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
+ }
+ break;
default:
super.handleMessage(msg);
break;
@@ -5192,8 +5322,16 @@ public class WebView extends AbsoluteLayout
// Need these to provide stable ids to my ArrayAdapter,
// which normally does not have stable ids. (Bug 1250098)
private class Container extends Object {
+ /**
+ * Possible values for mEnabled. Keep in sync with OptionStatus in
+ * WebViewCore.cpp
+ */
+ final static int OPTGROUP = -1;
+ final static int OPTION_DISABLED = 0;
+ final static int OPTION_ENABLED = 1;
+
String mString;
- boolean mEnabled;
+ int mEnabled;
int mId;
public String toString() {
@@ -5214,6 +5352,54 @@ public class WebView extends AbsoluteLayout
}
@Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ // Always pass in null so that we will get a new CheckedTextView
+ // Otherwise, an item which was previously used as an <optgroup>
+ // element (i.e. has no check), could get used as an <option>
+ // element, which needs a checkbox/radio, but it would not have
+ // one.
+ convertView = super.getView(position, null, parent);
+ Container c = item(position);
+ if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
+ // ListView does not draw dividers between disabled and
+ // enabled elements. Use a LinearLayout to provide dividers
+ LinearLayout layout = new LinearLayout(mContext);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ if (position > 0) {
+ View dividerTop = new View(mContext);
+ dividerTop.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerTop);
+ }
+
+ if (Container.OPTGROUP == c.mEnabled) {
+ // Currently select_dialog_multichoice and
+ // select_dialog_singlechoice are CheckedTextViews. If
+ // that changes, the class cast will no longer be valid.
+ Assert.assertTrue(
+ convertView instanceof CheckedTextView);
+ ((CheckedTextView) convertView).setCheckMarkDrawable(
+ null);
+ } else {
+ // c.mEnabled == Container.OPTION_DISABLED
+ // Draw the disabled element in a disabled state.
+ convertView.setEnabled(false);
+ }
+
+ layout.addView(convertView);
+ if (position < getCount() - 1) {
+ View dividerBottom = new View(mContext);
+ dividerBottom.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerBottom);
+ }
+ return layout;
+ }
+ return convertView;
+ }
+
+ @Override
public boolean hasStableIds() {
// AdapterView's onChanged method uses this to determine whether
// to restore the old state. Return false so that the old (out
@@ -5248,12 +5434,11 @@ public class WebView extends AbsoluteLayout
if (item == null) {
return false;
}
- return item.mEnabled;
+ return Container.OPTION_ENABLED == item.mEnabled;
}
}
- private InvokeListBox(String[] array,
- boolean[] enabled, int[] selected) {
+ private InvokeListBox(String[] array, int[] enabled, int[] selected) {
mMultiple = true;
mSelectedArray = selected;
@@ -5267,8 +5452,7 @@ public class WebView extends AbsoluteLayout
}
}
- private InvokeListBox(String[] array, boolean[] enabled, int
- selection) {
+ private InvokeListBox(String[] array, int[] enabled, int selection) {
mSelection = selection;
mMultiple = false;
@@ -5399,10 +5583,11 @@ public class WebView extends AbsoluteLayout
* Request a dropdown menu for a listbox with multiple selection.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selectedArray Which positions are initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int[]
+ void requestListBox(String[] array, int[] enabledArray, int[]
selectedArray) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selectedArray));
@@ -5413,10 +5598,11 @@ public class WebView extends AbsoluteLayout
* <select> element.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selection Which position is initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+ void requestListBox(String[] array, int[] enabledArray, int selection) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selection));
}
@@ -5575,6 +5761,17 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Draw the HTML page into the specified canvas. This call ignores any
+ * view-specific zoom, scroll offset, or other changes. It does not draw
+ * any view-specific chrome, such as progress or URL bars.
+ *
+ * @hide only needs to be accessible to Browser and testing
+ */
+ public void drawPage(Canvas canvas) {
+ mWebViewCore.drawContentPicture(canvas, 0, false, false);
+ }
+
+ /**
* Update our cache with updatedText.
* @param updatedText The new text to put in our cache.
*/
@@ -5592,7 +5789,6 @@ public class WebView extends AbsoluteLayout
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
- private native boolean nativeCursorIsPlugin();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -5611,7 +5807,9 @@ public class WebView extends AbsoluteLayout
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
+ private native int nativeFocusCandidateFramePointer();
private native boolean nativeFocusCandidateIsPassword();
+ private native boolean nativeFocusCandidateIsPlugin();
private native boolean nativeFocusCandidateIsRtlText();
private native boolean nativeFocusCandidateIsTextField();
private native boolean nativeFocusCandidateIsTextInput();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 86685fb..4dd75d8 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,7 @@ package android.webkit;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.DrawFilter;
import android.graphics.Paint;
@@ -26,11 +27,13 @@ import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.Browser;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
@@ -273,6 +276,39 @@ final class WebViewCore {
mCallbackProxy.onJsAlert(url, message);
}
+
+ /**
+ * Called by JNI. Open a file chooser to upload a file.
+ * @return String version of the URI plus the name of the file.
+ * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+ * into Java to get the filename.
+ */
+ private String openFileChooser() {
+ Uri uri = mCallbackProxy.openFileChooser();
+ if (uri == null) return "";
+ // Find out the name, and append it to the URI.
+ // Webkit will treat the name as the filename, and
+ // the URI as the path. The URI will be used
+ // in BrowserFrame to get the actual data.
+ Cursor cursor = mContext.getContentResolver().query(
+ uri,
+ new String[] { OpenableColumns.DISPLAY_NAME },
+ null,
+ null,
+ null);
+ String name = "";
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ name = cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return uri.toString() + "/" + name;
+ }
+
/**
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
@@ -422,6 +458,8 @@ final class WebViewCore {
*/
private native boolean nativeRecordContent(Region invalRegion, Point wh);
+ private native boolean nativeFocusBoundsChanged();
+
/**
* Splits slow parts of the picture set. Called from the webkit
* thread after nativeDrawContent returns true.
@@ -1593,6 +1631,7 @@ final class WebViewCore {
int mMinPrefWidth;
RestoreState mRestoreState; // only non-null if it is for the first
// picture set after the first layout
+ boolean mFocusSizeChanged;
}
private void webkitDraw() {
@@ -1607,6 +1646,7 @@ final class WebViewCore {
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mFocusSizeChanged = nativeFocusBoundsChanged();
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (mSettings.getUseWideViewPort()) {
draw.mMinPrefWidth = Math.max(
@@ -2101,7 +2141,7 @@ final class WebViewCore {
private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int[] selectedArray) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selectedArray);
@@ -2109,7 +2149,7 @@ final class WebViewCore {
}
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int selection) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selection);
@@ -2175,6 +2215,11 @@ final class WebViewCore {
return view;
}
+ private void updateSurface(ViewManager.ChildView childView, int x, int y,
+ int width, int height) {
+ childView.attachView(x, y, width, height);
+ }
+
private void destroySurface(ViewManager.ChildView childView) {
childView.removeView();
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 6e10811..110e4f8 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -27,6 +27,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import android.webkit.CookieManager.Cookie;
@@ -174,7 +175,16 @@ public class WebViewDatabase {
public static synchronized WebViewDatabase getInstance(Context context) {
if (mInstance == null) {
mInstance = new WebViewDatabase();
- mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+ try {
+ mDatabase = context
+ .openOrCreateDatabase(DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(DATABASE_FILE)) {
+ mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
+ null);
+ }
+ }
// mDatabase should not be null,
// the only case is RequestAPI test has problem to create db
@@ -194,8 +204,16 @@ public class WebViewDatabase {
mDatabase.setLockingEnabled(false);
}
- mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE,
- 0, null);
+ try {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
+ mCacheDatabase = context.openOrCreateDatabase(
+ CACHE_DATABASE_FILE, 0, null);
+ }
+ }
// mCacheDatabase should not be null,
// the only case is RequestAPI test has problem to create db
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 165794a..5991ad4 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3560,6 +3560,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
+ removeDetachedView(scrap, false);
return;
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index fd590ed..aa9062b 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -117,11 +117,11 @@ public class CheckedTextView extends TextView implements Checkable {
* @param d The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(Drawable d) {
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.setCallback(null);
+ unscheduleDrawable(mCheckMarkDrawable);
+ }
if (d != null) {
- if (mCheckMarkDrawable != null) {
- mCheckMarkDrawable.setCallback(null);
- unscheduleDrawable(mCheckMarkDrawable);
- }
d.setCallback(this);
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
@@ -130,10 +130,10 @@ public class CheckedTextView extends TextView implements Checkable {
mCheckMarkWidth = d.getIntrinsicWidth();
mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
d.setState(getDrawableState());
- mCheckMarkDrawable = d;
} else {
mPaddingRight = mBasePaddingRight;
}
+ mCheckMarkDrawable = d;
requestLayout();
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8019f14..07c3e4b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -25,9 +25,9 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.util.AttributeSet;
@@ -55,21 +55,28 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
static final private int TOKEN_PHONE_LOOKUP = 1;
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private int TOKEN_CONTACT_LOOKUP_AND_TRIGGER = 4;
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
};
- static int EMAIL_ID_COLUMN_INDEX = 0;
- static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int EMAIL_ID_COLUMN_INDEX = 0;
+ static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY,
};
- static int PHONE_ID_COLUMN_INDEX = 0;
- static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int PHONE_ID_COLUMN_INDEX = 0;
+ static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ };
+ static final int CONTACT_ID_COLUMN_INDEX = 0;
+ static final int CONTACT_LOOKUPKEY_COLUMN_INDEX = 1;
public QuickContactBadge(Context context) {
@@ -181,9 +188,9 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
public void onClick(View v) {
if (mContactUri != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri);
- trigger(lookupUri);
+ mQueryHandler.startQuery(TOKEN_CONTACT_LOOKUP_AND_TRIGGER, null,
+ mContactUri,
+ CONTACT_LOOKUP_PROJECTION, null, null, null);
} else if (mContactEmail != null) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -249,6 +256,17 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
lookupUri = Contacts.getLookupUri(contactId, lookupKey);
}
}
+
+ case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
+ if (cursor != null && cursor.moveToFirst()) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUPKEY_COLUMN_INDEX);
+ lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ trigger = true;
+ }
+
+ break;
+ }
}
} finally {
if (cursor != null) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index bf3d26e..3ed995b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5198,7 +5198,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(desired, height);
+ height = Math.min(desired, heightSize);
}
}