Merge change Idf7fafd3 into eclair
* changes:
Fix issue #2248951: Need to turn off touch filtering outside of sholes
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index e548524..df59dcf 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -683,22 +683,30 @@
{
LOGD("stopPreview (pid %d)", getCallingPid());
- Mutex::Autolock lock(mLock);
- if (checkPid() != NO_ERROR) return;
+ // hold main lock during state transition
+ {
+ Mutex::Autolock lock(mLock);
+ if (checkPid() != NO_ERROR) return;
- if (mHardware == 0) {
- LOGE("mHardware is NULL, returning.");
- return;
+ if (mHardware == 0) {
+ LOGE("mHardware is NULL, returning.");
+ return;
+ }
+
+ mHardware->stopPreview();
+ mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+ LOGD("stopPreview(), hardware stopped OK");
+
+ if (mSurface != 0 && !mUseOverlay) {
+ mSurface->unregisterBuffers();
+ }
}
- mHardware->stopPreview();
- mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- LOGD("stopPreview(), hardware stopped OK");
-
- if (mSurface != 0 && !mUseOverlay) {
- mSurface->unregisterBuffers();
+ // hold preview buffer lock
+ {
+ Mutex::Autolock lock(mPreviewLock);
+ mPreviewBuffer.clear();
}
- mPreviewBuffer.clear();
}
// stop recording mode
@@ -706,24 +714,31 @@
{
LOGD("stopRecording (pid %d)", getCallingPid());
- Mutex::Autolock lock(mLock);
- if (checkPid() != NO_ERROR) return;
+ // hold main lock during state transition
+ {
+ Mutex::Autolock lock(mLock);
+ if (checkPid() != NO_ERROR) return;
- if (mHardware == 0) {
- LOGE("mHardware is NULL, returning.");
- return;
+ if (mHardware == 0) {
+ LOGE("mHardware is NULL, returning.");
+ return;
+ }
+
+ if (mMediaPlayerBeep.get() != NULL) {
+ mMediaPlayerBeep->seekTo(0);
+ mMediaPlayerBeep->start();
+ }
+
+ mHardware->stopRecording();
+ mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME);
+ LOGD("stopRecording(), hardware stopped OK");
}
- if (mMediaPlayerBeep.get() != NULL) {
- mMediaPlayerBeep->seekTo(0);
- mMediaPlayerBeep->start();
+ // hold preview buffer lock
+ {
+ Mutex::Autolock lock(mPreviewLock);
+ mPreviewBuffer.clear();
}
-
- mHardware->stopRecording();
- mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME);
- LOGD("stopRecording(), hardware stopped OK");
-
- mPreviewBuffer.clear();
}
// release a recording frame
@@ -1216,10 +1231,10 @@
// provided it's big enough. Don't allocate the memory or
// perform the copy if there's no callback.
- // hold the lock while we grab a reference to the preview buffer
+ // hold the preview lock while we grab a reference to the preview buffer
sp<MemoryHeapBase> previewBuffer;
{
- Mutex::Autolock lock(mLock);
+ Mutex::Autolock lock(mPreviewLock);
if (mPreviewBuffer == 0) {
mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);
} else if (size > mPreviewBuffer->virtualSize()) {
diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 41c5d99..3e3e54f 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -181,7 +181,6 @@
mutable Condition mReady;
sp<CameraService> mCameraService;
sp<ISurface> mSurface;
- sp<MemoryHeapBase> mPreviewBuffer;
int mPreviewCallbackFlag;
sp<MediaPlayer> mMediaPlayerClick;
@@ -197,6 +196,9 @@
sp<OverlayRef> mOverlayRef;
int mOverlayW;
int mOverlayH;
+
+ mutable Mutex mPreviewLock;
+ sp<MemoryHeapBase> mPreviewBuffer;
};
// ----------------------------------------------------------------------------
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
index ce063a7..d6c76a2 100644
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ b/core/java/android/accounts/AccountAuthenticatorCache.java
@@ -18,10 +18,16 @@
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
import android.content.res.TypedArray;
import android.content.Context;
import android.util.AttributeSet;
import android.text.TextUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* A cache of services that export the {@link IAccountAuthenticator} interface. This cache
@@ -33,10 +39,12 @@
/* package private */ class AccountAuthenticatorCache
extends RegisteredServicesCache<AuthenticatorDescription> {
private static final String TAG = "Account";
+ private static final MySerializer sSerializer = new MySerializer();
public AccountAuthenticatorCache(Context context) {
super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
- AccountManager.AUTHENTICATOR_META_DATA_NAME, AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME);
+ AccountManager.AUTHENTICATOR_META_DATA_NAME,
+ AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
}
public AuthenticatorDescription parseServiceAttributes(String packageName, AttributeSet attrs) {
@@ -62,4 +70,16 @@
sa.recycle();
}
}
+
+ private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
+ public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
+ throws IOException {
+ out.attribute(null, "type", item.type);
+ }
+
+ public AuthenticatorDescription createFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
+ }
+ }
}
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f59c4e..800ad749 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -72,7 +72,7 @@
*/
public class AccountManagerService
extends IAccountManager.Stub
- implements RegisteredServicesCacheListener {
+ implements RegisteredServicesCacheListener<AuthenticatorDescription> {
private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
private static final String NO_BROADCAST_FLAG = "nobroadcast";
@@ -220,34 +220,29 @@
mMessageHandler = new MessageHandler(mMessageThread.getLooper());
mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
- mAuthenticatorCache.setListener(this);
+ mAuthenticatorCache.setListener(this, null /* Handler */);
mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
mSimWatcher = new SimWatcher(mContext);
sThis.set(this);
-
- onRegisteredServicesCacheChanged();
}
- public void onRegisteredServicesCacheChanged() {
+ public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
boolean accountDeleted = false;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor cursor = db.query(TABLE_ACCOUNTS,
new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
- null, null, null, null, null);
+ ACCOUNTS_TYPE + "=?", new String[]{desc.type}, null, null, null);
try {
while (cursor.moveToNext()) {
final long accountId = cursor.getLong(0);
final String accountType = cursor.getString(1);
final String accountName = cursor.getString(2);
- if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType))
- == null) {
- Log.d(TAG, "deleting account " + accountName + " because type "
- + accountType + " no longer has a registered authenticator");
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
- accountDeleted= true;
- }
+ Log.d(TAG, "deleting account " + accountName + " because type "
+ + accountType + " no longer has a registered authenticator");
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+ accountDeleted = true;
}
} finally {
cursor.close();
diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java
index e642700..91c94e6 100644
--- a/core/java/android/accounts/AuthenticatorDescription.java
+++ b/core/java/android/accounts/AuthenticatorDescription.java
@@ -87,6 +87,10 @@
return type.equals(other.type);
}
+ public String toString() {
+ return "AuthenticatorDescription {type=" + type + "}";
+ }
+
/** @inhericDoc */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type);
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index eba8715..fbe3548 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -135,6 +135,8 @@
public void onCreate(SQLiteDatabase db) {
bootstrapDatabase(db);
mSyncState.createDatabase(db);
+ ContentResolver.requestSync(null /* all accounts */,
+ mContentUri.getAuthority(), new Bundle());
}
@Override
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 7d9f1de..6ade837 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -17,9 +17,14 @@
package android.content;
import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
import android.content.res.TypedArray;
-import android.content.Context;
import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* A cache of services that export the {@link android.content.ISyncAdapter} interface.
@@ -31,9 +36,10 @@
private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
private static final String ATTRIBUTES_NAME = "sync-adapter";
+ private static final MySerializer sSerializer = new MySerializer();
SyncAdaptersCache(Context context) {
- super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME);
+ super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
}
public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) {
@@ -57,4 +63,18 @@
sa.recycle();
}
}
+
+ static class MySerializer implements XmlSerializerAndParser<SyncAdapterType> {
+ public void writeAsXml(SyncAdapterType item, XmlSerializer out) throws IOException {
+ out.attribute(null, "authority", item.authority);
+ out.attribute(null, "accountType", item.accountType);
+ }
+
+ public SyncAdapterType createFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ final String authority = parser.getAttributeValue(null, "authority");
+ final String accountType = parser.getAttributeValue(null, "accountType");
+ return SyncAdapterType.newKey(authority, accountType);
+ }
+ }
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 538373f..b39a67d 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -22,6 +22,8 @@
import android.content.IntentFilter;
import android.content.ComponentName;
import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Handler;
import android.util.Log;
import android.util.AttributeSet;
import android.util.Xml;
@@ -29,14 +31,26 @@
import java.util.Map;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.IOException;
+import java.io.FileInputStream;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
import com.google.android.collect.Maps;
+import com.google.android.collect.Lists;
+
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
/**
* A cache of registered services. This cache
@@ -52,75 +66,104 @@
private final String mInterfaceName;
private final String mMetaDataName;
private final String mAttributesName;
+ private final XmlSerializerAndParser<V> mSerializerAndParser;
+ private final AtomicReference<BroadcastReceiver> mReceiver;
- public RegisteredServicesCacheListener getListener() {
- return mListener;
- }
+ private final Object mServicesLock = new Object();
+ // synchronized on mServicesLock
+ private HashMap<V, Integer> mPersistentServices;
+ // synchronized on mServicesLock
+ private Map<V, ServiceInfo<V>> mServices;
+ // synchronized on mServicesLock
+ private boolean mPersistentServicesFileDidNotExist;
- public void setListener(RegisteredServicesCacheListener listener) {
- mListener = listener;
- }
+ /**
+ * This file contains the list of known services. We would like to maintain this forever
+ * so we store it as an XML file.
+ */
+ private final AtomicFile mPersistentServicesFile;
- private volatile RegisteredServicesCacheListener mListener;
-
- // no need to be synchronized since the map is never changed once mService is written
- volatile Map<V, ServiceInfo<V>> mServices;
-
- // synchronized on "this"
- private BroadcastReceiver mReceiver = null;
+ // the listener and handler are synchronized on "this" and must be updated together
+ private RegisteredServicesCacheListener<V> mListener;
+ private Handler mHandler;
public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
- String attributeName) {
+ String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
mContext = context;
mInterfaceName = interfaceName;
mMetaDataName = metaDataName;
mAttributesName = attributeName;
+ mSerializerAndParser = serializerAndParser;
+
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File syncDir = new File(systemDir, "registered_services");
+ mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
+
+ generateServicesMap();
+
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context1, Intent intent) {
+ generateServicesMap();
+ }
+ };
+ mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiver(receiver, intentFilter);
}
public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- getAllServices();
- Map<V, ServiceInfo<V>> services = mServices;
+ Map<V, ServiceInfo<V>> services;
+ synchronized (mServicesLock) {
+ services = mServices;
+ }
fout.println("RegisteredServicesCache: " + services.size() + " services");
for (ServiceInfo info : services.values()) {
fout.println(" " + info);
}
}
- private boolean maybeRegisterForPackageChanges() {
+ public RegisteredServicesCacheListener<V> getListener() {
synchronized (this) {
- if (mReceiver == null) {
- synchronized (this) {
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mServices = generateServicesMap();
- RegisteredServicesCacheListener listener = mListener;
- if (listener != null) {
- listener.onRegisteredServicesCacheChanged();
- }
- }
- };
- }
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mContext.registerReceiver(mReceiver, intentFilter);
- return true;
- }
- return false;
+ return mListener;
}
}
- private void maybeUnregisterForPackageChanges() {
- synchronized (this) {
- if (mReceiver != null) {
- mContext.unregisterReceiver(mReceiver);
- mReceiver = null;
- }
+ public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
+ if (handler == null) {
+ handler = new Handler(mContext.getMainLooper());
}
+ synchronized (this) {
+ mHandler = handler;
+ mListener = listener;
+ }
+ }
+
+ private void notifyListener(final V type, final boolean removed) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
+ }
+ RegisteredServicesCacheListener<V> listener;
+ Handler handler;
+ synchronized (this) {
+ listener = mListener;
+ handler = mHandler;
+ }
+ if (listener == null) {
+ return;
+ }
+
+ final RegisteredServicesCacheListener<V> listener2 = listener;
+ handler.post(new Runnable() {
+ public void run() {
+ listener2.onServiceChanged(type, removed);
+ }
+ });
}
/**
@@ -140,7 +183,7 @@
@Override
public String toString() {
- return "ServiceInfo: " + type + ", " + componentName;
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
}
}
@@ -150,11 +193,9 @@
* @return the AuthenticatorInfo that matches the account type or null if none is present
*/
public ServiceInfo<V> getServiceInfo(V type) {
- if (mServices == null) {
- maybeRegisterForPackageChanges();
- mServices = generateServicesMap();
+ synchronized (mServicesLock) {
+ return mServices.get(type);
}
- return mServices.get(type);
}
/**
@@ -162,54 +203,171 @@
* registered authenticators.
*/
public Collection<ServiceInfo<V>> getAllServices() {
- if (mServices == null) {
- maybeRegisterForPackageChanges();
- mServices = generateServicesMap();
+ synchronized (mServicesLock) {
+ return Collections.unmodifiableCollection(mServices.values());
}
- return Collections.unmodifiableCollection(mServices.values());
}
/**
* Stops the monitoring of package additions, removals and changes.
*/
public void close() {
- maybeUnregisterForPackageChanges();
+ final BroadcastReceiver receiver = mReceiver.getAndSet(null);
+ if (receiver != null) {
+ mContext.unregisterReceiver(receiver);
+ }
}
@Override
protected void finalize() throws Throwable {
- synchronized (this) {
- if (mReceiver != null) {
- Log.e(TAG, "RegisteredServicesCache finalized without being closed");
- }
+ if (mReceiver.get() != null) {
+ Log.e(TAG, "RegisteredServicesCache finalized without being closed");
}
close();
super.finalize();
}
- Map<V, ServiceInfo<V>> generateServicesMap() {
- Map<V, ServiceInfo<V>> services = Maps.newHashMap();
+ private boolean inSystemImage(int callerUid) {
+ String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo =
+ mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ void generateServicesMap() {
PackageManager pm = mContext.getPackageManager();
-
- List<ResolveInfo> resolveInfos =
- pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA);
-
+ ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(new Intent(mInterfaceName),
+ PackageManager.GET_META_DATA);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
- if (info != null) {
- services.put(info.type, info);
- } else {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
+ if (info == null) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
+ continue;
}
+ serviceInfos.add(info);
} catch (XmlPullParserException e) {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+ Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
} catch (IOException e) {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+ Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
- return services;
+ synchronized (mServicesLock) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "generateServicesMap: " + mInterfaceName);
+ }
+ if (mPersistentServices == null) {
+ readPersistentServicesLocked();
+ }
+ mServices = Maps.newHashMap();
+ boolean changed = false;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "found " + serviceInfos.size() + " services");
+ }
+ for (ServiceInfo<V> info : serviceInfos) {
+ // four cases:
+ // - doesn't exist yet
+ // - add, notify user that it was added
+ // - exists and the UID is the same
+ // - replace, don't notify user
+ // - exists, the UID is different, and the new one is not a system package
+ // - ignore
+ // - exists, the UID is different, and the new one is a system package
+ // - add, notify user that it was added
+ Integer previousUid = mPersistentServices.get(info.type);
+ if (previousUid == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "encountered new type: " + info);
+ }
+ changed = true;
+ mServices.put(info.type, info);
+ mPersistentServices.put(info.type, info.uid);
+ if (!mPersistentServicesFileDidNotExist) {
+ notifyListener(info.type, false /* removed */);
+ }
+ } else if (previousUid == info.uid) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "encountered existing type with the same uid: " + info);
+ }
+ mServices.put(info.type, info);
+ } else if (inSystemImage(info.uid)
+ || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (inSystemImage(info.uid)) {
+ Log.d(TAG, "encountered existing type with a new uid but from"
+ + " the system: " + info);
+ } else {
+ Log.d(TAG, "encountered existing type with a new uid but existing was"
+ + " removed: " + info);
+ }
+ }
+ changed = true;
+ mServices.put(info.type, info);
+ mPersistentServices.put(info.type, info.uid);
+ notifyListener(info.type, false /* removed */);
+ } else {
+ // ignore
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "encountered existing type with a new uid, ignoring: " + info);
+ }
+ }
+ }
+
+ ArrayList<V> toBeRemoved = Lists.newArrayList();
+ for (V v1 : mPersistentServices.keySet()) {
+ if (!containsType(serviceInfos, v1)) {
+ toBeRemoved.add(v1);
+ }
+ }
+ for (V v1 : toBeRemoved) {
+ mPersistentServices.remove(v1);
+ changed = true;
+ notifyListener(v1, true /* removed */);
+ }
+ if (changed) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "writing updated list of persistent services");
+ }
+ writePersistentServicesLocked();
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "persistent services did not change, so not writing anything");
+ }
+ }
+ mPersistentServicesFileDidNotExist = false;
+ }
+ }
+
+ private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
+ for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+ if (serviceInfos.get(i).type.equals(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
+ for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+ final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
+ if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
+ return true;
+ }
+ }
+
+ return false;
}
private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
@@ -252,5 +410,89 @@
}
}
+ /**
+ * Read all sync status back in to the initial engine state.
+ */
+ private void readPersistentServicesLocked() {
+ mPersistentServices = Maps.newHashMap();
+ if (mSerializerAndParser == null) {
+ return;
+ }
+ FileInputStream fis = null;
+ try {
+ mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
+ if (mPersistentServicesFileDidNotExist) {
+ return;
+ }
+ fis = mPersistentServicesFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("services".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
+ tagName = parser.getName();
+ if ("service".equals(tagName)) {
+ V service = mSerializerAndParser.createFromXml(parser);
+ if (service == null) {
+ break;
+ }
+ String uidString = parser.getAttributeValue(null, "uid");
+ int uid = Integer.parseInt(uidString);
+ mPersistentServices.put(service, uid);
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading persistent services, starting from scratch", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Write all sync status to the sync status file.
+ */
+ private void writePersistentServicesLocked() {
+ if (mSerializerAndParser == null) {
+ return;
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = mPersistentServicesFile.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "services");
+ for (Map.Entry<V, Integer> service : mPersistentServices.entrySet()) {
+ out.startTag(null, "service");
+ out.attribute(null, "uid", Integer.toString(service.getValue()));
+ mSerializerAndParser.writeAsXml(service.getKey(), out);
+ out.endTag(null, "service");
+ }
+ out.endTag(null, "services");
+ out.endDocument();
+ mPersistentServicesFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing accounts", e1);
+ if (fos != null) {
+ mPersistentServicesFile.failWrite(fos);
+ }
+ }
+ }
+
public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
}
diff --git a/core/java/android/content/pm/RegisteredServicesCacheListener.java b/core/java/android/content/pm/RegisteredServicesCacheListener.java
index c92c86e..2bc0942 100644
--- a/core/java/android/content/pm/RegisteredServicesCacheListener.java
+++ b/core/java/android/content/pm/RegisteredServicesCacheListener.java
@@ -1,12 +1,16 @@
package android.content.pm;
+import android.os.Parcelable;
+
/**
* Listener for changes to the set of registered services managed by a RegisteredServicesCache.
* @hide
*/
-public interface RegisteredServicesCacheListener {
+public interface RegisteredServicesCacheListener<V> {
/**
- * Invoked when the registered services cache changes.
+ * Invoked when a service is registered or changed.
+ * @param type the type of registered service
+ * @param removed true if the service was removed
*/
- void onRegisteredServicesCacheChanged();
+ void onServiceChanged(V type, boolean removed);
}
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
new file mode 100644
index 0000000..33598f0
--- /dev/null
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -0,0 +1,14 @@
+package android.content.pm;
+
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import android.os.Parcel;
+
+import java.io.IOException;
+
+/** @hide */
+public interface XmlSerializerAndParser<T> {
+ void writeAsXml(T item, XmlSerializer out) throws IOException;
+ T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
+}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 9638262..f9dce25 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -41,6 +41,8 @@
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.text.format.Time;
import android.util.CharsetUtils;
@@ -761,27 +763,58 @@
}
}
+ private boolean containsNonEmptyName(ContentValues contentValues) {
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+ TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+ TextUtils.isEmpty(suffix) && TextUtils.isEmpty(displayName));
+ }
+
private void appendStructuredNamesInternal(final StringBuilder builder,
final List<ContentValues> contentValuesList) {
// For safety, we'll emit just one value around StructuredName, as external importers
// 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 isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (isPrimary != null && isPrimary > 0 &&
+ containsNonEmptyName(contentValues)) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null &&
+ containsNonEmptyName(contentValues)) {
+ 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
@@ -1140,13 +1173,39 @@
if (TextUtils.isEmpty(phoneNumber)) {
continue;
}
- phoneLineExists = true;
int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
- // 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);
+
+ phoneLineExists = true;
+ if (type == Phone.TYPE_PAGER) {
+ phoneLineExists = true;
+ if (!phoneSet.contains(phoneNumber)) {
+ phoneSet.add(phoneNumber);
+ appendVCardTelephoneLine(builder, type, label, phoneNumber);
+ }
+ } else {
+ // The entry "may" have several phone numbers when the contact entry is
+ // corrupted because of its original source.
+ //
+ // e.g. I encountered the entry like the following.
+ // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
+ // This kind of entry is not able to be inserted via Android devices, but
+ // possible if the source of the data is already corrupted.
+ List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ if (phoneNumberList.isEmpty()) {
+ continue;
+ }
+ phoneLineExists = true;
+ for (String actualPhoneNumber : phoneNumberList) {
+ if (!phoneSet.contains(actualPhoneNumber)) {
+ final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+ SpannableStringBuilder tmpBuilder =
+ new SpannableStringBuilder(actualPhoneNumber);
+ PhoneNumberUtils.formatNumber(tmpBuilder, format);
+ final String formattedPhoneNumber = tmpBuilder.toString();
+ phoneSet.add(actualPhoneNumber);
+ appendVCardTelephoneLine(builder, type, label, formattedPhoneNumber);
+ }
+ }
}
}
}
@@ -1156,6 +1215,27 @@
}
}
+ private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+ List<String> phoneList = new ArrayList<String>();
+
+ StringBuilder builder = new StringBuilder();
+ final int length = phoneNumber.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = phoneNumber.charAt(i);
+ if (Character.isDigit(ch)) {
+ builder.append(ch);
+ } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+ phoneList.add(builder.toString());
+ builder = new StringBuilder();
+ }
+ }
+ if (builder.length() > 0) {
+ phoneList.add(builder.toString());
+ }
+
+ return phoneList;
+ }
+
private void appendEmails(final StringBuilder builder,
final Map<String, List<ContentValues>> contentValuesListMap) {
final List<ContentValues> contentValuesList = contentValuesListMap
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..11b3888 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -16,6 +16,8 @@
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
import android.pim.vcard.exception.VCardNestedException;
import android.pim.vcard.exception.VCardNotSupportedException;
import android.pim.vcard.exception.VCardVersionException;
@@ -52,7 +54,7 @@
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
/** Store the property names available in vCard 2.1 */
- private static final HashSet<String> sAvailablePropertyNameV21 =
+ private static final HashSet<String> sAvailablePropertyNameSetV21 =
new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
@@ -152,7 +154,7 @@
* @return true when the propertyName is a valid property name.
*/
protected boolean isValidPropertyName(String propertyName) {
- if (!(sAvailablePropertyNameV21.contains(propertyName.toUpperCase()) ||
+ if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
propertyName.startsWith("X-")) &&
!mWarningValueMap.contains(propertyName)) {
mWarningValueMap.add(propertyName);
@@ -342,7 +344,12 @@
mBuilder.startProperty();
mTimeStartProperty += System.currentTimeMillis() - start;
}
- ended = parseItem();
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
if (mBuilder != null && !ended) {
long start = System.currentTimeMillis();
mBuilder.endProperty();
@@ -369,7 +376,7 @@
return true;
}
if (propertyNameAndValue.length != 2) {
- throw new VCardException("Invalid line \"" + line + "\"");
+ throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
}
String propertyName = propertyNameAndValue[0].toUpperCase();
String propertyValue = propertyNameAndValue[1];
@@ -418,7 +425,11 @@
int nameIndex = 0;
String[] propertyNameAndValue = new String[2];
-
+
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
for (int i = 0; i < length; i++) {
char ch = line.charAt(i);
switch (state) {
@@ -483,7 +494,7 @@
}
}
- throw new VCardException("Invalid line: \"" + line + "\"");
+ throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
}
@@ -527,7 +538,7 @@
/**
* ptypeval = knowntype / "X-" word
*/
- protected void handleType(String ptypeval) {
+ protected void handleType(final String ptypeval) {
String upperTypeValue = ptypeval;
if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
!mWarningValueMap.contains(ptypeval)) {
@@ -543,7 +554,7 @@
/**
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
- protected void handleValue(String pvalueval) throws VCardException {
+ protected void handleValue(final String pvalueval) throws VCardException {
if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
pvalueval.startsWith("X-")) {
if (mBuilder != null) {
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..dd44288 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -20,6 +20,7 @@
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import java.util.Collection;
@@ -168,6 +169,14 @@
return list;
}
+ public static int getPhoneNumberFormat(final int vcardType) {
+ if (VCardConfig.isJapaneseDevice(vcardType)) {
+ return PhoneNumberUtils.FORMAT_JAPAN;
+ } else {
+ return PhoneNumberUtils.FORMAT_NANP;
+ }
+ }
+
/**
* Inserts postal data into the builder object.
*
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
new file mode 100644
index 0000000..67db62c
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.pim.vcard.exception;
+
+/**
+ * Thrown when the vCard has some line starting with '#'. In the specification,
+ * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
+ * such lines.
+ */
+public class VCardInvalidCommentLineException extends VCardInvalidLineException {
+ public VCardInvalidCommentLineException() {
+ super();
+ }
+
+ public VCardInvalidCommentLineException(final String message) {
+ super(message);
+ }
+}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
new file mode 100644
index 0000000..330153e
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.pim.vcard.exception;
+
+/**
+ * Thrown when the vCard has some line starting with '#'. In the specification,
+ * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
+ * such lines.
+ */
+public class VCardInvalidLineException extends VCardException {
+ public VCardInvalidLineException() {
+ super();
+ }
+
+ public VCardInvalidLineException(final String message) {
+ super(message);
+ }
+}
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index 65663b1..05b498c 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -269,7 +269,7 @@
if (mPrivateOutput == null) {
// there are 3 bytes operation headers and 3 bytes body headers //
- mPrivateOutput = new PrivateOutputStream(this, mMaxPacketSize - 6);
+ mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
}
mPrivateOutputOpen = true;
@@ -278,7 +278,13 @@
}
public int getMaxPacketSize() {
- return mMaxPacketSize - 6;
+ return mMaxPacketSize - 6 - getHeaderLength();
+ }
+
+ public int getHeaderLength() {
+ // OPP may need it
+ byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
+ return headerArray.length;
}
/**
diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java
index 20653f2..25656ed 100644
--- a/obex/javax/obex/Operation.java
+++ b/obex/javax/obex/Operation.java
@@ -163,6 +163,8 @@
long getLength();
+ int getHeaderLength();
+
String getType();
InputStream openInputStream() throws IOException;
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index 504fe35..07a3a53 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -124,21 +124,30 @@
* It is a PUT request.
*/
mGetOperation = false;
- } else {
+
+ /*
+ * Determine if the final bit is set
+ */
+ if ((request & 0x80) == 0) {
+ finalBitSet = false;
+ } else {
+ finalBitSet = true;
+ mRequestFinished = true;
+ }
+ } else if ((request == 0x03) || (request == 0x83)) {
/*
* It is a GET request.
*/
mGetOperation = true;
- }
- /*
- * Determine if the final bit is set
- */
- if ((request & 0x80) == 0) {
+ // For Get request, final bit set is decided by server side logic
finalBitSet = false;
+
+ if (request == 0x83) {
+ mRequestFinished = true;
+ }
} else {
- finalBitSet = true;
- mRequestFinished = true;
+ throw new IOException("ServerOperation can not handle such request");
}
int length = in.read();
@@ -216,12 +225,9 @@
}
// wait for get request finished !!!!
- while (mGetOperation && !finalBitSet) {
+ while (mGetOperation && !mRequestFinished) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
}
- if (finalBitSet && mGetOperation) {
- mRequestFinished = true;
- }
}
public boolean isValidBody() {
@@ -333,6 +339,12 @@
out.write(headerArray);
}
+ // For Get operation: if response code is OBEX_HTTP_OK, then this is the
+ // last packet; so set finalBitSet to true.
+ if (mGetOperation && type == ResponseCodes.OBEX_HTTP_OK) {
+ finalBitSet = true;
+ }
+
if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
if (bodyLength > 0) {
/*
@@ -410,9 +422,10 @@
}
} else {
- if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)
- || (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
+ if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
finalBitSet = true;
+ } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
+ mRequestFinished = true;
}
/*
@@ -584,7 +597,20 @@
}
public int getMaxPacketSize() {
- return mMaxPacketLength - 6;
+ return mMaxPacketLength - 6 - getHeaderLength();
+ }
+
+ public int getHeaderLength() {
+ long id = mListener.getConnectionId();
+ if (id == -1) {
+ replyHeader.mConnectionID = null;
+ } else {
+ replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
+ }
+
+ byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
+
+ return headerArray.length;
}
/**
@@ -623,7 +649,7 @@
}
if (mPrivateOutput == null) {
- mPrivateOutput = new PrivateOutputStream(this, mMaxPacketLength - 6);
+ mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
}
mPrivateOutputOpen = true;
return mPrivateOutput;