Add LTZP status APIs to the SDK

Give TimeZoneProviderService implementations the ability to supply
(optional) status information. This status information can be used for
two purposes:
1) To help report status to users in the SettingsUI (once plumbed
   through).
2) To influence higher-level time zone detection behavior, i.e. so that
   the time zone detection can understand that a TZP is unable to
   perform its duties, perhaps something the platform cannot detect for
   itself.

Test: atest core/tests/coretests/src/android/service/timezone/
Test: atest services/tests/servicestests/src/com/android/server/timezonedetector/location/
Bug: 236624675
Change-Id: Ifa66b8c968c0bdf31b6bdf55ead34b3fa1d9e6fa
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fdb5e07..25e1402 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11889,11 +11889,39 @@
     method public abstract void onStopUpdates();
     method public final void reportPermanentFailure(@NonNull Throwable);
     method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+    method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
     method public final void reportUncertain();
+    method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
     field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
     field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
   }
 
+  public final class TimeZoneProviderStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectivityDependencyStatus();
+    method public int getLocationDetectionDependencyStatus();
+    method public int getTimeZoneResolutionOperationStatus();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+    field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+    field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+    field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+    field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+    field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+    field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+    field public static final int OPERATION_STATUS_OK = 2; // 0x2
+  }
+
+  public static final class TimeZoneProviderStatus.Builder {
+    ctor public TimeZoneProviderStatus.Builder();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+    method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+  }
+
   public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
     method public int describeContents();
     method public long getElapsedRealtimeMillis();
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index 2cea95a..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
  *
  * <p>Once started, providers are expected to detect the time zone if possible, and report the
  * result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
  * method for details.
  *
  * <p>After starting, providers are expected to issue their first callback within the timeout
@@ -213,8 +213,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
             @NonNull TimeZoneProviderStatus providerStatus) {
@@ -248,8 +246,9 @@
 
     /**
      * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
+     * the provider is unable to detect location, or there was connectivity issue.
+     *
+     * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
      */
     public final void reportUncertain() {
         TimeZoneProviderStatus providerStatus = null;
@@ -264,8 +263,6 @@
      *
      * @param providerStatus provider status information that can influence detector service
      *   behavior and/or be reported via the device UI
-     *
-     * @hide
      */
     public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
         Objects.requireNonNull(providerStatus);
@@ -362,8 +359,8 @@
      * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
      * system server holds the latest report from the provider in memory. After an initial report,
      * provider implementations are only required to send a report via {@link
-     * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
-     * differs from the previous report.
+     * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+     * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
      *
      * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
      * in rare cases, after which the provider should consider itself stopped and not make any
@@ -375,7 +372,8 @@
      * Android system server may move on to use other providers or detection methods. Providers
      * should therefore make best efforts during this time to generate a report, which could involve
      * increased power usage. Providers should preferably report an explicit {@link
-     * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+     * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+     * initialization timeout.
      *
      * @see #onStopUpdates() for the signal from the system server to stop sending reports
      */
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 513068f..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -65,6 +66,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class TimeZoneProviderStatus implements Parcelable {
 
     /**
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index 03e378f..9006cd9 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,20 +16,15 @@
 
 package android.service.timezone;
 
-import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
 
 import org.junit.Test;
 
+/** Non-SDK tests. See CTS for SDK API tests. */
 public class TimeZoneProviderStatusTest {
 
     @Test
@@ -42,81 +37,4 @@
 
         assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
     }
-
-    @Test
-    public void testStatusValidation() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setLocationDetectionDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setConnectivityDependencyStatus(-1)
-                        .build());
-        assertThrows(IllegalArgumentException.class,
-                () -> new TimeZoneProviderStatus.Builder(status)
-                        .setTimeZoneResolutionOperationStatus(-1)
-                        .build());
-    }
-
-    @Test
-    public void testEqualsAndHashcode() {
-        TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
-                .build();
-        assertEqualsAndHashcode(status1_1, status1_1);
-        assertNotEquals(status1_1, null);
-
-        {
-            TimeZoneProviderStatus status1_2 =
-                    new TimeZoneProviderStatus.Builder(status1_1).build();
-            assertEqualsAndHashcode(status1_1, status1_2);
-            assertNotSame(status1_1, status1_2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-
-        {
-            TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                    .build();
-            assertNotEquals(status1_1, status2);
-        }
-    }
-
-    private static void assertEqualsAndHashcode(Object one, Object two) {
-        assertEquals(one, two);
-        assertEquals(two, one);
-        assertEquals(one.hashCode(), two.hashCode());
-    }
-
-    @Test
-    public void testParcelable() {
-        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
-                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
-                .build();
-        assertRoundTripParcelable(status);
-    }
 }