summaryrefslogtreecommitdiff
path: root/core/java/android/content
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2009-05-20 11:28:04 -0700
committerJean-Baptiste Queru <jbq@google.com>2009-05-20 11:28:04 -0700
commit843ef36f7b96cc19ea7d2996b7c8661b41ec3452 (patch)
tree560e1648c99a93986f8b7deef851ef8bb8029db7 /core/java/android/content
parent358d23017d0d6c4636eb7599ae7a9b48108899a3 (diff)
donut snapshot
Diffstat (limited to 'core/java/android/content')
-rw-r--r--core/java/android/content/ActiveSyncInfo.aidl19
-rw-r--r--core/java/android/content/ActiveSyncInfo.java64
-rw-r--r--core/java/android/content/ComponentName.java12
-rw-r--r--core/java/android/content/ContentResolver.java35
-rw-r--r--core/java/android/content/ContentService.java169
-rw-r--r--core/java/android/content/ContentServiceNative.java216
-rw-r--r--core/java/android/content/Context.java19
-rw-r--r--core/java/android/content/ContextWrapper.java8
-rw-r--r--core/java/android/content/IContentService.aidl83
-rw-r--r--core/java/android/content/IContentService.java53
-rw-r--r--core/java/android/content/ISyncStatusObserver.aidl24
-rw-r--r--core/java/android/content/Intent.java140
-rw-r--r--core/java/android/content/IntentFilter.java42
-rw-r--r--core/java/android/content/SyncManager.java666
-rw-r--r--core/java/android/content/SyncProvider.java53
-rw-r--r--core/java/android/content/SyncStatusInfo.aidl19
-rw-r--r--core/java/android/content/SyncStatusInfo.java108
-rw-r--r--core/java/android/content/SyncStorageEngine.java2055
-rw-r--r--core/java/android/content/UriMatcher.java6
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java38
-rw-r--r--core/java/android/content/pm/IPackageInstallObserver.aidl2
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl7
-rw-r--r--core/java/android/content/pm/PackageManager.java118
-rw-r--r--core/java/android/content/pm/PackageParser.java443
-rw-r--r--core/java/android/content/res/Configuration.java28
-rw-r--r--core/java/android/content/res/Resources.java42
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;
}
}
-