diff options
| author | Jean-Baptiste Queru <jbq@google.com> | 2009-05-20 11:28:04 -0700 |
|---|---|---|
| committer | Jean-Baptiste Queru <jbq@google.com> | 2009-05-20 11:28:04 -0700 |
| commit | 843ef36f7b96cc19ea7d2996b7c8661b41ec3452 (patch) | |
| tree | 560e1648c99a93986f8b7deef851ef8bb8029db7 /core/java/android/content | |
| parent | 358d23017d0d6c4636eb7599ae7a9b48108899a3 (diff) | |
donut snapshot
Diffstat (limited to 'core/java/android/content')
26 files changed, 2986 insertions, 1483 deletions
diff --git a/core/java/android/content/ActiveSyncInfo.aidl b/core/java/android/content/ActiveSyncInfo.aidl new file mode 100644 index 000000000000..1142206d13a8 --- /dev/null +++ b/core/java/android/content/ActiveSyncInfo.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.content; + +parcelable ActiveSyncInfo; diff --git a/core/java/android/content/ActiveSyncInfo.java b/core/java/android/content/ActiveSyncInfo.java new file mode 100644 index 000000000000..63be8d19250b --- /dev/null +++ b/core/java/android/content/ActiveSyncInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Parcel; +import android.os.Parcelable.Creator; + +/** @hide */ +public class ActiveSyncInfo { + public final int authorityId; + public final String account; + public final String authority; + public final long startTime; + + ActiveSyncInfo(int authorityId, String account, String authority, + long startTime) { + this.authorityId = authorityId; + this.account = account; + this.authority = authority; + this.startTime = startTime; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(authorityId); + parcel.writeString(account); + parcel.writeString(authority); + parcel.writeLong(startTime); + } + + ActiveSyncInfo(Parcel parcel) { + authorityId = parcel.readInt(); + account = parcel.readString(); + authority = parcel.readString(); + startTime = parcel.readLong(); + } + + public static final Creator<ActiveSyncInfo> CREATOR = new Creator<ActiveSyncInfo>() { + public ActiveSyncInfo createFromParcel(Parcel in) { + return new ActiveSyncInfo(in); + } + + public ActiveSyncInfo[] newArray(int size) { + return new ActiveSyncInfo[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index 32c68647709a..045520216ca2 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -18,6 +18,7 @@ package android.content; import android.os.Parcel; import android.os.Parcelable; +import java.lang.Comparable; /** * Identifier for a specific application component @@ -29,7 +30,7 @@ import android.os.Parcelable; * name inside of that package. * */ -public final class ComponentName implements Parcelable { +public final class ComponentName implements Parcelable, Comparable<ComponentName> { private final String mPackage; private final String mClass; @@ -196,6 +197,15 @@ public final class ComponentName implements Parcelable { public int hashCode() { return mPackage.hashCode() + mClass.hashCode(); } + + public int compareTo(ComponentName that) { + int v; + v = this.mPackage.compareTo(that.mPackage); + if (v != 0) { + return v; + } + return this.mClass.compareTo(that.mClass); + } public int describeContents() { return 0; diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0d886ee23e8d..74144fc5608f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -25,9 +25,13 @@ import android.database.CursorWrapper; import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceManager; import android.text.TextUtils; +import android.util.Config; +import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -85,8 +89,7 @@ public abstract class ContentResolver { */ public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir"; - public ContentResolver(Context context) - { + public ContentResolver(Context context) { mContext = context; } @@ -541,7 +544,7 @@ public abstract class ContentResolver { A null value will remove an existing field value. * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause (excluding the WHERE itself). - * @return the URL of the newly created row + * @return The number of rows updated. * @throws NullPointerException if uri or values are null */ public final int update(Uri uri, ContentValues values, String where, @@ -605,7 +608,7 @@ public abstract class ContentResolver { ContentObserver observer) { try { - ContentServiceNative.getDefault().registerContentObserver(uri, notifyForDescendents, + getContentService().registerContentObserver(uri, notifyForDescendents, observer.getContentObserver()); } catch (RemoteException e) { } @@ -621,7 +624,7 @@ public abstract class ContentResolver { try { IContentObserver contentObserver = observer.releaseContentObserver(); if (contentObserver != null) { - ContentServiceNative.getDefault().unregisterContentObserver( + getContentService().unregisterContentObserver( contentObserver); } } catch (RemoteException e) { @@ -651,7 +654,7 @@ public abstract class ContentResolver { */ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { try { - ContentServiceNative.getDefault().notifyChange( + getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), observer != null && observer.deliverSelfNotifications(), syncToNetwork); } catch (RemoteException e) { @@ -677,7 +680,7 @@ public abstract class ContentResolver { public void startSync(Uri uri, Bundle extras) { validateSyncExtrasBundle(extras); try { - ContentServiceNative.getDefault().startSync(uri, extras); + getContentService().startSync(uri, extras); } catch (RemoteException e) { } } @@ -718,7 +721,7 @@ public abstract class ContentResolver { public void cancelSync(Uri uri) { try { - ContentServiceNative.getDefault().cancelSync(uri); + getContentService().cancelSync(uri); } catch (RemoteException e) { } } @@ -779,6 +782,22 @@ public abstract class ContentResolver { } } + /** @hide */ + public static final String CONTENT_SERVICE_NAME = "content"; + + /** @hide */ + public static IContentService getContentService() { + if (sContentService != null) { + return sContentService; + } + IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME); + if (Config.LOGV) Log.v("ContentService", "default service binder = " + b); + sContentService = IContentService.Stub.asInterface(b); + if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService); + return sContentService; + } + + private static IContentService sContentService; private final Context mContext; private static final String TAG = "ContentResolver"; } diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index b0288681b2b7..6cd2c54a265c 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Config; @@ -34,7 +35,7 @@ import java.util.ArrayList; /** * {@hide} */ -public final class ContentService extends ContentServiceNative { +public final class ContentService extends IContentService.Stub { private static final String TAG = "ContentService"; private Context mContext; private boolean mFactoryTest; @@ -73,6 +74,21 @@ public final class ContentService extends ContentServiceNative { } } + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // The content service only throws security exceptions, so let's + // log all others. + if (!(e instanceof SecurityException)) { + Log.e(TAG, "Content Service Crash", e); + } + throw e; + } + } + /*package*/ ContentService(Context context, boolean factoryTest) { mContext = context; mFactoryTest = factoryTest; @@ -204,9 +220,158 @@ public final class ContentService extends ContentServiceNative { } } + public boolean getSyncProviderAutomatically(String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getSyncProviderAutomatically( + null, providerName); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setSyncProviderAutomatically(String providerName, boolean sync) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setSyncProviderAutomatically( + null, providerName, sync); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean getListenForNetworkTickles() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getListenForNetworkTickles(); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setListenForNetworkTickles(boolean flag) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setListenForNetworkTickles(flag); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean isSyncActive(String account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isSyncActive( + account, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public ActiveSyncInfo getActiveSync() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getActiveSync(); + } + } finally { + restoreCallingIdentity(identityToken); + } + return null; + } + + public SyncStatusInfo getStatusByAuthority(String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getStatusByAuthority( + authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return null; + } + + public boolean isAuthorityPending(String account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isAuthorityPending( + account, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().addStatusChangeListener( + mask, callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removeStatusChangeListener(ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().removeStatusChangeListener( + callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + public static IContentService main(Context context, boolean factoryTest) { ContentService service = new ContentService(context, factoryTest); - ServiceManager.addService("content", service); + ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service); return service; } diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java deleted file mode 100644 index 364f9ee8a29d..000000000000 --- a/core/java/android/content/ContentServiceNative.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2006 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.content; - -import android.database.IContentObserver; -import android.net.Uri; -import android.os.Binder; -import android.os.RemoteException; -import android.os.IBinder; -import android.os.Parcel; -import android.os.ServiceManager; -import android.os.Bundle; -import android.util.Config; -import android.util.Log; - -/** - * {@hide} - */ -abstract class ContentServiceNative extends Binder implements IContentService -{ - public ContentServiceNative() - { - attachInterface(this, descriptor); - } - - /** - * Cast a Binder object into a content resolver interface, generating - * a proxy if needed. - */ - static public IContentService asInterface(IBinder obj) - { - if (obj == null) { - return null; - } - IContentService in = - (IContentService)obj.queryLocalInterface(descriptor); - if (in != null) { - return in; - } - - return new ContentServiceProxy(obj); - } - - /** - * Retrieve the system's default/global content service. - */ - static public IContentService getDefault() - { - if (gDefault != null) { - return gDefault; - } - IBinder b = ServiceManager.getService("content"); - if (Config.LOGV) Log.v("ContentService", "default service binder = " + b); - gDefault = asInterface(b); - if (Config.LOGV) Log.v("ContentService", "default service = " + gDefault); - return gDefault; - } - - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - { - try { - switch (code) { - case 5038: { - data.readString(); // ignore the interface token that service generated - Uri uri = Uri.parse(data.readString()); - notifyChange(uri, null, false, false); - return true; - } - - case REGISTER_CONTENT_OBSERVER_TRANSACTION: { - Uri uri = Uri.CREATOR.createFromParcel(data); - boolean notifyForDescendents = data.readInt() != 0; - IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder()); - registerContentObserver(uri, notifyForDescendents, observer); - return true; - } - - case UNREGISTER_CHANGE_OBSERVER_TRANSACTION: { - IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder()); - unregisterContentObserver(observer); - return true; - } - - case NOTIFY_CHANGE_TRANSACTION: { - Uri uri = Uri.CREATOR.createFromParcel(data); - IContentObserver observer = IContentObserver.Stub.asInterface(data.readStrongBinder()); - boolean observerWantsSelfNotifications = data.readInt() != 0; - boolean syncToNetwork = data.readInt() != 0; - notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork); - return true; - } - - case START_SYNC_TRANSACTION: { - Uri url = null; - int hasUrl = data.readInt(); - if (hasUrl != 0) { - url = Uri.CREATOR.createFromParcel(data); - } - startSync(url, data.readBundle()); - return true; - } - - case CANCEL_SYNC_TRANSACTION: { - Uri url = null; - int hasUrl = data.readInt(); - if (hasUrl != 0) { - url = Uri.CREATOR.createFromParcel(data); - } - cancelSync(url); - return true; - } - - default: - return super.onTransact(code, data, reply, flags); - } - } catch (Exception e) { - Log.e("ContentServiceNative", "Caught exception in transact", e); - } - - return false; - } - - public IBinder asBinder() - { - return this; - } - - private static IContentService gDefault; -} - - -final class ContentServiceProxy implements IContentService -{ - public ContentServiceProxy(IBinder remote) - { - mRemote = remote; - } - - public IBinder asBinder() - { - return mRemote; - } - - public void registerContentObserver(Uri uri, boolean notifyForDescendents, - IContentObserver observer) throws RemoteException - { - Parcel data = Parcel.obtain(); - uri.writeToParcel(data, 0); - data.writeInt(notifyForDescendents ? 1 : 0); - data.writeStrongInterface(observer); - mRemote.transact(REGISTER_CONTENT_OBSERVER_TRANSACTION, data, null, 0); - data.recycle(); - } - - public void unregisterContentObserver(IContentObserver observer) throws RemoteException { - Parcel data = Parcel.obtain(); - data.writeStrongInterface(observer); - mRemote.transact(UNREGISTER_CHANGE_OBSERVER_TRANSACTION, data, null, 0); - data.recycle(); - } - - public void notifyChange(Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, boolean syncToNetwork) - throws RemoteException { - Parcel data = Parcel.obtain(); - uri.writeToParcel(data, 0); - data.writeStrongInterface(observer); - data.writeInt(observerWantsSelfNotifications ? 1 : 0); - data.writeInt(syncToNetwork ? 1 : 0); - mRemote.transact(NOTIFY_CHANGE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); - data.recycle(); - } - - public void startSync(Uri url, Bundle extras) throws RemoteException { - Parcel data = Parcel.obtain(); - if (url == null) { - data.writeInt(0); - } else { - data.writeInt(1); - url.writeToParcel(data, 0); - } - extras.writeToParcel(data, 0); - mRemote.transact(START_SYNC_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); - data.recycle(); - } - - public void cancelSync(Uri url) throws RemoteException { - Parcel data = Parcel.obtain(); - if (url == null) { - data.writeInt(0); - } else { - data.writeInt(1); - url.writeToParcel(data, 0); - } - mRemote.transact(CANCEL_SYNC_TRANSACTION, data, null /* reply */, IBinder.FLAG_ONEWAY); - data.recycle(); - } - - private IBinder mRemote; -} - diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 600dfa40abc4..f2ad2485d0e1 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -527,6 +527,16 @@ public abstract class Context { public abstract int getWallpaperDesiredMinimumHeight(); /** + * Returns the scale in which the application will be drawn on the + * screen. This is usually 1.0f if the application supports the device's + * resolution/density. This will be 1.5f, for example, if the application + * that supports only 160 density runs on 240 density screen. + * + * @hide + */ + public abstract float getApplicationScale(); + + /** * Change the current system wallpaper to a bitmap. The given bitmap is * converted to a PNG and stored as the wallpaper. On success, the intent * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. @@ -1259,6 +1269,15 @@ public abstract class Context { public static final String APPWIDGET_SERVICE = "appwidget"; /** + * Use with {@link #getSystemService} to retrieve an + * {@blink android.backup.IBackupManager IBackupManager} for communicating + * with the backup mechanism. + * + * @see #getSystemService + */ + public static final String BACKUP_SERVICE = "backup"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 36e1c340d1d9..25b2caeb7279 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -419,4 +419,12 @@ public class ContextWrapper extends Context { throws PackageManager.NameNotFoundException { return mBase.createPackageContext(packageName, flags); } + + /** + * @hide + */ + @Override + public float getApplicationScale() { + return mBase.getApplicationScale(); + } } diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl new file mode 100644 index 000000000000..8617d949c4fb --- /dev/null +++ b/core/java/android/content/IContentService.aidl @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.content.ActiveSyncInfo; +import android.content.ISyncStatusObserver; +import android.content.SyncStatusInfo; +import android.net.Uri; +import android.os.Bundle; +import android.database.IContentObserver; + +/** + * @hide + */ +interface IContentService { + void registerContentObserver(in Uri uri, boolean notifyForDescendentsn, + IContentObserver observer); + void unregisterContentObserver(IContentObserver observer); + + void notifyChange(in Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork); + + void startSync(in Uri url, in Bundle extras); + void cancelSync(in Uri uri); + + /** + * Check if the provider should be synced when a network tickle is received + * @param providerName the provider whose setting we are querying + * @return true of the provider should be synced when a network tickle is received + */ + boolean getSyncProviderAutomatically(String providerName); + + /** + * Set whether or not the provider is synced when it receives a network tickle. + * + * @param providerName the provider whose behavior is being controlled + * @param sync true if the provider should be synced when tickles are received for it + */ + void setSyncProviderAutomatically(String providerName, boolean sync); + + void setListenForNetworkTickles(boolean flag); + + boolean getListenForNetworkTickles(); + + /** + * Returns true if there is currently a sync operation for the given + * account or authority in the pending list, or actively being processed. + */ + boolean isSyncActive(String account, String authority); + + ActiveSyncInfo getActiveSync(); + + /** + * Returns the status that matches the authority. If there are multiples accounts for + * the authority, the one with the latest "lastSuccessTime" status is returned. + * @param authority the authority whose row should be selected + * @return the SyncStatusInfo for the authority, or null if none exists + */ + SyncStatusInfo getStatusByAuthority(String authority); + + /** + * Return true if the pending status is true of any matching authorities. + */ + boolean isAuthorityPending(String account, String authority); + + void addStatusChangeListener(int mask, ISyncStatusObserver callback); + + void removeStatusChangeListener(ISyncStatusObserver callback); +} diff --git a/core/java/android/content/IContentService.java b/core/java/android/content/IContentService.java deleted file mode 100644 index a3047da66c4e..000000000000 --- a/core/java/android/content/IContentService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2006 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.content; - -import android.database.IContentObserver; -import android.net.Uri; -import android.os.RemoteException; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Bundle; - -/** - * {@hide} - */ -public interface IContentService extends IInterface -{ - public void registerContentObserver(Uri uri, boolean notifyForDescendentsn, - IContentObserver observer) throws RemoteException; - public void unregisterContentObserver(IContentObserver observer) throws RemoteException; - - public void notifyChange(Uri uri, IContentObserver observer, - boolean observerWantsSelfNotifications, boolean syncToNetwork) - throws RemoteException; - - public void startSync(Uri url, Bundle extras) throws RemoteException; - public void cancelSync(Uri uri) throws RemoteException; - - static final String SERVICE_NAME = "content"; - - /* IPC constants */ - static final String descriptor = "android.content.IContentService"; - - static final int REGISTER_CONTENT_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1; - static final int UNREGISTER_CHANGE_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2; - static final int NOTIFY_CHANGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3; - static final int START_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4; - static final int CANCEL_SYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5; -} - diff --git a/core/java/android/content/ISyncStatusObserver.aidl b/core/java/android/content/ISyncStatusObserver.aidl new file mode 100644 index 000000000000..eb2684544abf --- /dev/null +++ b/core/java/android/content/ISyncStatusObserver.aidl @@ -0,0 +1,24 @@ +/* + * 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.content; + +/** + * @hide + */ +oneway interface ISyncStatusObserver { + void onStatusChanged(int which); +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 99cf34c2ca57..24262f51197d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -508,6 +508,9 @@ import java.util.Set; * <li> {@link #ACTION_PACKAGE_DATA_CLEARED} * <li> {@link #ACTION_UID_REMOVED} * <li> {@link #ACTION_BATTERY_CHANGED} + * <li> {@link #ACTION_POWER_CONNECTED} + * <li> {@link #ACTION_POWER_DISCONNECTED} + * <li> {@link #ACTION_SHUTDOWN} * </ul> * * <h3>Standard Categories</h3> @@ -1045,6 +1048,17 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; + /** + * Activity Action: The user pressed the "Report" button in the crash/ANR dialog. + * This intent is delivered to the package which installed the application, usually + * the Market. + * <p>Input: No data is specified. The bug report is passed in using + * an {@link #EXTRA_BUG_REPORT} field. + * <p>Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -1250,6 +1264,33 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW"; /** + * Broadcast Action: External power has been connected to the device. + * This is intended for applications that wish to register specifically to this notification. + * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to + * stay active to receive this notification. This action can be used to implement actions + * that wait until power is available to trigger. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED"; + /** + * Broadcast Action: External power has been removed from the device. + * This is intended for applications that wish to register specifically to this notification. + * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to + * stay active to receive this notification. This action can be used to implement actions + * that wait until power is available to trigger. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED"; + /** + * Broadcast Action: Device is shutting down. + * This is broadcast when the device is being shut down (completely turned + * off, not sleeping). Once the broadcast is complete, the final shutdown + * will proceed and all unsaved data lost. Apps will not normally need + * to handle this, since the forground activity will be paused as well. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + /** * Broadcast Action: Indicates low memory condition on the device */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -1750,6 +1791,24 @@ public class Intent implements Parcelable { * delivered. */ public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; + + /** + * Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing + * the bug report. + * + * @hide + */ + public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT"; + + /** + * Used as a string extra field when sending an intent to PackageInstaller to install a + * package. Specifies the installer package name; this package will receive the + * {@link #ACTION_APP_ERROR} intent. + * + * @hide + */ + public static final String EXTRA_INSTALLER_PACKAGE_NAME + = "android.intent.extra.INSTALLER_PACKAGE_NAME"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- @@ -1910,7 +1969,7 @@ public class Intent implements Parcelable { /** * If set, this marks a point in the task's activity stack that should * be cleared when the task is reset. That is, the next time the task - * is broad to the foreground with + * is brought to the foreground with * {@link #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED} (typically as a result of * the user re-launching it from home), this activity and all on top of * it will be finished so that the user does not return to them, but @@ -4370,12 +4429,35 @@ public class Intent implements Parcelable { @Override public String toString() { - StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(128); + + b.append("Intent { "); + toShortString(b, true, true); + b.append(" }"); - b.append("Intent {"); - if (mAction != null) b.append(" action=").append(mAction); + return b.toString(); + } + + /** @hide */ + public String toShortString(boolean comp, boolean extras) { + StringBuilder b = new StringBuilder(128); + toShortString(b, comp, extras); + return b.toString(); + } + + /** @hide */ + public void toShortString(StringBuilder b, boolean comp, boolean extras) { + boolean first = true; + if (mAction != null) { + b.append("act=").append(mAction); + first = false; + } if (mCategories != null) { - b.append(" categories={"); + if (!first) { + b.append(' '); + } + first = false; + b.append("cat=["); Iterator<String> i = mCategories.iterator(); boolean didone = false; while (i.hasNext()) { @@ -4383,20 +4465,48 @@ public class Intent implements Parcelable { didone = true; b.append(i.next()); } - b.append("}"); + b.append("]"); + } + if (mData != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("dat=").append(mData); + } + if (mType != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("typ=").append(mType); + } + if (mFlags != 0) { + if (!first) { + b.append(' '); + } + first = false; + b.append("flg=0x").append(Integer.toHexString(mFlags)); + } + if (comp && mComponent != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("cmp=").append(mComponent.flattenToShortString()); + } + if (extras && mExtras != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("(has extras)"); } - if (mData != null) b.append(" data=").append(mData); - if (mType != null) b.append(" type=").append(mType); - if (mFlags != 0) b.append(" flags=0x").append(Integer.toHexString(mFlags)); - if (mComponent != null) b.append(" comp=").append(mComponent.toShortString()); - if (mExtras != null) b.append(" (has extras)"); - b.append(" }"); - - return b.toString(); } public String toURI() { - StringBuilder uri = new StringBuilder(mData != null ? mData.toString() : ""); + StringBuilder uri = new StringBuilder(128); + if (mData != null) uri.append(mData.toString()); uri.append("#Intent;"); diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 9b190df84cd4..e5c5dc8a5e0c 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1254,47 +1254,71 @@ public class IntentFilter implements Parcelable { } public void dump(Printer du, String prefix) { + StringBuilder sb = new StringBuilder(256); if (mActions.size() > 0) { Iterator<String> it = mActions.iterator(); while (it.hasNext()) { - du.println(prefix + "Action: \"" + it.next() + "\""); + sb.setLength(0); + sb.append(prefix); sb.append("Action: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); } } if (mCategories != null) { Iterator<String> it = mCategories.iterator(); while (it.hasNext()) { - du.println(prefix + "Category: \"" + it.next() + "\""); + sb.setLength(0); + sb.append(prefix); sb.append("Category: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); } } if (mDataSchemes != null) { Iterator<String> it = mDataSchemes.iterator(); while (it.hasNext()) { - du.println(prefix + "Data Scheme: \"" + it.next() + "\""); + sb.setLength(0); + sb.append(prefix); sb.append("Scheme: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); } } if (mDataAuthorities != null) { Iterator<AuthorityEntry> it = mDataAuthorities.iterator(); while (it.hasNext()) { AuthorityEntry ae = it.next(); - du.println(prefix + "Data Authority: \"" + ae.mHost + "\":" - + ae.mPort + (ae.mWild ? " WILD" : "")); + sb.setLength(0); + sb.append(prefix); sb.append("Authority: \""); + sb.append(ae.mHost); sb.append("\": "); + sb.append(ae.mPort); + if (ae.mWild) sb.append(" WILD"); + du.println(sb.toString()); } } if (mDataPaths != null) { Iterator<PatternMatcher> it = mDataPaths.iterator(); while (it.hasNext()) { PatternMatcher pe = it.next(); - du.println(prefix + "Data Path: \"" + pe + "\""); + sb.setLength(0); + sb.append(prefix); sb.append("Path: \""); + sb.append(pe); sb.append("\""); + du.println(sb.toString()); } } if (mDataTypes != null) { Iterator<String> it = mDataTypes.iterator(); while (it.hasNext()) { - du.println(prefix + "Data Type: \"" + it.next() + "\""); + sb.setLength(0); + sb.append(prefix); sb.append("Type: \""); + sb.append(it.next()); sb.append("\""); + du.println(sb.toString()); } } - du.println(prefix + "mPriority=" + mPriority - + ", mHasPartialTypes=" + mHasPartialTypes); + if (mPriority != 0 || mHasPartialTypes) { + sb.setLength(0); + sb.append(prefix); sb.append("mPriority="); sb.append(mPriority); + sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes); + du.println(sb.toString()); + } } public static final Parcelable.Creator<IntentFilter> CREATOR diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 96470c3c5756..4d2cce881897 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -32,8 +32,6 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -43,18 +41,13 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.provider.Sync; import android.provider.Settings; -import android.provider.Sync.History; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Time; @@ -73,13 +66,12 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Random; -import java.util.Observer; -import java.util.Observable; /** * @hide @@ -138,7 +130,6 @@ class SyncManager { volatile private PowerManager.WakeLock mHandleAlarmWakeLock; volatile private boolean mDataConnectionIsConnected = false; volatile private boolean mStorageIsLow = false; - private Sync.Settings.QueryMap mSyncSettings; private final NotificationManager mNotificationMgr; private AlarmManager mAlarmService = null; @@ -221,21 +212,18 @@ class SyncManager { } }; + private BroadcastReceiver mShutdownIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "Writing sync state before shutdown..."); + getSyncStorageEngine().writeAllState(); + } + }; + private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM"; private final SyncHandler mSyncHandler; - private static final String[] SYNC_ACTIVE_PROJECTION = new String[]{ - Sync.Active.ACCOUNT, - Sync.Active.AUTHORITY, - Sync.Active.START_TIME, - }; - - private static final String[] SYNC_PENDING_PROJECTION = new String[]{ - Sync.Pending.ACCOUNT, - Sync.Pending.AUTHORITY - }; - private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours @@ -269,6 +257,10 @@ class SyncManager { intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + intentFilter.setPriority(100); + context.registerReceiver(mShutdownIntentReceiver, intentFilter); + if (!factoryTest) { mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -289,6 +281,14 @@ class SyncManager { HANDLE_SYNC_ALARM_WAKE_LOCK); mHandleAlarmWakeLock.setReferenceCounted(false); + mSyncStorageEngine.addStatusChangeListener( + SyncStorageEngine.CHANGE_SETTINGS, new ISyncStatusObserver.Stub() { + public void onStatusChanged(int which) { + // force the sync loop to run if the settings change + sendCheckAlarmsMessage(); + } + }); + if (!factoryTest) { AccountMonitorListener listener = new AccountMonitorListener() { public void onAccountsUpdated(String[] accounts) { @@ -448,20 +448,10 @@ class SyncManager { return mActiveSyncContext; } - private Sync.Settings.QueryMap getSyncSettings() { - if (mSyncSettings == null) { - mSyncSettings = new Sync.Settings.QueryMap(mContext.getContentResolver(), true, - new Handler()); - mSyncSettings.addObserver(new Observer(){ - public void update(Observable o, Object arg) { - // force the sync loop to run if the settings change - sendCheckAlarmsMessage(); - } - }); - } - return mSyncSettings; + public SyncStorageEngine getSyncStorageEngine() { + return mSyncStorageEngine; } - + private void ensureContentResolver() { if (mContentResolver == null) { mContentResolver = mContext.getContentResolver(); @@ -574,15 +564,15 @@ class SyncManager { int source; if (uploadOnly) { - source = Sync.History.SOURCE_LOCAL; + source = SyncStorageEngine.SOURCE_LOCAL; } else if (force) { - source = Sync.History.SOURCE_USER; + source = SyncStorageEngine.SOURCE_USER; } else if (url == null) { - source = Sync.History.SOURCE_POLL; + source = SyncStorageEngine.SOURCE_POLL; } else { // this isn't strictly server, since arbitrary callers can (and do) request // a non-forced two-way sync on a specific url - source = Sync.History.SOURCE_SERVER; + source = SyncStorageEngine.SOURCE_SERVER; } List<String> names = new ArrayList<String>(); @@ -667,9 +657,7 @@ class SyncManager { public void updateHeartbeatTime() { mHeartbeatTime = SystemClock.elapsedRealtime(); - ensureContentResolver(); - mContentResolver.notifyChange(Sync.Active.CONTENT_URI, - null /* this change wasn't made through an observer */); + mSyncStorageEngine.reportActiveChange(); } private void sendSyncAlarmMessage() { @@ -876,7 +864,7 @@ class SyncManager { final String key; long earliestRunTime; long delay; - Long rowId = null; + SyncStorageEngine.PendingOperation pendingOperation; SyncOperation(String account, int source, String authority, Bundle extras, long delay) { this.account = account; @@ -916,7 +904,7 @@ class SyncManager { sb.append(" when: ").append(earliestRunTime); sb.append(" delay: ").append(delay); sb.append(" key: {").append(key).append("}"); - if (rowId != null) sb.append(" rowId: ").append(rowId); + if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation); return sb.toString(); } @@ -999,244 +987,264 @@ class SyncManager { protected void dump(FileDescriptor fd, PrintWriter pw) { StringBuilder sb = new StringBuilder(); - dumpSyncState(sb); - sb.append("\n"); + dumpSyncState(pw, sb); if (isSyncEnabled()) { - dumpSyncHistory(sb); + dumpSyncHistory(pw, sb); } - pw.println(sb.toString()); } - protected void dumpSyncState(StringBuilder sb) { - sb.append("sync enabled: ").append(isSyncEnabled()).append("\n"); - sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n"); - sb.append("memory low: ").append(mStorageIsLow).append("\n"); + static String formatTime(long time) { + Time tobj = new Time(); + tobj.set(time); + return tobj.format("%Y-%m-%d %H:%M:%S"); + } + + protected void dumpSyncState(PrintWriter pw, StringBuilder sb) { + pw.print("sync enabled: "); pw.println(isSyncEnabled()); + pw.print("data connected: "); pw.println(mDataConnectionIsConnected); + pw.print("memory low: "); pw.println(mStorageIsLow); final String[] accounts = mAccounts; - sb.append("accounts: "); + pw.print("accounts: "); if (accounts != null) { - sb.append(accounts.length); + pw.println(accounts.length); } else { - sb.append("none"); + pw.println("none"); } - sb.append("\n"); final long now = SystemClock.elapsedRealtime(); - sb.append("now: ").append(now).append("\n"); - sb.append("uptime: ").append(DateUtils.formatElapsedTime(now/1000)).append(" (HH:MM:SS)\n"); - sb.append("time spent syncing : ") - .append(DateUtils.formatElapsedTime( - mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)) - .append(" (HH:MM:SS), sync ") - .append(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ") - .append("in progress").append("\n"); + pw.print("now: "); pw.println(now); + pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000)); + pw.println(" (HH:MM:SS)"); + pw.print("time spent syncing: "); + pw.print(DateUtils.formatElapsedTime( + mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); + pw.print(" (HH:MM:SS), sync "); + pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); + pw.println("in progress"); if (mSyncHandler.mAlarmScheduleTime != null) { - sb.append("next alarm time: ").append(mSyncHandler.mAlarmScheduleTime) - .append(" (") - .append(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000)) - .append(" (HH:MM:SS) from now)\n"); + pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime); + pw.print(" ("); + pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000)); + pw.println(" (HH:MM:SS) from now)"); } else { - sb.append("no alarm is scheduled (there had better not be any pending syncs)\n"); + pw.println("no alarm is scheduled (there had better not be any pending syncs)"); } - sb.append("active sync: ").append(mActiveSyncContext).append("\n"); + pw.print("active sync: "); pw.println(mActiveSyncContext); - sb.append("notification info: "); + pw.print("notification info: "); + sb.setLength(0); mSyncHandler.mSyncNotificationInfo.toString(sb); - sb.append("\n"); + pw.println(sb.toString()); synchronized (mSyncQueue) { - sb.append("sync queue: "); + pw.print("sync queue: "); + sb.setLength(0); mSyncQueue.dump(sb); - } - - Cursor c = mSyncStorageEngine.query(Sync.Active.CONTENT_URI, - SYNC_ACTIVE_PROJECTION, null, null, null); - sb.append("\n"); - try { - if (c.moveToNext()) { - final long durationInSeconds = (now - c.getLong(2)) / 1000; - sb.append("Active sync: ").append(c.getString(0)) - .append(" ").append(c.getString(1)) - .append(", duration is ") - .append(DateUtils.formatElapsedTime(durationInSeconds)).append(".\n"); - } else { - sb.append("No sync is in progress.\n"); - } - } finally { - c.close(); - } - - c = mSyncStorageEngine.query(Sync.Pending.CONTENT_URI, - SYNC_PENDING_PROJECTION, null, null, "account, authority"); - sb.append("\nPending Syncs\n"); - try { - if (c.getCount() != 0) { - dumpSyncPendingHeader(sb); - while (c.moveToNext()) { - dumpSyncPendingRow(sb, c); + pw.println(sb.toString()); + } + + ActiveSyncInfo active = mSyncStorageEngine.getActiveSync(); + if (active != null) { + SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(active.authorityId); + final long durationInSeconds = (now - active.startTime) / 1000; + pw.print("Active sync: "); + pw.print(authority != null ? authority.account : "<no account>"); + pw.print(" "); + pw.print(authority != null ? authority.authority : "<no account>"); + pw.print(", duration is "); + pw.println(DateUtils.formatElapsedTime(durationInSeconds)); + } else { + pw.println("No sync is in progress."); + } + + ArrayList<SyncStorageEngine.PendingOperation> ops + = mSyncStorageEngine.getPendingOperations(); + if (ops != null && ops.size() > 0) { + pw.println(); + pw.println("Pending Syncs"); + final int N = ops.size(); + for (int i=0; i<N; i++) { + SyncStorageEngine.PendingOperation op = ops.get(i); + pw.print(" #"); pw.print(i); pw.print(": account="); + pw.print(op.account); pw.print(" authority="); + pw.println(op.authority); + if (op.extras != null && op.extras.size() > 0) { + sb.setLength(0); + SyncOperation.extrasToStringBuilder(op.extras, sb); + pw.print(" extras: "); pw.println(sb.toString()); } - dumpSyncPendingFooter(sb); - } else { - sb.append("none\n"); } - } finally { - c.close(); } - String currentAccount = null; - c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI, - STATUS_PROJECTION, null, null, "account, authority"); - sb.append("\nSync history by account and authority\n"); - try { - while (c.moveToNext()) { - if (!TextUtils.equals(currentAccount, c.getString(0))) { - if (currentAccount != null) { - dumpSyncHistoryFooter(sb); + HashSet<String> processedAccounts = new HashSet<String>(); + ArrayList<SyncStatusInfo> statuses + = mSyncStorageEngine.getSyncStatus(); + if (statuses != null && statuses.size() > 0) { + pw.println(); + pw.println("Sync Status"); + final int N = statuses.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo status = statuses.get(i); + SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(status.authorityId); + if (authority != null) { + String curAccount = authority.account; + + if (processedAccounts.contains(curAccount)) { + continue; + } + + processedAccounts.add(curAccount); + + pw.print(" Account "); pw.print(authority.account); + pw.println(":"); + for (int j=i; j<N; j++) { + status = statuses.get(j); + authority = mSyncStorageEngine.getAuthority(status.authorityId); + if (!curAccount.equals(authority.account)) { + continue; + } + pw.print(" "); pw.print(authority.authority); + pw.println(":"); + pw.print(" count: local="); pw.print(status.numSourceLocal); + pw.print(" poll="); pw.print(status.numSourcePoll); + pw.print(" server="); pw.print(status.numSourceServer); + pw.print(" user="); pw.print(status.numSourceUser); + pw.print(" total="); pw.println(status.numSyncs); + pw.print(" total duration: "); + pw.println(DateUtils.formatElapsedTime( + status.totalElapsedTime/1000)); + if (status.lastSuccessTime != 0) { + pw.print(" SUCCESS: source="); + pw.print(SyncStorageEngine.SOURCES[ + status.lastSuccessSource]); + pw.print(" time="); + pw.println(formatTime(status.lastSuccessTime)); + } else { + pw.print(" FAILURE: source="); + pw.print(SyncStorageEngine.SOURCES[ + status.lastFailureSource]); + pw.print(" initialTime="); + pw.print(formatTime(status.initialFailureTime)); + pw.print(" lastTime="); + pw.println(formatTime(status.lastFailureTime)); + pw.print(" message: "); pw.println(status.lastFailureMesg); + } } - currentAccount = c.getString(0); - dumpSyncHistoryHeader(sb, currentAccount); } - - dumpSyncHistoryRow(sb, c); } - if (c.getCount() > 0) dumpSyncHistoryFooter(sb); - } finally { - c.close(); } } - private void dumpSyncHistoryHeader(StringBuilder sb, String account) { - sb.append(" Account: ").append(account).append("\n"); - sb.append(" ___________________________________________________________________________________________________________________________\n"); - sb.append(" | | num times synced | total | last success | |\n"); - sb.append(" | authority | local | poll | server | user | total | duration | source | time | result if failing |\n"); + private void dumpTimeSec(PrintWriter pw, long time) { + pw.print(time/1000); pw.print('.'); pw.print((time/100)%10); + pw.print('s'); } - - private static String[] STATUS_PROJECTION = new String[]{ - Sync.Status.ACCOUNT, // 0 - Sync.Status.AUTHORITY, // 1 - Sync.Status.NUM_SYNCS, // 2 - Sync.Status.TOTAL_ELAPSED_TIME, // 3 - Sync.Status.NUM_SOURCE_LOCAL, // 4 - Sync.Status.NUM_SOURCE_POLL, // 5 - Sync.Status.NUM_SOURCE_SERVER, // 6 - Sync.Status.NUM_SOURCE_USER, // 7 - Sync.Status.LAST_SUCCESS_SOURCE, // 8 - Sync.Status.LAST_SUCCESS_TIME, // 9 - Sync.Status.LAST_FAILURE_SOURCE, // 10 - Sync.Status.LAST_FAILURE_TIME, // 11 - Sync.Status.LAST_FAILURE_MESG // 12 - }; - - private void dumpSyncHistoryRow(StringBuilder sb, Cursor c) { - boolean hasSuccess = !c.isNull(9); - boolean hasFailure = !c.isNull(11); - Time timeSuccess = new Time(); - if (hasSuccess) timeSuccess.set(c.getLong(9)); - Time timeFailure = new Time(); - if (hasFailure) timeFailure.set(c.getLong(11)); - sb.append(String.format(" | %-15s | %5d | %5d | %6d | %5d | %5d | %8s | %7s | %19s | %19s |\n", - c.getString(1), - c.getLong(4), - c.getLong(5), - c.getLong(6), - c.getLong(7), - c.getLong(2), - DateUtils.formatElapsedTime(c.getLong(3)/1000), - hasSuccess ? Sync.History.SOURCES[c.getInt(8)] : "", - hasSuccess ? timeSuccess.format("%Y-%m-%d %H:%M:%S") : "", - hasFailure ? History.mesgToString(c.getString(12)) : "")); - } - - private void dumpSyncHistoryFooter(StringBuilder sb) { - sb.append(" |___________________________________________________________________________________________________________________________|\n"); - } - - private void dumpSyncPendingHeader(StringBuilder sb) { - sb.append(" ____________________________________________________\n"); - sb.append(" | account | authority |\n"); + + private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) { + pw.print("Success ("); pw.print(ds.successCount); + if (ds.successCount > 0) { + pw.print(" for "); dumpTimeSec(pw, ds.successTime); + pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount); + } + pw.print(") Failure ("); pw.print(ds.failureCount); + if (ds.failureCount > 0) { + pw.print(" for "); dumpTimeSec(pw, ds.failureTime); + pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount); + } + pw.println(")"); } - - private void dumpSyncPendingRow(StringBuilder sb, Cursor c) { - sb.append(String.format(" | %-30s | %-15s |\n", c.getString(0), c.getString(1))); - } - - private void dumpSyncPendingFooter(StringBuilder sb) { - sb.append(" |__________________________________________________|\n"); - } - - protected void dumpSyncHistory(StringBuilder sb) { - Cursor c = mSyncStorageEngine.query(Sync.History.CONTENT_URI, null, "event=?", - new String[]{String.valueOf(Sync.History.EVENT_STOP)}, - Sync.HistoryColumns.EVENT_TIME + " desc"); - try { - long numSyncsLastHour = 0, durationLastHour = 0; - long numSyncsLastDay = 0, durationLastDay = 0; - long numSyncsLastWeek = 0, durationLastWeek = 0; - long numSyncsLast4Weeks = 0, durationLast4Weeks = 0; - long numSyncsTotal = 0, durationTotal = 0; - - long now = System.currentTimeMillis(); - int indexEventTime = c.getColumnIndexOrThrow(Sync.History.EVENT_TIME); - int indexElapsedTime = c.getColumnIndexOrThrow(Sync.History.ELAPSED_TIME); - while (c.moveToNext()) { - long duration = c.getLong(indexElapsedTime); - long endTime = c.getLong(indexEventTime) + duration; - long millisSinceStart = now - endTime; - numSyncsTotal++; - durationTotal += duration; - if (millisSinceStart < MILLIS_IN_HOUR) { - numSyncsLastHour++; - durationLastHour += duration; + + protected void dumpSyncHistory(PrintWriter pw, StringBuilder sb) { + SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics(); + if (dses != null && dses[0] != null) { + pw.println(); + pw.println("Sync Statistics"); + pw.print(" Today: "); dumpDayStatistic(pw, dses[0]); + int today = dses[0].day; + int i; + SyncStorageEngine.DayStats ds; + + // Print each day in the current week. + for (i=1; i<=6 && i < dses.length; i++) { + ds = dses[i]; + if (ds == null) break; + int delta = today-ds.day; + if (delta > 6) break; + + pw.print(" Day-"); pw.print(delta); pw.print(": "); + dumpDayStatistic(pw, ds); + } + + // Aggregate all following days into weeks and print totals. + int weekDay = today; + while (i < dses.length) { + SyncStorageEngine.DayStats aggr = null; + weekDay -= 7; + while (i < dses.length) { + ds = dses[i]; + if (ds == null) { + i = dses.length; + break; + } + int delta = weekDay-ds.day; + if (delta > 6) break; + i++; + + if (aggr == null) { + aggr = new SyncStorageEngine.DayStats(weekDay); + } + aggr.successCount += ds.successCount; + aggr.successTime += ds.successTime; + aggr.failureCount += ds.failureCount; + aggr.failureTime += ds.failureTime; } - if (millisSinceStart < MILLIS_IN_DAY) { - numSyncsLastDay++; - durationLastDay += duration; + if (aggr != null) { + pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": "); + dumpDayStatistic(pw, aggr); } - if (millisSinceStart < MILLIS_IN_WEEK) { - numSyncsLastWeek++; - durationLastWeek += duration; + } + } + + ArrayList<SyncStorageEngine.SyncHistoryItem> items + = mSyncStorageEngine.getSyncHistory(); + if (items != null && items.size() > 0) { + pw.println(); + pw.println("Recent Sync History"); + final int N = items.size(); + for (int i=0; i<N; i++) { + SyncStorageEngine.SyncHistoryItem item = items.get(i); + SyncStorageEngine.AuthorityInfo authority + = mSyncStorageEngine.getAuthority(item.authorityId); + pw.print(" #"); pw.print(i+1); pw.print(": "); + pw.print(authority != null ? authority.account : "<no account>"); + pw.print(" "); + pw.print(authority != null ? authority.authority : "<no account>"); + Time time = new Time(); + time.set(item.eventTime); + pw.print(" "); pw.print(SyncStorageEngine.SOURCES[item.source]); + pw.print(" @ "); + pw.print(formatTime(item.eventTime)); + pw.print(" for "); + dumpTimeSec(pw, item.elapsedTime); + pw.println(); + if (item.event != SyncStorageEngine.EVENT_STOP + || item.upstreamActivity !=0 + || item.downstreamActivity != 0) { + pw.print(" event="); pw.print(item.event); + pw.print(" upstreamActivity="); pw.print(item.upstreamActivity); + pw.print(" downstreamActivity="); pw.println(item.downstreamActivity); } - if (millisSinceStart < MILLIS_IN_4WEEKS) { - numSyncsLast4Weeks++; - durationLast4Weeks += duration; + if (item.mesg != null + && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) { + pw.print(" mesg="); pw.println(item.mesg); } } - dumpSyncIntervalHeader(sb); - dumpSyncInterval(sb, "hour", MILLIS_IN_HOUR, numSyncsLastHour, durationLastHour); - dumpSyncInterval(sb, "day", MILLIS_IN_DAY, numSyncsLastDay, durationLastDay); - dumpSyncInterval(sb, "week", MILLIS_IN_WEEK, numSyncsLastWeek, durationLastWeek); - dumpSyncInterval(sb, "4 weeks", - MILLIS_IN_4WEEKS, numSyncsLast4Weeks, durationLast4Weeks); - dumpSyncInterval(sb, "total", 0, numSyncsTotal, durationTotal); - dumpSyncIntervalFooter(sb); - } finally { - c.close(); - } - } - - private void dumpSyncIntervalHeader(StringBuilder sb) { - sb.append("Sync Stats\n"); - sb.append(" ___________________________________________________________\n"); - sb.append(" | | | duration in sec | |\n"); - sb.append(" | interval | count | average | total | % of interval |\n"); - } - - private void dumpSyncInterval(StringBuilder sb, String label, - long interval, long numSyncs, long duration) { - sb.append(String.format(" | %-8s | %6d | %8.1f | %8.1f", - label, numSyncs, ((float)duration/numSyncs)/1000, (float)duration/1000)); - if (interval > 0) { - sb.append(String.format(" | %13.2f |\n", ((float)duration/interval)*100.0)); - } else { - sb.append(String.format(" | %13s |\n", "na")); } } - private void dumpSyncIntervalFooter(StringBuilder sb) { - sb.append(" |_________________________________________________________|\n"); - } - /** * A helper object to keep track of the time we have spent syncing since the last boot */ @@ -1461,7 +1469,6 @@ class SyncManager { // found that is runnable (not disabled, etc). If that one is ready to run then // start it, otherwise just get out. SyncOperation syncOperation; - final Sync.Settings.QueryMap syncSettings = getSyncSettings(); final ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); final boolean backgroundDataSetting = connManager.getBackgroundDataSetting(); @@ -1488,9 +1495,9 @@ class SyncManager { final boolean force = syncOperation.extras.getBoolean( ContentResolver.SYNC_EXTRAS_FORCE, false); if (!force && (!backgroundDataSetting - || !syncSettings.getListenForNetworkTickles() - || !syncSettings.getSyncProviderAutomatically( - syncOperation.authority))) { + || !mSyncStorageEngine.getListenForNetworkTickles() + || !mSyncStorageEngine.getSyncProviderAutomatically( + null, syncOperation.authority))) { if (isLoggable) { Log.v(TAG, "runStateIdle: sync off, dropping " + syncOperation); } @@ -1616,7 +1623,7 @@ class SyncManager { if (isLoggable) { Log.v(TAG, "finished sync operation " + syncOperation); } - historyMessage = History.MESG_SUCCESS; + historyMessage = SyncStorageEngine.MESG_SUCCESS; // TODO: set these correctly when the SyncResult is extended to include it downstreamActivity = 0; upstreamActivity = 0; @@ -1640,7 +1647,7 @@ class SyncManager { } catch (RemoteException e) { // we don't need to retry this in this case } - historyMessage = History.MESG_CANCELED; + historyMessage = SyncStorageEngine.MESG_CANCELED; downstreamActivity = 0; upstreamActivity = 0; } @@ -1675,14 +1682,22 @@ class SyncManager { * If SyncResult.error() is true then it is safe to call this. */ private int syncResultToErrorNumber(SyncResult syncResult) { - if (syncResult.syncAlreadyInProgress) return History.ERROR_SYNC_ALREADY_IN_PROGRESS; - if (syncResult.stats.numAuthExceptions > 0) return History.ERROR_AUTHENTICATION; - if (syncResult.stats.numIoExceptions > 0) return History.ERROR_IO; - if (syncResult.stats.numParseExceptions > 0) return History.ERROR_PARSE; - if (syncResult.stats.numConflictDetectedExceptions > 0) return History.ERROR_CONFLICT; - if (syncResult.tooManyDeletions) return History.ERROR_TOO_MANY_DELETIONS; - if (syncResult.tooManyRetries) return History.ERROR_TOO_MANY_RETRIES; - if (syncResult.databaseError) return History.ERROR_INTERNAL; + if (syncResult.syncAlreadyInProgress) + return SyncStorageEngine.ERROR_SYNC_ALREADY_IN_PROGRESS; + if (syncResult.stats.numAuthExceptions > 0) + return SyncStorageEngine.ERROR_AUTHENTICATION; + if (syncResult.stats.numIoExceptions > 0) + return SyncStorageEngine.ERROR_IO; + if (syncResult.stats.numParseExceptions > 0) + return SyncStorageEngine.ERROR_PARSE; + if (syncResult.stats.numConflictDetectedExceptions > 0) + return SyncStorageEngine.ERROR_CONFLICT; + if (syncResult.tooManyDeletions) + return SyncStorageEngine.ERROR_TOO_MANY_DELETIONS; + if (syncResult.tooManyRetries) + return SyncStorageEngine.ERROR_TOO_MANY_RETRIES; + if (syncResult.databaseError) + return SyncStorageEngine.ERROR_INTERNAL; throw new IllegalStateException("we are not in an error state, " + syncResult); } @@ -1904,7 +1919,8 @@ class SyncManager { final int source = syncOperation.syncSource; final long now = System.currentTimeMillis(); - EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_START, source); + EventLog.writeEvent(2720, syncOperation.authority, + SyncStorageEngine.EVENT_START, source); return mSyncStorageEngine.insertStartSyncEvent( syncOperation.account, syncOperation.authority, now, source); @@ -1912,7 +1928,8 @@ class SyncManager { public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, int upstreamActivity, int downstreamActivity, long elapsedTime) { - EventLog.writeEvent(2720, syncOperation.authority, Sync.History.EVENT_STOP, syncOperation.syncSource); + EventLog.writeEvent(2720, syncOperation.authority, + SyncStorageEngine.EVENT_STOP, syncOperation.syncSource); mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage, downstreamActivity, upstreamActivity); @@ -1921,18 +1938,6 @@ class SyncManager { static class SyncQueue { private SyncStorageEngine mSyncStorageEngine; - private final String[] COLUMNS = new String[]{ - "_id", - "authority", - "account", - "extras", - "source" - }; - private static final int COLUMN_ID = 0; - private static final int COLUMN_AUTHORITY = 1; - private static final int COLUMN_ACCOUNT = 2; - private static final int COLUMN_EXTRAS = 3; - private static final int COLUMN_SOURCE = 4; private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false; @@ -1946,25 +1951,28 @@ class SyncManager { public SyncQueue(SyncStorageEngine syncStorageEngine) { mSyncStorageEngine = syncStorageEngine; - Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS); - try { - while (cursor.moveToNext()) { - add(cursorToOperation(cursor), - true /* this is being added from the database */); - } - } finally { - cursor.close(); - if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); - } + ArrayList<SyncStorageEngine.PendingOperation> ops + = mSyncStorageEngine.getPendingOperations(); + final int N = ops.size(); + for (int i=0; i<N; i++) { + SyncStorageEngine.PendingOperation op = ops.get(i); + SyncOperation syncOperation = new SyncOperation( + op.account, op.syncSource, op.authority, op.extras, 0); + syncOperation.pendingOperation = op; + add(syncOperation, op); + } + + if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); } public boolean add(SyncOperation operation) { return add(new SyncOperation(operation), - false /* this is not coming from the database */); + null /* this is not coming from the database */); } - private boolean add(SyncOperation operation, boolean fromDatabase) { - if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase); + private boolean add(SyncOperation operation, + SyncStorageEngine.PendingOperation pop) { + if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); // If this operation is expedited then set its earliestRunTime to be immediately // before the head of the list, or not if none are in the list. @@ -1996,7 +2004,7 @@ class SyncManager { if (existingOperation != null && operation.earliestRunTime >= existingOperation.earliestRunTime) { - if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase); + if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); return false; } @@ -2004,26 +2012,17 @@ class SyncManager { removeByKey(operationKey); } - if (operation.rowId == null) { - byte[] extrasData = null; - Parcel parcel = Parcel.obtain(); - try { - operation.extras.writeToParcel(parcel, 0); - extrasData = parcel.marshall(); - } finally { - parcel.recycle(); - } - ContentValues values = new ContentValues(); - values.put("account", operation.account); - values.put("authority", operation.authority); - values.put("source", operation.syncSource); - values.put("extras", extrasData); - Uri pendingUri = mSyncStorageEngine.insertIntoPending(values); - operation.rowId = pendingUri == null ? null : ContentUris.parseId(pendingUri); - if (operation.rowId == null) { + operation.pendingOperation = pop; + if (operation.pendingOperation == null) { + pop = new SyncStorageEngine.PendingOperation( + operation.account, operation.syncSource, + operation.authority, operation.extras); + pop = mSyncStorageEngine.insertIntoPending(pop); + if (pop == null) { throw new IllegalStateException("error adding pending sync operation " + operation); } + operation.pendingOperation = pop; } if (DEBUG_CHECK_DATA_CONSISTENCY) { @@ -2033,7 +2032,7 @@ class SyncManager { } mOpsByKey.put(operationKey, operation); mOpsByWhen.add(operation); - if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(!fromDatabase); + if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); return true; } @@ -2045,7 +2044,7 @@ class SyncManager { "unable to find " + operationToRemove + " in mOpsByWhen"); } - if (mSyncStorageEngine.deleteFromPending(operationToRemove.rowId) != 1) { + if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) { throw new IllegalStateException("unable to find pending row for " + operationToRemove); } @@ -2065,7 +2064,7 @@ class SyncManager { throw new IllegalStateException("unable to find " + operation + " in mOpsByKey"); } - if (mSyncStorageEngine.deleteFromPending(operation.rowId) != 1) { + if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) { throw new IllegalStateException("unable to find pending row for " + operation); } @@ -2087,7 +2086,7 @@ class SyncManager { "unable to find " + syncOperation + " in mOpsByWhen"); } - if (mSyncStorageEngine.deleteFromPending(syncOperation.rowId) != 1) { + if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) { throw new IllegalStateException("unable to find pending row for " + syncOperation); } @@ -2128,48 +2127,29 @@ class SyncManager { } if (checkDatabase) { - // check that the DB contains the same rows as the in-memory data structures - Cursor cursor = mSyncStorageEngine.getPendingSyncsCursor(COLUMNS); - try { - if (mOpsByKey.size() != cursor.getCount()) { - StringBuilder sb = new StringBuilder(); - DatabaseUtils.dumpCursor(cursor, sb); + final int N = mSyncStorageEngine.getPendingOperationCount(); + if (mOpsByKey.size() != N) { + ArrayList<SyncStorageEngine.PendingOperation> ops + = mSyncStorageEngine.getPendingOperations(); + StringBuilder sb = new StringBuilder(); + for (int i=0; i<N; i++) { + SyncStorageEngine.PendingOperation op = ops.get(i); + sb.append("#"); + sb.append(i); + sb.append(": account="); + sb.append(op.account); + sb.append(" syncSource="); + sb.append(op.syncSource); + sb.append(" authority="); + sb.append(op.authority); sb.append("\n"); - dump(sb); - throw new IllegalStateException("DB size mismatch: " - + mOpsByKey .size() + " != " + cursor.getCount() + "\n" - + sb.toString()); } - } finally { - cursor.close(); + dump(sb); + throw new IllegalStateException("DB size mismatch: " + + mOpsByKey.size() + " != " + N + "\n" + + sb.toString()); } } } - - private SyncOperation cursorToOperation(Cursor cursor) { - byte[] extrasData = cursor.getBlob(COLUMN_EXTRAS); - Bundle extras; - Parcel parcel = Parcel.obtain(); - try { - parcel.unmarshall(extrasData, 0, extrasData.length); - parcel.setDataPosition(0); - extras = parcel.readBundle(); - } catch (RuntimeException e) { - // A RuntimeException is thrown if we were unable to parse the parcel. - // Create an empty parcel in this case. - extras = new Bundle(); - } finally { - parcel.recycle(); - } - - SyncOperation syncOperation = new SyncOperation( - cursor.getString(COLUMN_ACCOUNT), - cursor.getInt(COLUMN_SOURCE), - cursor.getString(COLUMN_AUTHORITY), - extras, - 0 /* delay */); - syncOperation.rowId = cursor.getLong(COLUMN_ID); - return syncOperation; - } } } diff --git a/core/java/android/content/SyncProvider.java b/core/java/android/content/SyncProvider.java deleted file mode 100644 index 6ddd0462a46d..000000000000 --- a/core/java/android/content/SyncProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2007 The Android Open Source Project -package android.content; - -import android.database.Cursor; -import android.net.Uri; - -/** - * ContentProvider that tracks the sync data and overall sync - * history on the device. - * - * @hide - */ -public class SyncProvider extends ContentProvider { - public SyncProvider() { - } - - private SyncStorageEngine mSyncStorageEngine; - - @Override - public boolean onCreate() { - mSyncStorageEngine = SyncStorageEngine.getSingleton(); - return true; - } - - @Override - public Cursor query(Uri url, String[] projectionIn, - String selection, String[] selectionArgs, String sort) { - return mSyncStorageEngine.query(url, projectionIn, selection, selectionArgs, sort); - } - - @Override - public Uri insert(Uri url, ContentValues initialValues) { - return mSyncStorageEngine.insert(true /* the caller is the provider */, - url, initialValues); - } - - @Override - public int delete(Uri url, String where, String[] whereArgs) { - return mSyncStorageEngine.delete(true /* the caller is the provider */, - url, where, whereArgs); - } - - @Override - public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) { - return mSyncStorageEngine.update(true /* the caller is the provider */, - url, initialValues, where, whereArgs); - } - - @Override - public String getType(Uri url) { - return mSyncStorageEngine.getType(url); - } -} diff --git a/core/java/android/content/SyncStatusInfo.aidl b/core/java/android/content/SyncStatusInfo.aidl new file mode 100644 index 000000000000..b188c0bdb8a1 --- /dev/null +++ b/core/java/android/content/SyncStatusInfo.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.content; + +parcelable SyncStatusInfo; diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java new file mode 100644 index 000000000000..6687fcb4f8ab --- /dev/null +++ b/core/java/android/content/SyncStatusInfo.java @@ -0,0 +1,108 @@ +/* + * 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.content; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** @hide */ +public class SyncStatusInfo implements Parcelable { + static final int VERSION = 1; + + public final int authorityId; + public long totalElapsedTime; + public int numSyncs; + public int numSourcePoll; + public int numSourceServer; + public int numSourceLocal; + public int numSourceUser; + public long lastSuccessTime; + public int lastSuccessSource; + public long lastFailureTime; + public int lastFailureSource; + public String lastFailureMesg; + public long initialFailureTime; + public boolean pending; + + SyncStatusInfo(int authorityId) { + this.authorityId = authorityId; + } + + public int getLastFailureMesgAsInt(int def) { + try { + if (lastFailureMesg != null) { + return Integer.parseInt(lastFailureMesg); + } + } catch (NumberFormatException e) { + } + return def; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(VERSION); + parcel.writeInt(authorityId); + parcel.writeLong(totalElapsedTime); + parcel.writeInt(numSyncs); + parcel.writeInt(numSourcePoll); + parcel.writeInt(numSourceServer); + parcel.writeInt(numSourceLocal); + parcel.writeInt(numSourceUser); + parcel.writeLong(lastSuccessTime); + parcel.writeInt(lastSuccessSource); + parcel.writeLong(lastFailureTime); + parcel.writeInt(lastFailureSource); + parcel.writeString(lastFailureMesg); + parcel.writeLong(initialFailureTime); + parcel.writeInt(pending ? 1 : 0); + } + + SyncStatusInfo(Parcel parcel) { + int version = parcel.readInt(); + if (version != VERSION) { + Log.w("SyncStatusInfo", "Unknown version: " + version); + } + authorityId = parcel.readInt(); + totalElapsedTime = parcel.readLong(); + numSyncs = parcel.readInt(); + numSourcePoll = parcel.readInt(); + numSourceServer = parcel.readInt(); + numSourceLocal = parcel.readInt(); + numSourceUser = parcel.readInt(); + lastSuccessTime = parcel.readLong(); + lastSuccessSource = parcel.readInt(); + lastFailureTime = parcel.readLong(); + lastFailureSource = parcel.readInt(); + lastFailureMesg = parcel.readString(); + initialFailureTime = parcel.readLong(); + pending = parcel.readInt() != 0; + } + + public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() { + public SyncStatusInfo createFromParcel(Parcel in) { + return new SyncStatusInfo(in); + } + + public SyncStatusInfo[] newArray(int size) { + return new SyncStatusInfo[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 282f6e71db70..9c25e73b0cf3 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -1,125 +1,282 @@ +/* + * 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.content; -import android.Manifest; +import com.android.internal.os.AtomicFile; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.provider.Sync; -import android.text.TextUtils; -import android.util.Config; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.Parcel; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.util.Log; +import android.util.SparseArray; +import android.util.Xml; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; +import java.util.TimeZone; /** - * ContentProvider that tracks the sync data and overall sync + * Singleton that tracks the sync data and overall sync * history on the device. * * @hide */ -public class SyncStorageEngine { +public class SyncStorageEngine extends Handler { private static final String TAG = "SyncManager"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_FILE = false; + + // @VisibleForTesting + static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; - private static final String DATABASE_NAME = "syncmanager.db"; - private static final int DATABASE_VERSION = 10; + /** Enum value for a sync start event. */ + public static final int EVENT_START = 0; - private static final int STATS = 1; - private static final int STATS_ID = 2; - private static final int HISTORY = 3; - private static final int HISTORY_ID = 4; - private static final int SETTINGS = 5; - private static final int PENDING = 7; - private static final int ACTIVE = 8; - private static final int STATUS = 9; + /** Enum value for a sync stop event. */ + public static final int EVENT_STOP = 1; - private static final UriMatcher sURLMatcher = - new UriMatcher(UriMatcher.NO_MATCH); + // TODO: i18n -- grab these out of resources. + /** String names for the sync event types. */ + public static final String[] EVENTS = { "START", "STOP" }; - private static final HashMap<String,String> HISTORY_PROJECTION_MAP; - private static final HashMap<String,String> PENDING_PROJECTION_MAP; - private static final HashMap<String,String> ACTIVE_PROJECTION_MAP; - private static final HashMap<String,String> STATUS_PROJECTION_MAP; + /** Enum value for a server-initiated sync. */ + public static final int SOURCE_SERVER = 0; - private final Context mContext; - private final SQLiteOpenHelper mOpenHelper; - private static SyncStorageEngine sSyncStorageEngine = null; - - static { - sURLMatcher.addURI("sync", "stats", STATS); - sURLMatcher.addURI("sync", "stats/#", STATS_ID); - sURLMatcher.addURI("sync", "history", HISTORY); - sURLMatcher.addURI("sync", "history/#", HISTORY_ID); - sURLMatcher.addURI("sync", "settings", SETTINGS); - sURLMatcher.addURI("sync", "status", STATUS); - sURLMatcher.addURI("sync", "active", ACTIVE); - sURLMatcher.addURI("sync", "pending", PENDING); - - HashMap<String,String> map; - PENDING_PROJECTION_MAP = map = new HashMap<String,String>(); - map.put(Sync.History._ID, Sync.History._ID); - map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT); - map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY); - - ACTIVE_PROJECTION_MAP = map = new HashMap<String,String>(); - map.put(Sync.History._ID, Sync.History._ID); - map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT); - map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY); - map.put("startTime", "startTime"); - - HISTORY_PROJECTION_MAP = map = new HashMap<String,String>(); - map.put(Sync.History._ID, "history._id as _id"); - map.put(Sync.History.ACCOUNT, "stats.account as account"); - map.put(Sync.History.AUTHORITY, "stats.authority as authority"); - map.put(Sync.History.EVENT, Sync.History.EVENT); - map.put(Sync.History.EVENT_TIME, Sync.History.EVENT_TIME); - map.put(Sync.History.ELAPSED_TIME, Sync.History.ELAPSED_TIME); - map.put(Sync.History.SOURCE, Sync.History.SOURCE); - map.put(Sync.History.UPSTREAM_ACTIVITY, Sync.History.UPSTREAM_ACTIVITY); - map.put(Sync.History.DOWNSTREAM_ACTIVITY, Sync.History.DOWNSTREAM_ACTIVITY); - map.put(Sync.History.MESG, Sync.History.MESG); - - STATUS_PROJECTION_MAP = map = new HashMap<String,String>(); - map.put(Sync.Status._ID, "status._id as _id"); - map.put(Sync.Status.ACCOUNT, "stats.account as account"); - map.put(Sync.Status.AUTHORITY, "stats.authority as authority"); - map.put(Sync.Status.TOTAL_ELAPSED_TIME, Sync.Status.TOTAL_ELAPSED_TIME); - map.put(Sync.Status.NUM_SYNCS, Sync.Status.NUM_SYNCS); - map.put(Sync.Status.NUM_SOURCE_LOCAL, Sync.Status.NUM_SOURCE_LOCAL); - map.put(Sync.Status.NUM_SOURCE_POLL, Sync.Status.NUM_SOURCE_POLL); - map.put(Sync.Status.NUM_SOURCE_SERVER, Sync.Status.NUM_SOURCE_SERVER); - map.put(Sync.Status.NUM_SOURCE_USER, Sync.Status.NUM_SOURCE_USER); - map.put(Sync.Status.LAST_SUCCESS_SOURCE, Sync.Status.LAST_SUCCESS_SOURCE); - map.put(Sync.Status.LAST_SUCCESS_TIME, Sync.Status.LAST_SUCCESS_TIME); - map.put(Sync.Status.LAST_FAILURE_SOURCE, Sync.Status.LAST_FAILURE_SOURCE); - map.put(Sync.Status.LAST_FAILURE_TIME, Sync.Status.LAST_FAILURE_TIME); - map.put(Sync.Status.LAST_FAILURE_MESG, Sync.Status.LAST_FAILURE_MESG); - map.put(Sync.Status.PENDING, Sync.Status.PENDING); - } - - private static final String[] STATS_ACCOUNT_PROJECTION = - new String[] { Sync.Stats.ACCOUNT }; - - private static final int MAX_HISTORY_EVENTS_TO_KEEP = 5000; - - private static final String SELECT_INITIAL_FAILURE_TIME_QUERY_STRING = "" - + "SELECT min(a) " - + "FROM (" - + " SELECT initialFailureTime AS a " - + " FROM status " - + " WHERE stats_id=? AND a IS NOT NULL " - + " UNION " - + " SELECT ? AS a" - + " )"; + /** Enum value for a local-initiated sync. */ + public static final int SOURCE_LOCAL = 1; + /** + * Enum value for a poll-based sync (e.g., upon connection to + * network) + */ + public static final int SOURCE_POLL = 2; + + /** Enum value for a user-initiated sync. */ + public static final int SOURCE_USER = 3; + + // TODO: i18n -- grab these out of resources. + /** String names for the sync source types. */ + public static final String[] SOURCES = { "SERVER", + "LOCAL", + "POLL", + "USER" }; + + // Error types + public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1; + public static final int ERROR_AUTHENTICATION = 2; + public static final int ERROR_IO = 3; + public static final int ERROR_PARSE = 4; + public static final int ERROR_CONFLICT = 5; + public static final int ERROR_TOO_MANY_DELETIONS = 6; + public static final int ERROR_TOO_MANY_RETRIES = 7; + public static final int ERROR_INTERNAL = 8; + + // The MESG column will contain one of these or one of the Error types. + public static final String MESG_SUCCESS = "success"; + public static final String MESG_CANCELED = "canceled"; + + public static final int CHANGE_SETTINGS = 1<<0; + public static final int CHANGE_PENDING = 1<<1; + public static final int CHANGE_ACTIVE = 1<<2; + public static final int CHANGE_STATUS = 1<<3; + public static final int CHANGE_ALL = 0x7fffffff; + + public static final int MAX_HISTORY = 15; + + private static final int MSG_WRITE_STATUS = 1; + private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes + + private static final int MSG_WRITE_STATISTICS = 2; + private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour + + public static class PendingOperation { + final String account; + final int syncSource; + final String authority; + final Bundle extras; // note: read-only. + + int authorityId; + byte[] flatExtras; + + PendingOperation(String account, int source, + String authority, Bundle extras) { + this.account = account; + this.syncSource = source; + this.authority = authority; + this.extras = extras != null ? new Bundle(extras) : extras; + this.authorityId = -1; + } + PendingOperation(PendingOperation other) { + this.account = other.account; + this.syncSource = other.syncSource; + this.authority = other.authority; + this.extras = other.extras; + this.authorityId = other.authorityId; + } + } + + static class AccountInfo { + final String account; + final HashMap<String, AuthorityInfo> authorities = + new HashMap<String, AuthorityInfo>(); + + AccountInfo(String account) { + this.account = account; + } + } + + public static class AuthorityInfo { + final String account; + final String authority; + final int ident; + boolean enabled; + + AuthorityInfo(String account, String authority, int ident) { + this.account = account; + this.authority = authority; + this.ident = ident; + enabled = true; + } + } + + public static class SyncHistoryItem { + int authorityId; + int historyId; + long eventTime; + long elapsedTime; + int source; + int event; + long upstreamActivity; + long downstreamActivity; + String mesg; + } + + public static class DayStats { + public final int day; + public int successCount; + public long successTime; + public int failureCount; + public long failureTime; + + public DayStats(int day) { + this.day = day; + } + } + + // Primary list of all syncable authorities. Also our global lock. + private final SparseArray<AuthorityInfo> mAuthorities = + new SparseArray<AuthorityInfo>(); + + private final HashMap<String, AccountInfo> mAccounts = + new HashMap<String, AccountInfo>(); + + private final ArrayList<PendingOperation> mPendingOperations = + new ArrayList<PendingOperation>(); + + private ActiveSyncInfo mActiveSync; + + private final SparseArray<SyncStatusInfo> mSyncStatus = + new SparseArray<SyncStatusInfo>(); + + private final ArrayList<SyncHistoryItem> mSyncHistory = + new ArrayList<SyncHistoryItem>(); + + private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners + = new RemoteCallbackList<ISyncStatusObserver>(); + + // We keep 4 weeks of stats. + private final DayStats[] mDayStats = new DayStats[7*4]; + private final Calendar mCal; + private int mYear; + private int mYearInDays; + + private final Context mContext; + private static volatile SyncStorageEngine sSyncStorageEngine = null; + + /** + * This file contains the core engine state: all accounts and the + * settings for them. It must never be lost, and should be changed + * infrequently, so it is stored as an XML file. + */ + private final AtomicFile mAccountInfoFile; + + /** + * This file contains the current sync status. We would like to retain + * it across boots, but its loss is not the end of the world, so we store + * this information as binary data. + */ + private final AtomicFile mStatusFile; + + /** + * This file contains sync statistics. This is purely debugging information + * so is written infrequently and can be thrown away at any time. + */ + private final AtomicFile mStatisticsFile; + + /** + * This file contains the pending sync operations. It is a binary file, + * which must be updated every time an operation is added or removed, + * so we have special handling of it. + */ + private final AtomicFile mPendingFile; + private static final int PENDING_FINISH_TO_WRITE = 4; + private int mNumPendingFinished = 0; + + private int mNextHistoryId = 0; + private boolean mListenForTickles = true; + private SyncStorageEngine(Context context) { mContext = context; - mOpenHelper = new SyncStorageEngine.DatabaseHelper(context); sSyncStorageEngine = this; + + mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); + + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + File syncDir = new File(systemDir, "sync"); + mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); + mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); + mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); + mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); + + readAccountInfoLocked(); + readStatusLocked(); + readPendingOperationsLocked(); + readStatisticsLocked(); + readLegacyAccountInfoLocked(); } public static SyncStorageEngine newTestInstance(Context context) { @@ -140,619 +297,1263 @@ public class SyncStorageEngine { return sSyncStorageEngine; } - private class DatabaseHelper extends SQLiteOpenHelper { - DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE pending (" - + "_id INTEGER PRIMARY KEY," - + "authority TEXT NOT NULL," - + "account TEXT NOT NULL," - + "extras BLOB NOT NULL," - + "source INTEGER NOT NULL" - + ");"); - - db.execSQL("CREATE TABLE stats (" + - "_id INTEGER PRIMARY KEY," + - "account TEXT, " + - "authority TEXT, " + - "syncdata TEXT, " + - "UNIQUE (account, authority)" + - ");"); - - db.execSQL("CREATE TABLE history (" + - "_id INTEGER PRIMARY KEY," + - "stats_id INTEGER," + - "eventTime INTEGER," + - "elapsedTime INTEGER," + - "source INTEGER," + - "event INTEGER," + - "upstreamActivity INTEGER," + - "downstreamActivity INTEGER," + - "mesg TEXT);"); - - db.execSQL("CREATE TABLE status (" - + "_id INTEGER PRIMARY KEY," - + "stats_id INTEGER NOT NULL," - + "totalElapsedTime INTEGER NOT NULL DEFAULT 0," - + "numSyncs INTEGER NOT NULL DEFAULT 0," - + "numSourcePoll INTEGER NOT NULL DEFAULT 0," - + "numSourceServer INTEGER NOT NULL DEFAULT 0," - + "numSourceLocal INTEGER NOT NULL DEFAULT 0," - + "numSourceUser INTEGER NOT NULL DEFAULT 0," - + "lastSuccessTime INTEGER," - + "lastSuccessSource INTEGER," - + "lastFailureTime INTEGER," - + "lastFailureSource INTEGER," - + "lastFailureMesg STRING," - + "initialFailureTime INTEGER," - + "pending INTEGER NOT NULL DEFAULT 0);"); - - db.execSQL("CREATE TABLE active (" - + "_id INTEGER PRIMARY KEY," - + "authority TEXT," - + "account TEXT," - + "startTime INTEGER);"); - - db.execSQL("CREATE INDEX historyEventTime ON history (eventTime)"); - - db.execSQL("CREATE TABLE settings (" + - "name TEXT PRIMARY KEY," + - "value TEXT);"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 9 && newVersion == 10) { - Log.w(TAG, "Upgrading database from version " + oldVersion + " to " - + newVersion + ", which will preserve old data"); - db.execSQL("ALTER TABLE status ADD COLUMN initialFailureTime INTEGER"); - return; + @Override public void handleMessage(Message msg) { + if (msg.what == MSG_WRITE_STATUS) { + synchronized (mAccounts) { + writeStatusLocked(); + } + } else if (msg.what == MSG_WRITE_STATISTICS) { + synchronized (mAccounts) { + writeStatisticsLocked(); } - - Log.w(TAG, "Upgrading database from version " + oldVersion + " to " - + newVersion + ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS pending"); - db.execSQL("DROP TABLE IF EXISTS stats"); - db.execSQL("DROP TABLE IF EXISTS history"); - db.execSQL("DROP TABLE IF EXISTS settings"); - db.execSQL("DROP TABLE IF EXISTS active"); - db.execSQL("DROP TABLE IF EXISTS status"); - onCreate(db); } - - @Override - public void onOpen(SQLiteDatabase db) { - if (!db.isReadOnly()) { - db.delete("active", null, null); - db.insert("active", "account", null); + } + + public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { + synchronized (mAuthorities) { + mChangeListeners.register(callback, mask); + } + } + + public void removeStatusChangeListener(ISyncStatusObserver callback) { + synchronized (mAuthorities) { + mChangeListeners.unregister(callback); + } + } + + private void reportChange(int which) { + ArrayList<ISyncStatusObserver> reports = null; + synchronized (mAuthorities) { + int i = mChangeListeners.beginBroadcast(); + while (i > 0) { + i--; + Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i); + if ((which & mask.intValue()) == 0) { + continue; + } + if (reports == null) { + reports = new ArrayList<ISyncStatusObserver>(i); + } + reports.add(mChangeListeners.getBroadcastItem(i)); + } + } + + if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports); + + if (reports != null) { + int i = reports.size(); + while (i > 0) { + i--; + try { + reports.get(i).onStatusChanged(which); + } catch (RemoteException e) { + // The remote callback list will take care of this for us. + } + } + } + } + + public boolean getSyncProviderAutomatically(String account, String providerName) { + synchronized (mAuthorities) { + if (account != null) { + AuthorityInfo authority = getAuthorityLocked(account, providerName, + "getSyncProviderAutomatically"); + return authority != null ? authority.enabled : false; + } + + int i = mAuthorities.size(); + while (i > 0) { + i--; + AuthorityInfo authority = mAuthorities.get(i); + if (authority.authority.equals(providerName) + && authority.enabled) { + return true; + } } + return false; } } - protected void doDatabaseCleanup(String[] accounts) { - HashSet<String> currentAccounts = new HashSet<String>(); - for (String account : accounts) currentAccounts.add(account); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION, - null /* where */, null /* where args */, Sync.Stats.ACCOUNT, - null /* having */, null /* order by */); - try { - while (cursor.moveToNext()) { - String account = cursor.getString(0); - if (TextUtils.isEmpty(account)) { - continue; + public void setSyncProviderAutomatically(String account, String providerName, boolean sync) { + synchronized (mAuthorities) { + if (account != null) { + AuthorityInfo authority = getAuthorityLocked(account, providerName, + "setSyncProviderAutomatically"); + if (authority != null) { + authority.enabled = sync; } - if (!currentAccounts.contains(account)) { - String where = Sync.Stats.ACCOUNT + "=?"; - int numDeleted; - numDeleted = db.delete("stats", where, new String[]{account}); - if (Config.LOGD) { - Log.d(TAG, "deleted " + numDeleted - + " records from stats table" - + " for account " + account); + } else { + int i = mAuthorities.size(); + while (i > 0) { + i--; + AuthorityInfo authority = mAuthorities.get(i); + if (authority.authority.equals(providerName)) { + authority.enabled = sync; } } } - } finally { - cursor.close(); + writeAccountInfoLocked(); } + + reportChange(CHANGE_SETTINGS); } - protected void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { - if (activeSyncContext != null) { - updateActiveSync(activeSyncContext.mSyncOperation.account, - activeSyncContext.mSyncOperation.authority, activeSyncContext.mStartTime); - } else { - // we indicate that the sync is not active by passing null for all the parameters - updateActiveSync(null, null, null); + public void setListenForNetworkTickles(boolean flag) { + synchronized (mAuthorities) { + mListenForTickles = flag; + writeAccountInfoLocked(); } + reportChange(CHANGE_SETTINGS); } - private int updateActiveSync(String account, String authority, Long startTime) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put("account", account); - values.put("authority", authority); - values.put("startTime", startTime); - int numChanges = db.update("active", values, null, null); - if (numChanges > 0) { - mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI, - null /* this change wasn't made through an observer */); + public boolean getListenForNetworkTickles() { + synchronized (mAuthorities) { + return mListenForTickles; } - return numChanges; } - - /** - * Implements the {@link ContentProvider#query} method - */ - public Cursor query(Uri url, String[] projectionIn, - String selection, String[] selectionArgs, String sort) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - // Generate the body of the query - int match = sURLMatcher.match(url); - String groupBy = null; - switch (match) { - case STATS: - qb.setTables("stats"); - break; - case STATS_ID: - qb.setTables("stats"); - qb.appendWhere("_id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case HISTORY: - // join the stats and history tables, so the caller can get - // the account and authority information as part of this query. - qb.setTables("stats, history"); - qb.setProjectionMap(HISTORY_PROJECTION_MAP); - qb.appendWhere("stats._id = history.stats_id"); - break; - case ACTIVE: - qb.setTables("active"); - qb.setProjectionMap(ACTIVE_PROJECTION_MAP); - qb.appendWhere("account is not null"); - break; - case PENDING: - qb.setTables("pending"); - qb.setProjectionMap(PENDING_PROJECTION_MAP); - groupBy = "account, authority"; - break; - case STATUS: - // join the stats and status tables, so the caller can get - // the account and authority information as part of this query. - qb.setTables("stats, status"); - qb.setProjectionMap(STATUS_PROJECTION_MAP); - qb.appendWhere("stats._id = status.stats_id"); - break; - case HISTORY_ID: - // join the stats and history tables, so the caller can get - // the account and authority information as part of this query. - qb.setTables("stats, history"); - qb.setProjectionMap(HISTORY_PROJECTION_MAP); - qb.appendWhere("stats._id = history.stats_id"); - qb.appendWhere("AND history._id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - case SETTINGS: - qb.setTables("settings"); - break; - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - - if (match == SETTINGS) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, - "no permission to read the sync settings"); - } else { - mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, - "no permission to read the sync stats"); - } - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor c = qb.query(db, projectionIn, selection, selectionArgs, groupBy, null, sort); - c.setNotificationUri(mContext.getContentResolver(), url); - return c; + + public AuthorityInfo getAuthority(String account, String authority) { + synchronized (mAuthorities) { + return getAuthorityLocked(account, authority, null); + } } - + + public AuthorityInfo getAuthority(int authorityId) { + synchronized (mAuthorities) { + return mAuthorities.get(authorityId); + } + } + /** - * Implements the {@link ContentProvider#insert} method - * @param callerIsTheProvider true if this is being called via the - * {@link ContentProvider#insert} in method rather than directly. - * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't - * for the Settings table. + * Returns true if there is currently a sync operation for the given + * account or authority in the pending list, or actively being processed. */ - public Uri insert(boolean callerIsTheProvider, Uri url, ContentValues values) { - String table; - long rowID; - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - final int match = sURLMatcher.match(url); - checkCaller(callerIsTheProvider, match); - switch (match) { - case SETTINGS: - mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, - "no permission to write the sync settings"); - table = "settings"; - rowID = db.replace(table, null, values); - break; - default: - throw new IllegalArgumentException("Unknown URL " + url); + public boolean isSyncActive(String account, String authority) { + synchronized (mAuthorities) { + int i = mPendingOperations.size(); + while (i > 0) { + i--; + // TODO(fredq): this probably shouldn't be considering + // pending operations. + PendingOperation op = mPendingOperations.get(i); + if (op.account.equals(account) && op.authority.equals(authority)) { + return true; + } + } + + if (mActiveSync != null) { + AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId); + if (ainfo != null && ainfo.account.equals(account) + && ainfo.authority.equals(authority)) { + return true; + } + } } - - - if (rowID > 0) { - mContext.getContentResolver().notifyChange(url, null /* observer */); - return Uri.parse("content://sync/" + table + "/" + rowID); + + return false; + } + + public PendingOperation insertIntoPending(PendingOperation op) { + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); + + AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, + op.authority, + -1 /* desired identifier */, + true /* write accounts to storage */); + if (authority == null) { + return null; + } + + op = new PendingOperation(op); + op.authorityId = authority.ident; + mPendingOperations.add(op); + appendPendingOperationLocked(op); + + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.pending = true; } + + reportChange(CHANGE_PENDING); + return op; + } - return null; + public boolean deleteFromPending(PendingOperation op) { + boolean res = false; + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); + if (mPendingOperations.remove(op)) { + if (mPendingOperations.size() == 0 + || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { + writePendingOperationsLocked(); + mNumPendingFinished = 0; + } else { + mNumPendingFinished++; + } + + AuthorityInfo authority = getAuthorityLocked(op.account, op.authority, + "deleteFromPending"); + if (authority != null) { + if (DEBUG) Log.v(TAG, "removing - " + authority); + final int N = mPendingOperations.size(); + boolean morePending = false; + for (int i=0; i<N; i++) { + PendingOperation cur = mPendingOperations.get(i); + if (cur.account.equals(op.account) + && cur.authority.equals(op.authority)) { + morePending = true; + break; + } + } + + if (!morePending) { + if (DEBUG) Log.v(TAG, "no more pending!"); + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.pending = false; + } + } + + res = true; + } + } + + reportChange(CHANGE_PENDING); + return res; } - private static void checkCaller(boolean callerIsTheProvider, int match) { - if (callerIsTheProvider && match != SETTINGS) { - throw new UnsupportedOperationException( - "only the settings are modifiable via the ContentProvider interface"); + public int clearPending() { + int num; + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "clearPending"); + num = mPendingOperations.size(); + mPendingOperations.clear(); + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + mSyncStatus.get(i).pending = false; + } + writePendingOperationsLocked(); } + reportChange(CHANGE_PENDING); + return num; } /** - * Implements the {@link ContentProvider#delete} method - * @param callerIsTheProvider true if this is being called via the - * {@link ContentProvider#delete} in method rather than directly. - * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't - * for the Settings table. + * Return a copy of the current array of pending operations. The + * PendingOperation objects are the real objects stored inside, so that + * they can be used with deleteFromPending(). */ - public int delete(boolean callerIsTheProvider, Uri url, String where, String[] whereArgs) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int match = sURLMatcher.match(url); - - int numRows; - switch (match) { - case SETTINGS: - mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, - "no permission to write the sync settings"); - numRows = db.delete("settings", where, whereArgs); - break; - default: - throw new UnsupportedOperationException("Cannot delete URL: " + url); + public ArrayList<PendingOperation> getPendingOperations() { + synchronized (mAuthorities) { + return new ArrayList<PendingOperation>(mPendingOperations); } - - if (numRows > 0) { - mContext.getContentResolver().notifyChange(url, null /* observer */); - } - return numRows; } - + /** - * Implements the {@link ContentProvider#update} method - * @param callerIsTheProvider true if this is being called via the - * {@link ContentProvider#update} in method rather than directly. - * @throws UnsupportedOperationException if callerIsTheProvider is true and the url isn't - * for the Settings table. + * Return the number of currently pending operations. */ - public int update(boolean callerIsTheProvider, Uri url, ContentValues initialValues, - String where, String[] whereArgs) { - switch (sURLMatcher.match(url)) { - case SETTINGS: - throw new UnsupportedOperationException("updating url " + url - + " is not allowed, use insert instead"); - default: - throw new UnsupportedOperationException("Cannot update URL: " + url); + public int getPendingOperationCount() { + synchronized (mAuthorities) { + return mPendingOperations.size(); } } - + /** - * Implements the {@link ContentProvider#getType} method + * Called when the set of account has changed, given the new array of + * active accounts. */ - public String getType(Uri url) { - int match = sURLMatcher.match(url); - switch (match) { - case SETTINGS: - return "vnd.android.cursor.dir/sync-settings"; - default: - throw new IllegalArgumentException("Unknown URL"); + public void doDatabaseCleanup(String[] accounts) { + synchronized (mAuthorities) { + if (DEBUG) Log.w(TAG, "Updating for new accounts..."); + SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); + Iterator<AccountInfo> accIt = mAccounts.values().iterator(); + while (accIt.hasNext()) { + AccountInfo acc = accIt.next(); + if (!ArrayUtils.contains(accounts, acc.account)) { + // This account no longer exists... + if (DEBUG) Log.w(TAG, "Account removed: " + acc.account); + for (AuthorityInfo auth : acc.authorities.values()) { + removing.put(auth.ident, auth); + } + accIt.remove(); + } + } + + // Clean out all data structures. + int i = removing.size(); + if (i > 0) { + while (i > 0) { + i--; + int ident = removing.keyAt(i); + mAuthorities.remove(ident); + int j = mSyncStatus.size(); + while (j > 0) { + j--; + if (mSyncStatus.keyAt(j) == ident) { + mSyncStatus.remove(mSyncStatus.keyAt(j)); + } + } + j = mSyncHistory.size(); + while (j > 0) { + j--; + if (mSyncHistory.get(j).authorityId == ident) { + mSyncHistory.remove(j); + } + } + } + writeAccountInfoLocked(); + writeStatusLocked(); + writePendingOperationsLocked(); + writeStatisticsLocked(); + } } } - protected Uri insertIntoPending(ContentValues values) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - try { - db.beginTransaction(); - long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values); - if (rowId < 0) return null; - String account = values.getAsString(Sync.Pending.ACCOUNT); - String authority = values.getAsString(Sync.Pending.AUTHORITY); - - long statsId = createStatsRowIfNecessary(account, authority); - createStatusRowIfNecessary(statsId); - - values.clear(); - values.put(Sync.Status.PENDING, 1); - int numUpdatesStatus = db.update("status", values, "stats_id=" + statsId, null); - - db.setTransactionSuccessful(); - - mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI, - null /* no observer initiated this change */); - if (numUpdatesStatus > 0) { - mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, - null /* no observer initiated this change */); + /** + * Called when the currently active sync is changing (there can only be + * one at a time). Either supply a valid ActiveSyncContext with information + * about the sync, or null to stop the currently active sync. + */ + public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { + synchronized (mAuthorities) { + if (activeSyncContext != null) { + if (DEBUG) Log.v(TAG, "setActiveSync: account=" + + activeSyncContext.mSyncOperation.account + + " auth=" + activeSyncContext.mSyncOperation.authority + + " src=" + activeSyncContext.mSyncOperation.syncSource + + " extras=" + activeSyncContext.mSyncOperation.extras); + if (mActiveSync != null) { + Log.w(TAG, "setActiveSync called with existing active sync!"); + } + AuthorityInfo authority = getAuthorityLocked( + activeSyncContext.mSyncOperation.account, + activeSyncContext.mSyncOperation.authority, + "setActiveSync"); + if (authority == null) { + return; + } + mActiveSync = new ActiveSyncInfo(authority.ident, + authority.account, authority.authority, + activeSyncContext.mStartTime); + } else { + if (DEBUG) Log.v(TAG, "setActiveSync: null"); + mActiveSync = null; } - return ContentUris.withAppendedId(Sync.Pending.CONTENT_URI, rowId); - } finally { - db.endTransaction(); } + + reportChange(CHANGE_ACTIVE); } - int deleteFromPending(long rowId) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - String account; - String authority; - Cursor c = db.query("pending", - new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY}, - "_id=" + rowId, null, null, null, null); - try { - if (c.getCount() != 1) { - return 0; - } - c.moveToNext(); - account = c.getString(0); - authority = c.getString(1); - } finally { - c.close(); - } - db.delete("pending", "_id=" + rowId, null /* no where args */); - final String[] accountAuthorityWhereArgs = new String[]{account, authority}; - boolean isPending = 0 < DatabaseUtils.longForQuery(db, - "SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?", - accountAuthorityWhereArgs); - if (!isPending) { - long statsId = createStatsRowIfNecessary(account, authority); - db.execSQL("UPDATE status SET pending=0 WHERE stats_id=" + statsId); - } - db.setTransactionSuccessful(); - - mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI, - null /* no observer initiated this change */); - if (!isPending) { - mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, - null /* no observer initiated this change */); - } - return 1; - } finally { - db.endTransaction(); + /** + * To allow others to send active change reports, to poke clients. + */ + public void reportActiveChange() { + reportChange(CHANGE_ACTIVE); + } + + /** + * Note that sync has started for the given account and authority. + */ + public long insertStartSyncEvent(String accountName, String authorityName, + long now, int source) { + long id; + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName + + " auth=" + authorityName + " source=" + source); + AuthorityInfo authority = getAuthorityLocked(accountName, authorityName, + "insertStartSyncEvent"); + if (authority == null) { + return -1; + } + SyncHistoryItem item = new SyncHistoryItem(); + item.authorityId = authority.ident; + item.historyId = mNextHistoryId++; + if (mNextHistoryId < 0) mNextHistoryId = 0; + item.eventTime = now; + item.source = source; + item.event = EVENT_START; + mSyncHistory.add(0, item); + while (mSyncHistory.size() > MAX_HISTORY) { + mSyncHistory.remove(mSyncHistory.size()-1); + } + id = item.historyId; + if (DEBUG) Log.v(TAG, "returning historyId " + id); } + + reportChange(CHANGE_STATUS); + return id; } - int clearPending() { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - int numChanges = db.delete("pending", null, null /* no where args */); - if (numChanges > 0) { - db.execSQL("UPDATE status SET pending=0"); - mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI, - null /* no observer initiated this change */); - mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, - null /* no observer initiated this change */); - } - db.setTransactionSuccessful(); - return numChanges; - } finally { - db.endTransaction(); + public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, + long downstreamActivity, long upstreamActivity) { + synchronized (mAuthorities) { + if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId); + SyncHistoryItem item = null; + int i = mSyncHistory.size(); + while (i > 0) { + i--; + item = mSyncHistory.get(i); + if (item.historyId == historyId) { + break; + } + item = null; + } + + if (item == null) { + Log.w(TAG, "stopSyncEvent: no history for id " + historyId); + return; + } + + item.elapsedTime = elapsedTime; + item.event = EVENT_STOP; + item.mesg = resultMessage; + item.downstreamActivity = downstreamActivity; + item.upstreamActivity = upstreamActivity; + + SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); + + status.numSyncs++; + status.totalElapsedTime += elapsedTime; + switch (item.source) { + case SOURCE_LOCAL: + status.numSourceLocal++; + break; + case SOURCE_POLL: + status.numSourcePoll++; + break; + case SOURCE_USER: + status.numSourceUser++; + break; + case SOURCE_SERVER: + status.numSourceServer++; + break; + } + + boolean writeStatisticsNow = false; + int day = getCurrentDayLocked(); + if (mDayStats[0] == null) { + mDayStats[0] = new DayStats(day); + } else if (day != mDayStats[0].day) { + System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); + mDayStats[0] = new DayStats(day); + writeStatisticsNow = true; + } else if (mDayStats[0] == null) { + } + final DayStats ds = mDayStats[0]; + + final long lastSyncTime = (item.eventTime + elapsedTime); + boolean writeStatusNow = false; + if (MESG_SUCCESS.equals(resultMessage)) { + // - if successful, update the successful columns + if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { + writeStatusNow = true; + } + status.lastSuccessTime = lastSyncTime; + status.lastSuccessSource = item.source; + status.lastFailureTime = 0; + status.lastFailureSource = -1; + status.lastFailureMesg = null; + status.initialFailureTime = 0; + ds.successCount++; + ds.successTime += elapsedTime; + } else if (!MESG_CANCELED.equals(resultMessage)) { + if (status.lastFailureTime == 0) { + writeStatusNow = true; + } + status.lastFailureTime = lastSyncTime; + status.lastFailureSource = item.source; + status.lastFailureMesg = resultMessage; + if (status.initialFailureTime == 0) { + status.initialFailureTime = lastSyncTime; + } + ds.failureCount++; + ds.failureTime += elapsedTime; + } + + if (writeStatusNow) { + writeStatusLocked(); + } else if (!hasMessages(MSG_WRITE_STATUS)) { + sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), + WRITE_STATUS_DELAY); + } + if (writeStatisticsNow) { + writeStatisticsLocked(); + } else if (!hasMessages(MSG_WRITE_STATISTICS)) { + sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), + WRITE_STATISTICS_DELAY); + } } + + reportChange(CHANGE_STATUS); } /** - * Returns a cursor over all the pending syncs in no particular order. This cursor is not - * "live", in that if changes are made to the pending table any observers on this cursor - * will not be notified. - * @param projection Return only these columns. If null then all columns are returned. - * @return the cursor of pending syncs + * Return the currently active sync information, or null if there is no + * active sync. Note that the returned object is the real, live active + * sync object, so be careful what you do with it. */ - public Cursor getPendingSyncsCursor(String[] projection) { - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - return db.query("pending", projection, null, null, null, null, null); + public ActiveSyncInfo getActiveSync() { + synchronized (mAuthorities) { + return mActiveSync; + } } - - // @VisibleForTesting - static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; - - private boolean purgeOldHistoryEvents(long now) { - // remove events that are older than MILLIS_IN_4WEEKS - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int numDeletes = db.delete("history", "eventTime<" + (now - MILLIS_IN_4WEEKS), null); - if (Log.isLoggable(TAG, Log.VERBOSE)) { - if (numDeletes > 0) { - Log.v(TAG, "deleted " + numDeletes + " old event(s) from the sync history"); + + /** + * Return an array of the current sync status for all authorities. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public ArrayList<SyncStatusInfo> getSyncStatus() { + synchronized (mAuthorities) { + final int N = mSyncStatus.size(); + ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); + for (int i=0; i<N; i++) { + ops.add(mSyncStatus.valueAt(i)); } + return ops; } - - // keep only the last MAX_HISTORY_EVENTS_TO_KEEP history events - numDeletes += db.delete("history", "eventTime < (select min(eventTime) from " - + "(select eventTime from history order by eventTime desc limit ?))", - new String[]{String.valueOf(MAX_HISTORY_EVENTS_TO_KEEP)}); - - return numDeletes > 0; } - - public long insertStartSyncEvent(String account, String authority, long now, int source) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - long statsId = createStatsRowIfNecessary(account, authority); - - purgeOldHistoryEvents(now); - ContentValues values = new ContentValues(); - values.put(Sync.History.STATS_ID, statsId); - values.put(Sync.History.EVENT_TIME, now); - values.put(Sync.History.SOURCE, source); - values.put(Sync.History.EVENT, Sync.History.EVENT_START); - long rowId = db.insert("history", null, values); - mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, null /* observer */); - mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, null /* observer */); - return rowId; + + /** + * Returns the status that matches the authority. If there are multiples accounts for + * the authority, the one with the latest "lastSuccessTime" status is returned. + * @param authority the authority whose row should be selected + * @return the SyncStatusInfo for the authority, or null if none exists + */ + public SyncStatusInfo getStatusByAuthority(String authority) { + synchronized (mAuthorities) { + SyncStatusInfo best = null; + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo cur = mSyncStatus.get(i); + AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); + if (ainfo != null && ainfo.authority.equals(authority)) { + if (best == null) { + best = cur; + } else if (best.lastSuccessTime > cur.lastSuccessTime) { + best = cur; + } + } + } + return best; + } } - - public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, - long downstreamActivity, long upstreamActivity) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - ContentValues values = new ContentValues(); - values.put(Sync.History.ELAPSED_TIME, elapsedTime); - values.put(Sync.History.EVENT, Sync.History.EVENT_STOP); - values.put(Sync.History.MESG, resultMessage); - values.put(Sync.History.DOWNSTREAM_ACTIVITY, downstreamActivity); - values.put(Sync.History.UPSTREAM_ACTIVITY, upstreamActivity); - - int count = db.update("history", values, "_id=?", - new String[]{Long.toString(historyId)}); - // We think that count should always be 1 but don't want to change this until after - // launch. - if (count > 0) { - int source = (int) DatabaseUtils.longForQuery(db, - "SELECT source FROM history WHERE _id=" + historyId, null); - long eventTime = DatabaseUtils.longForQuery(db, - "SELECT eventTime FROM history WHERE _id=" + historyId, null); - long statsId = DatabaseUtils.longForQuery(db, - "SELECT stats_id FROM history WHERE _id=" + historyId, null); - - createStatusRowIfNecessary(statsId); - - // update the status table to reflect this sync - StringBuilder sb = new StringBuilder(); - ArrayList<String> bindArgs = new ArrayList<String>(); - sb.append("UPDATE status SET"); - sb.append(" numSyncs=numSyncs+1"); - sb.append(", totalElapsedTime=totalElapsedTime+" + elapsedTime); - switch (source) { - case Sync.History.SOURCE_LOCAL: - sb.append(", numSourceLocal=numSourceLocal+1"); - break; - case Sync.History.SOURCE_POLL: - sb.append(", numSourcePoll=numSourcePoll+1"); - break; - case Sync.History.SOURCE_USER: - sb.append(", numSourceUser=numSourceUser+1"); - break; - case Sync.History.SOURCE_SERVER: - sb.append(", numSourceServer=numSourceServer+1"); - break; + + /** + * Return true if the pending status is true of any matching authorities. + */ + public boolean isAuthorityPending(String account, String authority) { + synchronized (mAuthorities) { + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo cur = mSyncStatus.get(i); + AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); + if (ainfo == null) { + continue; + } + if (account != null && !ainfo.account.equals(account)) { + continue; + } + if (ainfo.authority.equals(authority) && cur.pending) { + return true; } - - final String statsIdString = String.valueOf(statsId); - final long lastSyncTime = (eventTime + elapsedTime); - if (Sync.History.MESG_SUCCESS.equals(resultMessage)) { - // - if successful, update the successful columns - sb.append(", lastSuccessTime=" + lastSyncTime); - sb.append(", lastSuccessSource=" + source); - sb.append(", lastFailureTime=null"); - sb.append(", lastFailureSource=null"); - sb.append(", lastFailureMesg=null"); - sb.append(", initialFailureTime=null"); - } else if (!Sync.History.MESG_CANCELED.equals(resultMessage)) { - sb.append(", lastFailureTime=" + lastSyncTime); - sb.append(", lastFailureSource=" + source); - sb.append(", lastFailureMesg=?"); - bindArgs.add(resultMessage); - long initialFailureTime = DatabaseUtils.longForQuery(db, - SELECT_INITIAL_FAILURE_TIME_QUERY_STRING, - new String[]{statsIdString, String.valueOf(lastSyncTime)}); - sb.append(", initialFailureTime=" + initialFailureTime); - } - sb.append(" WHERE stats_id=?"); - bindArgs.add(statsIdString); - db.execSQL(sb.toString(), bindArgs.toArray()); - db.setTransactionSuccessful(); - mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, - null /* observer */); - mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, - null /* observer */); } - } finally { - db.endTransaction(); + return false; } } /** + * Return an array of the current sync status for all authorities. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public ArrayList<SyncHistoryItem> getSyncHistory() { + synchronized (mAuthorities) { + final int N = mSyncHistory.size(); + ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); + for (int i=0; i<N; i++) { + items.add(mSyncHistory.get(i)); + } + return items; + } + } + + /** + * Return an array of the current per-day statistics. Note + * that the objects inside the array are the real, live status objects, + * so be careful what you do with them. + */ + public DayStats[] getDayStatistics() { + synchronized (mAuthorities) { + DayStats[] ds = new DayStats[mDayStats.length]; + System.arraycopy(mDayStats, 0, ds, 0, ds.length); + return ds; + } + } + + /** * If sync is failing for any of the provider/accounts then determine the time at which it * started failing and return the earliest time over all the provider/accounts. If none are * failing then return 0. */ public long getInitialSyncFailureTime() { - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - // Join the settings for a provider with the status so that we can easily - // check if each provider is enabled for syncing. We also join in the overall - // enabled flag ("listen_for_tickles") to each row so that we don't need to - // make a separate DB lookup to access it. - Cursor c = db.rawQuery("" - + "SELECT initialFailureTime, s1.value, s2.value " - + "FROM status " - + "LEFT JOIN stats ON status.stats_id=stats._id " - + "LEFT JOIN settings as s1 ON 'sync_provider_' || authority=s1.name " - + "LEFT JOIN settings as s2 ON s2.name='listen_for_tickles' " - + "where initialFailureTime is not null " - + " AND lastFailureMesg!=" + Sync.History.ERROR_TOO_MANY_DELETIONS - + " AND lastFailureMesg!=" + Sync.History.ERROR_AUTHENTICATION - + " AND lastFailureMesg!=" + Sync.History.ERROR_SYNC_ALREADY_IN_PROGRESS - + " AND authority!='subscribedfeeds' " - + " ORDER BY initialFailureTime", null); + synchronized (mAuthorities) { + if (!mListenForTickles) { + return 0; + } + + long oldest = 0; + int i = mSyncStatus.size(); + while (i > 0) { + i--; + SyncStatusInfo stats = mSyncStatus.valueAt(i); + AuthorityInfo authority = mAuthorities.get(stats.authorityId); + if (authority != null && authority.enabled) { + if (oldest == 0 || stats.initialFailureTime < oldest) { + oldest = stats.initialFailureTime; + } + } + } + + return oldest; + } + } + + private int getCurrentDayLocked() { + mCal.setTimeInMillis(System.currentTimeMillis()); + final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); + if (mYear != mCal.get(Calendar.YEAR)) { + mYear = mCal.get(Calendar.YEAR); + mCal.clear(); + mCal.set(Calendar.YEAR, mYear); + mYearInDays = (int)(mCal.getTimeInMillis()/86400000); + } + return dayOfYear + mYearInDays; + } + + /** + * Retrieve an authority, returning null if one does not exist. + * + * @param accountName The name of the account for the authority. + * @param authorityName The name of the authority itself. + * @param tag If non-null, this will be used in a log message if the + * requested authority does not exist. + */ + private AuthorityInfo getAuthorityLocked(String accountName, String authorityName, + String tag) { + AccountInfo account = mAccounts.get(accountName); + if (account == null) { + if (tag != null) { + Log.w(TAG, tag + ": unknown account " + accountName); + } + return null; + } + AuthorityInfo authority = account.authorities.get(authorityName); + if (authority == null) { + if (tag != null) { + Log.w(TAG, tag + ": unknown authority " + authorityName); + } + return null; + } + + return authority; + } + + private AuthorityInfo getOrCreateAuthorityLocked(String accountName, + String authorityName, int ident, boolean doWrite) { + AccountInfo account = mAccounts.get(accountName); + if (account == null) { + account = new AccountInfo(accountName); + mAccounts.put(accountName, account); + } + AuthorityInfo authority = account.authorities.get(authorityName); + if (authority == null) { + if (ident < 0) { + // Look for a new identifier for this authority. + final int N = mAuthorities.size(); + ident = 0; + for (int i=0; i<N; i++) { + if (mAuthorities.valueAt(i).ident > ident) { + break; + } + ident++; + } + } + authority = new AuthorityInfo(accountName, authorityName, ident); + account.authorities.put(authorityName, authority); + mAuthorities.put(ident, authority); + if (doWrite) { + writeAccountInfoLocked(); + } + } + + return authority; + } + + private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { + SyncStatusInfo status = mSyncStatus.get(authorityId); + if (status == null) { + status = new SyncStatusInfo(authorityId); + mSyncStatus.put(authorityId, status); + } + return status; + } + + public void writeAllState() { + synchronized (mAuthorities) { + // Account info is always written so no need to do it here. + + if (mNumPendingFinished > 0) { + // Only write these if they are out of date. + writePendingOperationsLocked(); + } + + // Just always write these... they are likely out of date. + writeStatusLocked(); + writeStatisticsLocked(); + } + } + + /** + * Read all account information back in to the initial engine state. + */ + private void readAccountInfoLocked() { + FileInputStream fis = null; + try { + fis = mAccountInfoFile.openRead(); + if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + 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 ("accounts".equals(tagName)) { + String listen = parser.getAttributeValue( + null, "listen-for-tickles"); + mListenForTickles = listen == null + || Boolean.parseBoolean(listen); + eventType = parser.next(); + do { + if (eventType == XmlPullParser.START_TAG + && parser.getDepth() == 2) { + tagName = parser.getName(); + if ("authority".equals(tagName)) { + int id = -1; + try { + id = Integer.parseInt(parser.getAttributeValue( + null, "id")); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + if (id >= 0) { + String accountName = parser.getAttributeValue( + null, "account"); + String authorityName = parser.getAttributeValue( + null, "authority"); + String enabled = parser.getAttributeValue( + null, "enabled"); + AuthorityInfo authority = mAuthorities.get(id); + if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" + + accountName + " auth=" + authorityName + + " enabled=" + enabled); + if (authority == null) { + if (DEBUG_FILE) Log.v(TAG, "Creating entry"); + authority = getOrCreateAuthorityLocked( + accountName, authorityName, id, false); + } + if (authority != null) { + authority.enabled = enabled == null + || Boolean.parseBoolean(enabled); + } else { + Log.w(TAG, "Failure adding authority: account=" + + accountName + " auth=" + authorityName + + " enabled=" + enabled); + } + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Error reading accounts", e); + } catch (java.io.IOException e) { + if (fis == null) Log.i(TAG, "No initial accounts"); + else Log.w(TAG, "Error reading accounts", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + } + + /** + * Write all account information to the account file. + */ + private void writeAccountInfoLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + FileOutputStream fos = null; + + try { + fos = mAccountInfoFile.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, "accounts"); + if (!mListenForTickles) { + out.attribute(null, "listen-for-tickles", "false"); + } + + final int N = mAuthorities.size(); + for (int i=0; i<N; i++) { + AuthorityInfo authority = mAuthorities.get(i); + out.startTag(null, "authority"); + out.attribute(null, "id", Integer.toString(authority.ident)); + out.attribute(null, "account", authority.account); + out.attribute(null, "authority", authority.authority); + if (!authority.enabled) { + out.attribute(null, "enabled", "false"); + } + out.endTag(null, "authority"); + } + + out.endTag(null, "accounts"); + + out.endDocument(); + + mAccountInfoFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing accounts", e1); + if (fos != null) { + mAccountInfoFile.failWrite(fos); + } + } + } + + static int getIntColumn(Cursor c, String name) { + return c.getInt(c.getColumnIndex(name)); + } + + static long getLongColumn(Cursor c, String name) { + return c.getLong(c.getColumnIndex(name)); + } + + /** + * Load sync engine state from the old syncmanager database, and then + * erase it. Note that we don't deal with pending operations, active + * sync, or history. + */ + private void readLegacyAccountInfoLocked() { + // Look for old database to initialize from. + File file = mContext.getDatabasePath("syncmanager.db"); + if (!file.exists()) { + return; + } + String path = file.getPath(); + SQLiteDatabase db = null; try { + db = SQLiteDatabase.openDatabase(path, null, + SQLiteDatabase.OPEN_READONLY); + } catch (SQLiteException e) { + } + + if (db != null) { + // Copy in all of the status information, as well as accounts. + if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables("stats, status"); + HashMap<String,String> map = new HashMap<String,String>(); + map.put("_id", "status._id as _id"); + map.put("account", "stats.account as account"); + map.put("authority", "stats.authority as authority"); + map.put("totalElapsedTime", "totalElapsedTime"); + map.put("numSyncs", "numSyncs"); + map.put("numSourceLocal", "numSourceLocal"); + map.put("numSourcePoll", "numSourcePoll"); + map.put("numSourceServer", "numSourceServer"); + map.put("numSourceUser", "numSourceUser"); + map.put("lastSuccessSource", "lastSuccessSource"); + map.put("lastSuccessTime", "lastSuccessTime"); + map.put("lastFailureSource", "lastFailureSource"); + map.put("lastFailureTime", "lastFailureTime"); + map.put("lastFailureMesg", "lastFailureMesg"); + map.put("pending", "pending"); + qb.setProjectionMap(map); + qb.appendWhere("stats._id = status.stats_id"); + Cursor c = qb.query(db, null, null, null, null, null, null); while (c.moveToNext()) { - // these settings default to true, so if they are null treat them as enabled - final String providerEnabledString = c.getString(1); - if (providerEnabledString != null && !Boolean.parseBoolean(providerEnabledString)) { - continue; + String accountName = c.getString(c.getColumnIndex("account")); + String authorityName = c.getString(c.getColumnIndex("authority")); + AuthorityInfo authority = this.getOrCreateAuthorityLocked( + accountName, authorityName, -1, false); + if (authority != null) { + int i = mSyncStatus.size(); + boolean found = false; + SyncStatusInfo st = null; + while (i > 0) { + i--; + st = mSyncStatus.get(i); + if (st.authorityId == authority.ident) { + found = true; + break; + } + } + if (!found) { + st = new SyncStatusInfo(authority.ident); + mSyncStatus.put(authority.ident, st); + } + st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); + st.numSyncs = getIntColumn(c, "numSyncs"); + st.numSourceLocal = getIntColumn(c, "numSourceLocal"); + st.numSourcePoll = getIntColumn(c, "numSourcePoll"); + st.numSourceServer = getIntColumn(c, "numSourceServer"); + st.numSourceUser = getIntColumn(c, "numSourceUser"); + st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); + st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); + st.lastFailureSource = getIntColumn(c, "lastFailureSource"); + st.lastFailureTime = getLongColumn(c, "lastFailureTime"); + st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); + st.pending = getIntColumn(c, "pending") != 0; } - final String allEnabledString = c.getString(2); - if (allEnabledString != null && !Boolean.parseBoolean(allEnabledString)) { - continue; + } + + c.close(); + + // Retrieve the settings. + qb = new SQLiteQueryBuilder(); + qb.setTables("settings"); + c = qb.query(db, null, null, null, null, null, null); + while (c.moveToNext()) { + String name = c.getString(c.getColumnIndex("name")); + String value = c.getString(c.getColumnIndex("value")); + if (name == null) continue; + if (name.equals("listen_for_tickles")) { + setListenForNetworkTickles(value == null + || Boolean.parseBoolean(value)); + } else if (name.startsWith("sync_provider_")) { + String provider = name.substring("sync_provider_".length(), + name.length()); + setSyncProviderAutomatically(null, provider, + value == null || Boolean.parseBoolean(value)); } - return c.getLong(0); } - } finally { + c.close(); + + db.close(); + + writeAccountInfoLocked(); + writeStatusLocked(); + (new File(path)).delete(); } - return 0; } - - private void createStatusRowIfNecessary(long statsId) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - boolean statusExists = 0 != DatabaseUtils.longForQuery(db, - "SELECT count(*) FROM status WHERE stats_id=" + statsId, null); - if (!statusExists) { - ContentValues values = new ContentValues(); - values.put("stats_id", statsId); - db.insert("status", null, values); + + public static final int STATUS_FILE_END = 0; + public static final int STATUS_FILE_ITEM = 100; + + /** + * Read all sync status back in to the initial engine state. + */ + private void readStatusLocked() { + if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + try { + byte[] data = mStatusFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + int token; + while ((token=in.readInt()) != STATUS_FILE_END) { + if (token == STATUS_FILE_ITEM) { + SyncStatusInfo status = new SyncStatusInfo(in); + if (mAuthorities.indexOfKey(status.authorityId) >= 0) { + status.pending = false; + if (DEBUG_FILE) Log.v(TAG, "Adding status for id " + + status.authorityId); + mSyncStatus.put(status.authorityId, status); + } + } else { + // Ooops. + Log.w(TAG, "Unknown status token: " + token); + break; + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial status"); } } - - private long createStatsRowIfNecessary(String account, String authority) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - StringBuilder where = new StringBuilder(); - where.append(Sync.Stats.ACCOUNT + "= ?"); - where.append(" and " + Sync.Stats.AUTHORITY + "= ?"); - Cursor cursor = query(Sync.Stats.CONTENT_URI, - Sync.Stats.SYNC_STATS_PROJECTION, - where.toString(), new String[] { account, authority }, - null /* order */); + + /** + * Write all sync status to the sync status file. + */ + private void writeStatusLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + + // The file is being written, so we don't need to have a scheduled + // write until the next change. + removeMessages(MSG_WRITE_STATUS); + + FileOutputStream fos = null; try { - long id; - if (cursor.moveToFirst()) { - id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID)); - } else { - ContentValues values = new ContentValues(); - values.put(Sync.Stats.ACCOUNT, account); - values.put(Sync.Stats.AUTHORITY, authority); - id = db.insert("stats", null, values); + fos = mStatusFile.startWrite(); + Parcel out = Parcel.obtain(); + final int N = mSyncStatus.size(); + for (int i=0; i<N; i++) { + SyncStatusInfo status = mSyncStatus.valueAt(i); + out.writeInt(STATUS_FILE_ITEM); + status.writeToParcel(out, 0); + } + out.writeInt(STATUS_FILE_END); + fos.write(out.marshall()); + out.recycle(); + + mStatusFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing status", e1); + if (fos != null) { + mStatusFile.failWrite(fos); + } + } + } + + public static final int PENDING_OPERATION_VERSION = 1; + + /** + * Read all pending operations back in to the initial engine state. + */ + private void readPendingOperationsLocked() { + if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); + try { + byte[] data = mPendingFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + final int SIZE = in.dataSize(); + while (in.dataPosition() < SIZE) { + int version = in.readInt(); + if (version != PENDING_OPERATION_VERSION) { + Log.w(TAG, "Unknown pending operation version " + + version + "; dropping all ops"); + break; + } + int authorityId = in.readInt(); + int syncSource = in.readInt(); + byte[] flatExtras = in.createByteArray(); + AuthorityInfo authority = mAuthorities.get(authorityId); + if (authority != null) { + Bundle extras = null; + if (flatExtras != null) { + extras = unflattenBundle(flatExtras); + } + PendingOperation op = new PendingOperation( + authority.account, syncSource, + authority.authority, extras); + op.authorityId = authorityId; + op.flatExtras = flatExtras; + if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account + + " auth=" + op.authority + + " src=" + op.syncSource + + " extras=" + op.extras); + mPendingOperations.add(op); + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial pending operations"); + } + } + + private void writePendingOperationLocked(PendingOperation op, Parcel out) { + out.writeInt(PENDING_OPERATION_VERSION); + out.writeInt(op.authorityId); + out.writeInt(op.syncSource); + if (op.flatExtras == null && op.extras != null) { + op.flatExtras = flattenBundle(op.extras); + } + out.writeByteArray(op.flatExtras); + } + + /** + * Write all currently pending ops to the pending ops file. + */ + private void writePendingOperationsLocked() { + final int N = mPendingOperations.size(); + FileOutputStream fos = null; + try { + if (N == 0) { + if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); + mPendingFile.truncate(); + return; } - return id; + + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); + fos = mPendingFile.startWrite(); + + Parcel out = Parcel.obtain(); + for (int i=0; i<N; i++) { + PendingOperation op = mPendingOperations.get(i); + writePendingOperationLocked(op, out); + } + fos.write(out.marshall()); + out.recycle(); + + mPendingFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing pending operations", e1); + if (fos != null) { + mPendingFile.failWrite(fos); + } + } + } + + /** + * Append the given operation to the pending ops file; if unable to, + * write all pending ops. + */ + private void appendPendingOperationLocked(PendingOperation op) { + if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); + FileOutputStream fos = null; + try { + fos = mPendingFile.openAppend(); + } catch (java.io.IOException e) { + if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); + writePendingOperationsLocked(); + return; + } + + try { + Parcel out = Parcel.obtain(); + writePendingOperationLocked(op, out); + fos.write(out.marshall()); + out.recycle(); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing pending operations", e1); + } finally { + try { + fos.close(); + } catch (java.io.IOException e2) { + } + } + } + + static private byte[] flattenBundle(Bundle bundle) { + byte[] flatData = null; + Parcel parcel = Parcel.obtain(); + try { + bundle.writeToParcel(parcel, 0); + flatData = parcel.marshall(); + } finally { + parcel.recycle(); + } + return flatData; + } + + static private Bundle unflattenBundle(byte[] flatData) { + Bundle bundle; + Parcel parcel = Parcel.obtain(); + try { + parcel.unmarshall(flatData, 0, flatData.length); + parcel.setDataPosition(0); + bundle = parcel.readBundle(); + } catch (RuntimeException e) { + // A RuntimeException is thrown if we were unable to parse the parcel. + // Create an empty parcel in this case. + bundle = new Bundle(); } finally { - cursor.close(); + parcel.recycle(); + } + return bundle; + } + + public static final int STATISTICS_FILE_END = 0; + public static final int STATISTICS_FILE_ITEM_OLD = 100; + public static final int STATISTICS_FILE_ITEM = 101; + + /** + * Read all sync statistics back in to the initial engine state. + */ + private void readStatisticsLocked() { + try { + byte[] data = mStatisticsFile.readFully(); + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + int token; + int index = 0; + while ((token=in.readInt()) != STATISTICS_FILE_END) { + if (token == STATISTICS_FILE_ITEM + || token == STATISTICS_FILE_ITEM_OLD) { + int day = in.readInt(); + if (token == STATISTICS_FILE_ITEM_OLD) { + day = day - 2009 + 14245; // Magic! + } + DayStats ds = new DayStats(day); + ds.successCount = in.readInt(); + ds.successTime = in.readLong(); + ds.failureCount = in.readInt(); + ds.failureTime = in.readLong(); + if (index < mDayStats.length) { + mDayStats[index] = ds; + index++; + } + } else { + // Ooops. + Log.w(TAG, "Unknown stats token: " + token); + break; + } + } + } catch (java.io.IOException e) { + Log.i(TAG, "No initial statistics"); + } + } + + /** + * Write all sync statistics to the sync status file. + */ + private void writeStatisticsLocked() { + if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + + // The file is being written, so we don't need to have a scheduled + // write until the next change. + removeMessages(MSG_WRITE_STATISTICS); + + FileOutputStream fos = null; + try { + fos = mStatisticsFile.startWrite(); + Parcel out = Parcel.obtain(); + final int N = mDayStats.length; + for (int i=0; i<N; i++) { + DayStats ds = mDayStats[i]; + if (ds == null) { + break; + } + out.writeInt(STATISTICS_FILE_ITEM); + out.writeInt(ds.day); + out.writeInt(ds.successCount); + out.writeLong(ds.successTime); + out.writeInt(ds.failureCount); + out.writeLong(ds.failureTime); + } + out.writeInt(STATISTICS_FILE_END); + fos.write(out.marshall()); + out.recycle(); + + mStatisticsFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing stats", e1); + if (fos != null) { + mStatisticsFile.failWrite(fos); + } } } } diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java index a98e6d57eed8..72ec46955b9f 100644 --- a/core/java/android/content/UriMatcher.java +++ b/core/java/android/content/UriMatcher.java @@ -19,6 +19,7 @@ package android.content; import android.net.Uri; import java.util.ArrayList; +import java.util.List; import java.util.regex.Pattern; /** @@ -200,7 +201,8 @@ public class UriMatcher */ public int match(Uri uri) { - final int li = uri.getPathSegments().size(); + final List<String> pathSegments = uri.getPathSegments(); + final int li = pathSegments.size(); UriMatcher node = this; @@ -209,7 +211,7 @@ public class UriMatcher } for (int i=-1; i<li; i++) { - String u = i < 0 ? uri.getAuthority() : uri.getPathSegments().get(i); + String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); ArrayList<UriMatcher> list = node.mChildren; if (list == null) { break; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 8d727ed9d0b4..88ac04c24e6d 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -113,19 +113,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6; - /** - * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. - * {@hide} + * Value for {@link #flags}: this is set if this application has been + * install as an update to a built-in system application. */ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; + + /** + * Value for {@link #flags}: this is set of the application has set + * its android:targetSdkVersion to something >= the current SDK version. + */ + public static final int FLAG_TARGETS_SDK = 1<<8; + + /** + * Value for {@link #flags}: this is set of the application has set + * its android:targetSdkVersion to something >= the current SDK version. + */ + public static final int FLAG_TEST_ONLY = 1<<9; /** * Flags associated with the application. Any combination of * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and * {@link #FLAG_ALLOW_TASK_REPARENTING} - * {@link #FLAG_ALLOW_CLEAR_USER_DATA}. + * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, + * {@link #FLAG_TARGETS_SDK}. */ public int flags = 0; @@ -161,6 +173,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public int uid; + + /** + * The list of densities in DPI that application supprots. This + * field is only set if the {@link PackageManager#GET_SUPPORTS_DENSITIES} flag was + * used when retrieving the structure. + */ + public int[] supportsDensities; + /** * When false, indicates that all components within this application are * considered disabled, regardless of their individually set enabled status. @@ -181,8 +201,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); pw.println(prefix + "dataDir=" + dataDir); pw.println(prefix + "enabled=" + enabled); - pw.println(prefix+"manageSpaceActivityName="+manageSpaceActivityName); - pw.println(prefix+"description=0x"+Integer.toHexString(descriptionRes)); + pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); + pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes)); + pw.println(prefix + "supportsDensities=" + supportsDensities); super.dumpBack(pw, prefix); } @@ -228,6 +249,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { enabled = orig.enabled; manageSpaceActivityName = orig.manageSpaceActivityName; descriptionRes = orig.descriptionRes; + supportsDensities = orig.supportsDensities; } @@ -257,6 +279,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(enabled ? 1 : 0); dest.writeString(manageSpaceActivityName); dest.writeInt(descriptionRes); + dest.writeIntArray(supportsDensities); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -285,8 +308,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { enabled = source.readInt() != 0; manageSpaceActivityName = source.readString(); descriptionRes = source.readInt(); + supportsDensities = source.createIntArray(); } - + /** * Retrieve the textual description of the application. This * will call back on the given PackageManager to load the description from diff --git a/core/java/android/content/pm/IPackageInstallObserver.aidl b/core/java/android/content/pm/IPackageInstallObserver.aidl index e83bbc67d117..613336537317 100644 --- a/core/java/android/content/pm/IPackageInstallObserver.aidl +++ b/core/java/android/content/pm/IPackageInstallObserver.aidl @@ -19,7 +19,7 @@ package android.content.pm; /** * API for installation callbacks from the Package Manager. - * + * @hide */ oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index d3f6f3c564c8..c199619c57a9 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -139,8 +139,11 @@ interface IPackageManager { * @param observer a callback to use to notify when the package installation in finished. * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE}, * {@link #REPLACE_EXISITING_PACKAGE} + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. */ - void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags); + void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags, + in String installerPackageName); /** * Delete a package. @@ -151,6 +154,8 @@ interface IPackageManager { */ void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags); + String getInstallerPackageName(in String packageName); + void addPackageToPreferred(String packageName); void removePackageFromPreferred(String packageName); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3e9473472dcb..3a192f7eb749 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -166,6 +166,20 @@ public abstract class PackageManager { public static final int GET_CONFIGURATIONS = 0x00004000; /** + * {@link ApplicationInfo} flag: return the + * {@link ApplicationInfo#supportsDensities} that the package supports. + */ + public static final int GET_SUPPORTS_DENSITIES = 0x00008000; + + /** + * Resolution and querying flag: if set, only filters that support the + * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for + * matching. This is a synonym for including the CATEGORY_DEFAULT in your + * supplied Intent. + */ + public static final int MATCH_DEFAULT_ONLY = 0x00010000; + + /** * Permission check result: this is returned by {@link #checkPermission} * if the permission has been granted to the given package. */ @@ -213,14 +227,6 @@ public abstract class PackageManager { */ public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; - /** - * Resolution and querying flag: if set, only filters that support the - * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for - * matching. This is a synonym for including the CATEGORY_DEFAULT in your - * supplied Intent. - */ - public static final int MATCH_DEFAULT_ONLY = 0x00010000; - public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; @@ -229,14 +235,24 @@ public abstract class PackageManager { * Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to * indicate that this package should be installed as forward locked, i.e. only the app itself * should have access to it's code and non-resource assets. + * @hide */ - public static final int FORWARD_LOCK_PACKAGE = 0x00000001; + public static final int INSTALL_FORWARD_LOCK = 0x00000001; /** * Flag parameter for {@link #installPackage} to indicate that you want to replace an already - * installed package, if one exists + * installed package, if one exists. + * @hide + */ + public static final int INSTALL_REPLACE_EXISTING = 0x00000002; + + /** + * Flag parameter for {@link #installPackage} to indicate that you want to + * allow test packages (those that have set android:testOnly in their + * manifest) to be installed. + * @hide */ - public static final int REPLACE_EXISTING_PACKAGE = 0x00000002; + public static final int INSTALL_ALLOW_TEST = 0x00000004; /** * Flag parameter for @@ -249,6 +265,7 @@ public abstract class PackageManager { /** * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success. + * @hide */ public static final int INSTALL_SUCCEEDED = 1; @@ -256,6 +273,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package is * already installed. + * @hide */ public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; @@ -263,6 +281,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package archive * file is invalid. + * @hide */ public static final int INSTALL_FAILED_INVALID_APK = -2; @@ -270,13 +289,15 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the URI passed in * is invalid. + * @hide */ public static final int INSTALL_FAILED_INVALID_URI = -3; /** * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package manager - * service found that the device didn't have enough storage space to install the app + * service found that the device didn't have enough storage space to install the app. + * @hide */ public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; @@ -284,6 +305,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a * package is already installed with the same name. + * @hide */ public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; @@ -291,6 +313,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the requested shared user does not exist. + * @hide */ public static final int INSTALL_FAILED_NO_SHARED_USER = -6; @@ -299,6 +322,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * a previously installed package of the same name has a different signature * than the new package (and the old package's data was not removed). + * @hide */ public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; @@ -307,6 +331,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package is requested a shared user which is already installed on the * device and does not have matching signature. + * @hide */ public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; @@ -314,6 +339,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package uses a shared library that is not available. + * @hide */ public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; @@ -321,6 +347,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package uses a shared library that is not available. + * @hide */ public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; @@ -329,6 +356,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed while optimizing and validating its dex files, * either because there was not enough storage or the validation failed. + * @hide */ public static final int INSTALL_FAILED_DEXOPT = -11; @@ -337,6 +365,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed because the current SDK version is older than * that required by the package. + * @hide */ public static final int INSTALL_FAILED_OLDER_SDK = -12; @@ -345,14 +374,35 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed because it contains a content provider with the * same authority as a provider already installed in the system. + * @hide */ public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because the current SDK version is newer than + * that required by the package. + * @hide + */ + public static final int INSTALL_FAILED_NEWER_SDK = -14; + + /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because it has specified that it is a test-only + * package and the caller has not supplied the {@link #INSTALL_ALLOW_TEST} + * flag. + * @hide + */ + public static final int INSTALL_FAILED_TEST_ONLY = -15; + + /** * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was given a path that is not a file, or does not end with the expected * '.apk' extension. + * @hide */ public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; @@ -360,6 +410,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was unable to retrieve the AndroidManifest.xml file. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; @@ -367,6 +418,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered an unexpected exception. + * @hide */ public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; @@ -374,6 +426,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser did not find any certificates in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; @@ -381,6 +434,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser found inconsistent certificates on the files in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; @@ -389,6 +443,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a CertificateEncodingException in one of the * files in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; @@ -396,6 +451,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a bad or missing package name in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; @@ -403,6 +459,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a bad shared user id name in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; @@ -410,6 +467,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered some structural problem in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; @@ -418,6 +476,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser did not find any actionable tags (instrumentation or application) * in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; @@ -1324,6 +1383,8 @@ public abstract class PackageManager { } /** + * @hide + * * Install a package. Since this may take a little while, the result will * be posted back to the given observer. An installation will fail if the calling context * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the @@ -1335,13 +1396,14 @@ public abstract class PackageManager { * @param observer An observer callback to get notified when the package installation is * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be * called when that happens. observer may be null to indicate that no callback is desired. - * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE}, - * {@link #REPLACE_EXISTING_PACKAGE} - * - * @see #installPackage(android.net.Uri) + * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, + * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. */ public abstract void installPackage( - Uri packageURI, IPackageInstallObserver observer, int flags); + Uri packageURI, IPackageInstallObserver observer, int flags, + String installerPackageName); /** * Attempts to delete a package. Since this may take a little while, the result will @@ -1360,6 +1422,17 @@ public abstract class PackageManager { */ public abstract void deletePackage( String packageName, IPackageDeleteObserver observer, int flags); + + /** + * Retrieve the package name of the application that installed a package. This identifies + * which market the package came from. + * + * @param packageName The name of the package to query + * + * @hide + */ + public abstract String getInstallerPackageName(String packageName); + /** * Attempts to clear the user data directory of an application. * Since this may take a little while, the result will @@ -1465,17 +1538,6 @@ public abstract class PackageManager { IPackageStatsObserver observer); /** - * Install a package. - * - * @param packageURI The location of the package file to install - * - * @see #installPackage(android.net.Uri, IPackageInstallObserver, int) - */ - public void installPackage(Uri packageURI) { - installPackage(packageURI, null, 0); - } - - /** * Add a new package to the list of preferred packages. This new package * will be added to the front of the list (removed from its current location * if already listed), meaning it will now be preferred over all other diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2dcb4830bf48..88907c180117 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -45,6 +45,7 @@ import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -58,12 +59,55 @@ public class PackageParser { private String mArchiveSourcePath; private String[] mSeparateProcesses; private int mSdkVersion; + private String mSdkCodename; private int mParseError = PackageManager.INSTALL_SUCCEEDED; private static final Object mSync = new Object(); private static WeakReference<byte[]> mReadBuffer; + static class ParsePackageItemArgs { + final Package owner; + final String[] outError; + final int nameRes; + final int labelRes; + final int iconRes; + + String tag; + TypedArray sa; + + ParsePackageItemArgs(Package _owner, String[] _outError, + int _nameRes, int _labelRes, int _iconRes) { + owner = _owner; + outError = _outError; + nameRes = _nameRes; + labelRes = _labelRes; + iconRes = _iconRes; + } + } + + static class ParseComponentArgs extends ParsePackageItemArgs { + final String[] sepProcesses; + final int processRes; + final int enabledRes; + int flags; + + ParseComponentArgs(Package _owner, String[] _outError, + int _nameRes, int _labelRes, int _iconRes, + String[] _sepProcesses, int _processRes,int _enabledRes) { + super(_owner, _outError, _nameRes, _labelRes, _iconRes); + sepProcesses = _sepProcesses; + processRes = _processRes; + enabledRes = _enabledRes; + } + } + + private ParsePackageItemArgs mParseInstrumentationArgs; + private ParseComponentArgs mParseActivityArgs; + private ParseComponentArgs mParseActivityAliasArgs; + private ParseComponentArgs mParseServiceArgs; + private ParseComponentArgs mParseProviderArgs; + /** If set to true, we will only allow package files that exactly match * the DTD. Otherwise, we try to get as much from the package as we * can without failing. This should normally be set to false, to @@ -80,8 +124,9 @@ public class PackageParser { mSeparateProcesses = procs; } - public void setSdkVersion(int sdkVersion) { + public void setSdkVersion(int sdkVersion, String codename) { mSdkVersion = sdkVersion; + mSdkCodename = codename; } private static final boolean isPackageFilename(String name) { @@ -557,6 +602,11 @@ public class PackageParser { throws XmlPullParserException, IOException { AttributeSet attrs = parser; + mParseInstrumentationArgs = null; + mParseActivityArgs = null; + mParseServiceArgs = null; + mParseProviderArgs = null; + String pkgName = parsePackageName(parser, attrs, flags, outError); if (pkgName == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; @@ -565,9 +615,9 @@ public class PackageParser { int type; final Package pkg = new Package(pkgName); - pkg.mSystem = (flags&PARSE_IS_SYSTEM) != 0; boolean foundApp = false; - + boolean targetsSdk = false; + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifest); pkg.mVersionCode = sa.getInteger( @@ -593,8 +643,6 @@ public class PackageParser { } sa.recycle(); - final int innerDepth = parser.getDepth(); - int outerDepth = parser.getDepth(); while ((type=parser.next()) != parser.END_DOCUMENT && (type != parser.END_TAG || parser.getDepth() > outerDepth)) { @@ -675,19 +723,74 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("uses-sdk")) { + } else if (tagName.equals("uses-sdk")) { if (mSdkVersion > 0) { sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesSdk); - int vers = sa.getInt( - com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion, 0); + int minVers = 0; + String minCode = null; + int targetVers = 0; + String targetCode = null; + + TypedValue val = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion); + if (val != null) { + if (val.type == TypedValue.TYPE_STRING && val.string != null) { + targetCode = minCode = val.string.toString(); + } else { + // If it's not a string, it's an integer. + minVers = val.data; + } + } + + val = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + if (val != null) { + if (val.type == TypedValue.TYPE_STRING && val.string != null) { + targetCode = minCode = val.string.toString(); + } else { + // If it's not a string, it's an integer. + targetVers = val.data; + } + } + + int maxVers = sa.getInt( + com.android.internal.R.styleable.AndroidManifestUsesSdk_maxSdkVersion, + mSdkVersion); sa.recycle(); - if (vers > mSdkVersion) { - outError[0] = "Requires newer sdk version #" + vers - + " (current version is #" + mSdkVersion + ")"; + if (targetCode != null) { + if (!targetCode.equals(mSdkCodename)) { + if (mSdkCodename != null) { + outError[0] = "Requires development platform " + targetCode + + " (current platform is " + mSdkCodename + ")"; + } else { + outError[0] = "Requires development platform " + targetCode + + " but this is a release platform."; + } + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + // If the code matches, it definitely targets this SDK. + targetsSdk = true; + } else if (targetVers >= mSdkVersion) { + // If they have explicitly targeted our current version + // or something after it, then note this. + targetsSdk = true; + } + + if (minVers > mSdkVersion) { + outError[0] = "Requires newer sdk version #" + minVers + + " (current version is #" + mSdkVersion + ")"; + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + + if (maxVers < mSdkVersion) { + outError[0] = "Requires older sdk version #" + maxVers + + " (current version is #" + mSdkVersion + ")"; mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } @@ -721,11 +824,23 @@ public class PackageParser { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY; } + if (targetsSdk) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_TARGETS_SDK; + } + if (pkg.usesLibraries.size() > 0) { pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()]; pkg.usesLibraries.toArray(pkg.usesLibraryFiles); } + int size = pkg.supportsDensityList.size(); + if (size > 0) { + int densities[] = pkg.supportsDensities = new int[size]; + List<Integer> densityList = pkg.supportsDensityList; + for (int i = 0; i < size; i++) { + densities[i] = densityList.get(i); + } + } return pkg; } @@ -950,20 +1065,24 @@ public class PackageParser { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestInstrumentation); - Instrumentation a = new Instrumentation(owner); - - if (!parsePackageItemInfo(owner, a.info, outError, "<instrumentation>", sa, - com.android.internal.R.styleable.AndroidManifestInstrumentation_name, - com.android.internal.R.styleable.AndroidManifestInstrumentation_label, - com.android.internal.R.styleable.AndroidManifestInstrumentation_icon)) { + if (mParseInstrumentationArgs == null) { + mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError, + com.android.internal.R.styleable.AndroidManifestInstrumentation_name, + com.android.internal.R.styleable.AndroidManifestInstrumentation_label, + com.android.internal.R.styleable.AndroidManifestInstrumentation_icon); + mParseInstrumentationArgs.tag = "<instrumentation>"; + } + + mParseInstrumentationArgs.sa = sa; + + Instrumentation a = new Instrumentation(mParseInstrumentationArgs, + new InstrumentationInfo()); + if (outError[0] != null) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } - a.component = new ComponentName(owner.applicationInfo.packageName, - a.info.name); - String str; str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage); @@ -1068,6 +1187,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_testOnly, + false)) { + ai.flags |= ApplicationInfo.FLAG_TEST_ONLY; + } + String str; str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestApplication_permission); @@ -1140,7 +1265,7 @@ public class PackageParser { owner.providers.add(p); } else if (tagName.equals("activity-alias")) { - Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError, false); + Activity a = parseActivityAlias(owner, res, parser, attrs, flags, outError); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; @@ -1173,6 +1298,21 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); + } else if (tagName.equals("supports-density")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestSupportsDensity); + + int density = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1); + + sa.recycle(); + + if (density != -1 && !owner.supportsDensityList.contains(density)) { + owner.supportsDensityList.add(density); + } + + XmlUtils.skipCurrentTag(parser); + } else { if (!RIGID_PARSER) { Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); @@ -1239,22 +1379,29 @@ public class PackageParser { return outError[0] == null; } - + private Activity parseActivity(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError, boolean receiver) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestActivity); - Activity a = new Activity(owner); - - if (!parseComponentInfo(owner, flags, a.info, outError, - receiver ? "<receiver>" : "<activity>", sa, - com.android.internal.R.styleable.AndroidManifestActivity_name, - com.android.internal.R.styleable.AndroidManifestActivity_label, - com.android.internal.R.styleable.AndroidManifestActivity_icon, - com.android.internal.R.styleable.AndroidManifestActivity_process, - com.android.internal.R.styleable.AndroidManifestActivity_enabled)) { + if (mParseActivityArgs == null) { + mParseActivityArgs = new ParseComponentArgs(owner, outError, + com.android.internal.R.styleable.AndroidManifestActivity_name, + com.android.internal.R.styleable.AndroidManifestActivity_label, + com.android.internal.R.styleable.AndroidManifestActivity_icon, + mSeparateProcesses, + com.android.internal.R.styleable.AndroidManifestActivity_process, + com.android.internal.R.styleable.AndroidManifestActivity_enabled); + } + + mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>"; + mParseActivityArgs.sa = sa; + mParseActivityArgs.flags = flags; + + Activity a = new Activity(mParseActivityArgs, new ActivityInfo()); + if (outError[0] != null) { sa.recycle(); return null; } @@ -1266,9 +1413,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_exported, false); } - a.component = new ComponentName(owner.applicationInfo.packageName, - a.info.name); - a.info.theme = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestActivity_theme, 0); @@ -1412,8 +1556,8 @@ public class PackageParser { } private Activity parseActivityAlias(Package owner, Resources res, - XmlPullParser parser, AttributeSet attrs, int flags, String[] outError, - boolean receiver) throws XmlPullParserException, IOException { + XmlPullParser parser, AttributeSet attrs, int flags, String[] outError) + throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestActivityAlias); @@ -1432,7 +1576,20 @@ public class PackageParser { return null; } - Activity a = new Activity(owner); + if (mParseActivityAliasArgs == null) { + mParseActivityAliasArgs = new ParseComponentArgs(owner, outError, + com.android.internal.R.styleable.AndroidManifestActivityAlias_name, + com.android.internal.R.styleable.AndroidManifestActivityAlias_label, + com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, + mSeparateProcesses, + 0, + com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled); + mParseActivityAliasArgs.tag = "<activity-alias>"; + } + + mParseActivityAliasArgs.sa = sa; + mParseActivityAliasArgs.flags = flags; + Activity target = null; final int NA = owner.activities.size(); @@ -1451,26 +1608,21 @@ public class PackageParser { return null; } - a.info.targetActivity = targetActivity; - - a.info.configChanges = target.info.configChanges; - a.info.flags = target.info.flags; - a.info.icon = target.info.icon; - a.info.labelRes = target.info.labelRes; - a.info.launchMode = target.info.launchMode; - a.info.nonLocalizedLabel = target.info.nonLocalizedLabel; - a.info.processName = target.info.processName; - a.info.screenOrientation = target.info.screenOrientation; - a.info.taskAffinity = target.info.taskAffinity; - a.info.theme = target.info.theme; - - if (!parseComponentInfo(owner, flags, a.info, outError, - receiver ? "<receiver>" : "<activity>", sa, - com.android.internal.R.styleable.AndroidManifestActivityAlias_name, - com.android.internal.R.styleable.AndroidManifestActivityAlias_label, - com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, - 0, - com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled)) { + ActivityInfo info = new ActivityInfo(); + info.targetActivity = targetActivity; + info.configChanges = target.info.configChanges; + info.flags = target.info.flags; + info.icon = target.info.icon; + info.labelRes = target.info.labelRes; + info.nonLocalizedLabel = target.info.nonLocalizedLabel; + info.launchMode = target.info.launchMode; + info.processName = target.info.processName; + info.screenOrientation = target.info.screenOrientation; + info.taskAffinity = target.info.taskAffinity; + info.theme = target.info.theme; + + Activity a = new Activity(mParseActivityAliasArgs, info); + if (outError[0] != null) { sa.recycle(); return null; } @@ -1482,9 +1634,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivityAlias_exported, false); } - a.component = new ComponentName(owner.applicationInfo.packageName, - a.info.name); - String str; str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestActivityAlias_permission); @@ -1548,14 +1697,22 @@ public class PackageParser { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestProvider); - Provider p = new Provider(owner); - - if (!parseComponentInfo(owner, flags, p.info, outError, "<provider>", sa, - com.android.internal.R.styleable.AndroidManifestProvider_name, - com.android.internal.R.styleable.AndroidManifestProvider_label, - com.android.internal.R.styleable.AndroidManifestProvider_icon, - com.android.internal.R.styleable.AndroidManifestProvider_process, - com.android.internal.R.styleable.AndroidManifestProvider_enabled)) { + if (mParseProviderArgs == null) { + mParseProviderArgs = new ParseComponentArgs(owner, outError, + com.android.internal.R.styleable.AndroidManifestProvider_name, + com.android.internal.R.styleable.AndroidManifestProvider_label, + com.android.internal.R.styleable.AndroidManifestProvider_icon, + mSeparateProcesses, + com.android.internal.R.styleable.AndroidManifestProvider_process, + com.android.internal.R.styleable.AndroidManifestProvider_enabled); + mParseProviderArgs.tag = "<provider>"; + } + + mParseProviderArgs.sa = sa; + mParseProviderArgs.flags = flags; + + Provider p = new Provider(mParseProviderArgs, new ProviderInfo()); + if (outError[0] != null) { sa.recycle(); return null; } @@ -1563,9 +1720,6 @@ public class PackageParser { p.info.exported = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_exported, true); - p.component = new ComponentName(owner.applicationInfo.packageName, - p.info.name); - String cpname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestProvider_authorities); @@ -1706,14 +1860,22 @@ public class PackageParser { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestService); - Service s = new Service(owner); - - if (!parseComponentInfo(owner, flags, s.info, outError, "<service>", sa, - com.android.internal.R.styleable.AndroidManifestService_name, - com.android.internal.R.styleable.AndroidManifestService_label, - com.android.internal.R.styleable.AndroidManifestService_icon, - com.android.internal.R.styleable.AndroidManifestService_process, - com.android.internal.R.styleable.AndroidManifestService_enabled)) { + if (mParseServiceArgs == null) { + mParseServiceArgs = new ParseComponentArgs(owner, outError, + com.android.internal.R.styleable.AndroidManifestService_name, + com.android.internal.R.styleable.AndroidManifestService_label, + com.android.internal.R.styleable.AndroidManifestService_icon, + mSeparateProcesses, + com.android.internal.R.styleable.AndroidManifestService_process, + com.android.internal.R.styleable.AndroidManifestService_enabled); + mParseServiceArgs.tag = "<service>"; + } + + mParseServiceArgs.sa = sa; + mParseServiceArgs.flags = flags; + + Service s = new Service(mParseServiceArgs, new ServiceInfo()); + if (outError[0] != null) { sa.recycle(); return null; } @@ -1725,9 +1887,6 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_exported, false); } - s.component = new ComponentName(owner.applicationInfo.packageName, - s.info.name); - String str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestService_permission); if (str == null) { @@ -2035,12 +2194,12 @@ public class PackageParser { // We store the application meta-data independently to avoid multiple unwanted references public Bundle mAppMetaData = null; + public final ArrayList<Integer> supportsDensityList = new ArrayList<Integer>(); + public int[] supportsDensities = null; + // If this is a 3rd party app, this is the path of the zip file. public String mPath; - // True if this package is part of the system image. - public boolean mSystem; - // The version code declared for this package. public int mVersionCode; @@ -2084,16 +2243,75 @@ public class PackageParser { public static class Component<II extends IntentInfo> { public final Package owner; - public final ArrayList<II> intents = new ArrayList<II>(0); - public ComponentName component; + public final ArrayList<II> intents; + public final ComponentName component; + public final String componentShortName; public Bundle metaData; public Component(Package _owner) { owner = _owner; + intents = null; + component = null; + componentShortName = null; + } + + public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) { + owner = args.owner; + intents = new ArrayList<II>(0); + String name = args.sa.getNonResourceString(args.nameRes); + if (name == null) { + component = null; + componentShortName = null; + args.outError[0] = args.tag + " does not specify android:name"; + return; + } + + outInfo.name + = buildClassName(owner.applicationInfo.packageName, name, args.outError); + if (outInfo.name == null) { + component = null; + componentShortName = null; + args.outError[0] = args.tag + " does not have valid android:name"; + return; + } + + component = new ComponentName(owner.applicationInfo.packageName, + outInfo.name); + componentShortName = component.flattenToShortString(); + + int iconVal = args.sa.getResourceId(args.iconRes, 0); + if (iconVal != 0) { + outInfo.icon = iconVal; + outInfo.nonLocalizedLabel = null; + } + + TypedValue v = args.sa.peekValue(args.labelRes); + if (v != null && (outInfo.labelRes=v.resourceId) == 0) { + outInfo.nonLocalizedLabel = v.coerceToString(); + } + + outInfo.packageName = owner.packageName; + } + + public Component(final ParseComponentArgs args, final ComponentInfo outInfo) { + this(args, (PackageItemInfo)outInfo); + if (args.outError[0] != null) { + return; + } + + if (args.processRes != 0) { + outInfo.processName = buildProcessName(owner.applicationInfo.packageName, + owner.applicationInfo.processName, args.sa.getNonResourceString(args.processRes), + args.flags, args.sepProcesses, args.outError); + } + outInfo.enabled = args.sa.getBoolean(args.enabledRes, true); } public Component(Component<II> clone) { owner = clone.owner; + intents = clone.intents; + component = clone.component; + componentShortName = clone.componentShortName; metaData = clone.metaData; } } @@ -2149,6 +2367,10 @@ public class PackageParser { && p.usesLibraryFiles != null) { return true; } + if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0 + && p.supportsDensities != null) { + return true; + } return false; } @@ -2166,6 +2388,9 @@ public class PackageParser { if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) { ai.sharedLibraryFiles = p.usesLibraryFiles; } + if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0) { + ai.supportsDensities = p.supportsDensities; + } return ai; } @@ -2192,14 +2417,14 @@ public class PackageParser { } public final static class Activity extends Component<ActivityIntentInfo> { - public final ActivityInfo info = - new ActivityInfo(); + public final ActivityInfo info; - public Activity(Package _owner) { - super(_owner); - info.applicationInfo = owner.applicationInfo; + public Activity(final ParseComponentArgs args, final ActivityInfo _info) { + super(args, _info); + info = _info; + info.applicationInfo = args.owner.applicationInfo; } - + public String toString() { return "Activity{" + Integer.toHexString(System.identityHashCode(this)) @@ -2221,14 +2446,14 @@ public class PackageParser { } public final static class Service extends Component<ServiceIntentInfo> { - public final ServiceInfo info = - new ServiceInfo(); + public final ServiceInfo info; - public Service(Package _owner) { - super(_owner); - info.applicationInfo = owner.applicationInfo; + public Service(final ParseComponentArgs args, final ServiceInfo _info) { + super(args, _info); + info = _info; + info.applicationInfo = args.owner.applicationInfo; } - + public String toString() { return "Service{" + Integer.toHexString(System.identityHashCode(this)) @@ -2252,13 +2477,13 @@ public class PackageParser { public final ProviderInfo info; public boolean syncable; - public Provider(Package _owner) { - super(_owner); - info = new ProviderInfo(); - info.applicationInfo = owner.applicationInfo; + public Provider(final ParseComponentArgs args, final ProviderInfo _info) { + super(args, _info); + info = _info; + info.applicationInfo = args.owner.applicationInfo; syncable = false; } - + public Provider(Provider existingProvider) { super(existingProvider); this.info = existingProvider.info; @@ -2291,13 +2516,13 @@ public class PackageParser { } public final static class Instrumentation extends Component { - public final InstrumentationInfo info = - new InstrumentationInfo(); + public final InstrumentationInfo info; - public Instrumentation(Package _owner) { - super(_owner); + public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo _info) { + super(args, _info); + info = _info; } - + public String toString() { return "Instrumentation{" + Integer.toHexString(System.identityHashCode(this)) diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 956b15a78828..bb3486c20554 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -144,11 +144,29 @@ public final class Configuration implements Parcelable, Comparable<Configuration } public String toString() { - return "{ scale=" + fontScale + " imsi=" + mcc + "/" + mnc - + " locale=" + locale - + " touch=" + touchscreen + " key=" + keyboard + "/" - + keyboardHidden + "/" + hardKeyboardHidden - + " nav=" + navigation + " orien=" + orientation + " }"; + StringBuilder sb = new StringBuilder(128); + sb.append("{ scale="); + sb.append(fontScale); + sb.append(" imsi="); + sb.append(mcc); + sb.append("/"); + sb.append(mnc); + sb.append(" loc="); + sb.append(locale); + sb.append(" touch="); + sb.append(touchscreen); + sb.append(" keys="); + sb.append(keyboard); + sb.append("/"); + sb.append(keyboardHidden); + sb.append("/"); + sb.append(hardKeyboardHidden); + sb.append(" nav="); + sb.append(navigation); + sb.append(" orien="); + sb.append(orientation); + sb.append('}'); + return sb.toString(); } /** diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e020462f8bd7..665e40ca362f 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.graphics.Movie; import android.graphics.drawable.Drawable; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.SystemProperties; @@ -56,12 +57,14 @@ public class Resources { // Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all // single-threaded, and after that these are immutable. - private static final SparseArray<Drawable.ConstantState> mPreloadedDrawables + private static final SparseArray<Drawable.ConstantState> sPreloadedDrawables = new SparseArray<Drawable.ConstantState>(); private static final SparseArray<ColorStateList> mPreloadedColorStateLists = new SparseArray<ColorStateList>(); private static boolean mPreloaded; + private final SparseArray<Drawable.ConstantState> mPreloadedDrawables; + /*package*/ final TypedValue mTmpValue = new TypedValue(); // These are protected by the mTmpValue lock. @@ -82,6 +85,22 @@ public class Resources { /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); PluralRules mPluralRule; + private static final SparseArray<Object> EMPTY_ARRAY = new SparseArray<Object>() { + @Override + public void put(int k, Object o) { + throw new UnsupportedOperationException(); + } + @Override + public void append(int k, Object o) { + throw new UnsupportedOperationException(); + } + }; + + @SuppressWarnings("unchecked") + private static <T> SparseArray<T> emptySparseArray() { + return (SparseArray<T>) EMPTY_ARRAY; + } + /** * This exception is thrown by the resource APIs when a requested resource * can not be found. @@ -107,11 +126,27 @@ public class Resources { */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { + this(assets, metrics, config, true); + } + + /** + * Create a resource with an additional flag for preloaded + * drawable cache. Used by {@link ActivityThread}. + * + * @hide + */ + public Resources(AssetManager assets, DisplayMetrics metrics, + Configuration config, boolean usePreloadedCache) { mAssets = assets; mConfiguration.setToDefaults(); mMetrics.setToDefaults(); updateConfiguration(config, metrics); assets.ensureStringBlocks(); + if (usePreloadedCache) { + mPreloadedDrawables = sPreloadedDrawables; + } else { + mPreloadedDrawables = emptySparseArray(); + } } /** @@ -1218,6 +1253,7 @@ public class Resources { mMetrics.setTo(metrics); } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; + String locale = null; if (mConfiguration.locale != null) { locale = mConfiguration.locale.getLanguage(); @@ -1653,7 +1689,7 @@ public class Resources { cs = dr.getConstantState(); if (cs != null) { if (mPreloading) { - mPreloadedDrawables.put(key, cs); + sPreloadedDrawables.put(key, cs); } else { synchronized (mTmpValue) { //Log.i(TAG, "Saving cached drawable @ #" + @@ -1883,6 +1919,6 @@ public class Resources { mMetrics.setToDefaults(); updateConfiguration(null, null); mAssets.ensureStringBlocks(); + mPreloadedDrawables = sPreloadedDrawables; } } - |
