expose some sync control methods
- ActiveSyncInfo
- ContentResolver.addStatusChangeListener
  - SYNC_OBSERVER_TYPE_SETTINGS
  - SYNC_OBSERVER_TYPE_PENDING
  - SYNC_OBSERVER_TYPE_ACTIVE
- make the ContentService resilient to nulls passed in to the
  status change listener registration and unregistration calls

bug http://b/issue?id=2337197
diff --git a/api/current.xml b/api/current.xml
index 394a68a..815b9ff 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -30038,6 +30038,48 @@
 >
 </field>
 </class>
+<class name="ActiveSyncInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getAccount"
+ return="android.accounts.Account"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAuthority"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getStartTime"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
 <class name="ActivityNotFoundException"
  extends="java.lang.RuntimeException"
  abstract="false"
@@ -32073,6 +32115,17 @@
 <parameter name="selectionArgs" type="java.lang.String[]">
 </parameter>
 </method>
+<method name="getActiveSync"
+ return="android.content.ActiveSyncInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getIsSyncable"
  return="int"
  abstract="false"
@@ -32679,6 +32732,39 @@
  visibility="public"
 >
 </field>
+<field name="SYNC_OBSERVER_TYPE_ACTIVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SYNC_OBSERVER_TYPE_PENDING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SYNC_OBSERVER_TYPE_SETTINGS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="ContentUris"
  extends="java.lang.Object"
diff --git a/core/java/android/content/ActiveSyncInfo.java b/core/java/android/content/ActiveSyncInfo.java
index 209dffa..1255304 100644
--- a/core/java/android/content/ActiveSyncInfo.java
+++ b/core/java/android/content/ActiveSyncInfo.java
@@ -20,13 +20,50 @@
 import android.os.Parcel;
 import android.os.Parcelable.Creator;
 
-/** @hide */
+/**
+ * Information about the sync operation that is currently underway.
+ */
 public class ActiveSyncInfo {
-    public final int authorityId;
-    public final Account account;
-    public final String authority;
-    public final long startTime;
-    
+    /** @hide */
+    private final int authorityId;
+    /** @hide */
+    private final Account account;
+    /** @hide */
+    private final String authority;
+    /** @hide */
+    private final long startTime;
+
+    /**
+     * Get the {@link Account} that is currently being synced.
+     * @return the account
+     */
+    public Account getAccount() {
+        return new Account(account.name, account.type);
+    }
+
+    /** @hide */
+    public int getAuthorityId() {
+        return authorityId;
+    }
+
+    /**
+     * Get the authority of the provider that is currently being synced.
+     * @return the authority
+     */
+    public String getAuthority() {
+        return authority;
+    }
+
+    /**
+     * Get the start time of the current sync operation. This is represented in elapsed real time.
+     * See {@link android.os.SystemClock#elapsedRealtime()}.
+     * @return the start time in milliseconds since boot
+     */
+    public long getStartTime() {
+        return startTime;
+    }
+
+    /** @hide */
     ActiveSyncInfo(int authorityId, Account account, String authority,
             long startTime) {
         this.authorityId = authorityId;
@@ -34,11 +71,13 @@
         this.authority = authority;
         this.startTime = startTime;
     }
-    
+
+    /** @hide */
     public int describeContents() {
         return 0;
     }
 
+    /** @hide */
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(authorityId);
         account.writeToParcel(parcel, 0);
@@ -46,13 +85,15 @@
         parcel.writeLong(startTime);
     }
 
+    /** @hide */
     ActiveSyncInfo(Parcel parcel) {
         authorityId = parcel.readInt();
         account = new Account(parcel);
         authority = parcel.readString();
         startTime = parcel.readLong();
     }
-    
+
+    /** @hide */
     public static final Creator<ActiveSyncInfo> CREATOR = new Creator<ActiveSyncInfo>() {
         public ActiveSyncInfo createFromParcel(Parcel in) {
             return new ActiveSyncInfo(in);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 29f388a..1b0ef34 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -156,11 +156,8 @@
     /** @hide */
     public static final int SYNC_ERROR_INTERNAL = 8;
 
-    /** @hide */
     public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
-    /** @hide */
     public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
-    /** @hide */
     public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
     /** @hide */
     public static final int SYNC_OBSERVER_TYPE_STATUS = 1<<3;
@@ -1183,7 +1180,6 @@
     /**
      * If a sync is active returns the information about it, otherwise returns false.
      * @return the ActiveSyncInfo for the currently active sync or null if one is not active.
-     * @hide
      */
     public static ActiveSyncInfo getActiveSync() {
         try {
@@ -1222,7 +1218,24 @@
         }
     }
 
+    /**
+     * Request notifications when the different aspects of the SyncManager change. The
+     * different items that can be requested are:
+     * <ul>
+     * <li> {@link #SYNC_OBSERVER_TYPE_PENDING}
+     * <li> {@link #SYNC_OBSERVER_TYPE_ACTIVE}
+     * <li> {@link #SYNC_OBSERVER_TYPE_SETTINGS}
+     * </ul>
+     * The caller can set one or more of the status types in the mask for any
+     * given listener registration.
+     * @param mask the status change types that will cause the callback to be invoked
+     * @param callback observer to be invoked when the status changes
+     * @return a handle that can be used to remove the listener at a later time
+     */
     public static Object addStatusChangeListener(int mask, final SyncStatusObserver callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("you passed in a null callback");
+        }
         try {
             ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
                 public void onStatusChanged(int which) throws RemoteException {
@@ -1236,7 +1249,14 @@
         }
     }
 
+    /**
+     * Remove a previously registered status change listener.
+     * @param handle the handle that was returned by {@link #addStatusChangeListener}
+     */
     public static void removeStatusChangeListener(Object handle) {
+        if (handle == null) {
+            throw new IllegalArgumentException("you passed in a null handle");
+        }
         try {
             getContentService().removeStatusChangeListener((ISyncStatusObserver.Stub) handle);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index e0dfab5..b5a78fa 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -437,7 +437,7 @@
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
+            if (syncManager != null && callback != null) {
                 syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
             }
         } finally {
@@ -449,7 +449,7 @@
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager != null) {
+            if (syncManager != null && callback != null) {
                 syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
             }
         } finally {
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 317e5a9..393bbba 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -982,8 +982,8 @@
         ActiveSyncInfo active = mSyncStorageEngine.getActiveSync();
         if (active != null) {
             SyncStorageEngine.AuthorityInfo authority
-                    = mSyncStorageEngine.getAuthority(active.authorityId);
-            final long durationInSeconds = (now - active.startTime) / 1000;
+                    = mSyncStorageEngine.getAuthority(active.getAuthorityId());
+            final long durationInSeconds = (now - active.getStartTime()) / 1000;
             pw.print("Active sync: ");
                     pw.print(authority != null ? authority.account : "<no account>");
                     pw.print(" ");
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 5aad3af..240da72 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -664,7 +664,7 @@
             }
 
             if (mActiveSync != null) {
-                AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
+                AuthorityInfo ainfo = getAuthority(mActiveSync.getAuthorityId());
                 if (ainfo != null && ainfo.account.equals(account)
                         && ainfo.authority.equals(authority)) {
                     return true;