Merge "Redact privacy sensitive info from logcat" into sc-dev
diff --git a/common/Android.bp b/common/Android.bp
index 08a7ee1..dbb149b 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -22,3 +22,32 @@
name: "geotz_host_common",
srcs: ["host/main/java/**/*.java"],
}
+
+// Code intended for device or host usage.
+java_library {
+ name: "geotz_common",
+ host_supported: true,
+ srcs: ["src/main/java/**/*.java"],
+ libs: ["androidx.annotation_annotation"],
+ sdk_version: "current",
+ min_sdk_version: "current",
+ apex_available: [
+ "com.android.geotz",
+ ],
+}
+
+// Device side tests for the geotz_common code.
+android_test {
+ name: "GeotzCommonTests",
+ srcs: ["src/test/java/**/*.java"],
+ manifest: "src/test/AndroidManifest.xml",
+ sdk_version: "current",
+ min_sdk_version: "current",
+ static_libs: [
+ "androidx.test.runner",
+ "geotz_lookup",
+ "junit",
+ "truth-prebuilt",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/common/src/TEST_MAPPING b/common/src/TEST_MAPPING
new file mode 100644
index 0000000..ef838db
--- /dev/null
+++ b/common/src/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "GeotzCommonTests"
+ }
+ ]
+}
diff --git a/common/src/main/java/com/android/timezone/location/common/PiiLoggable.java b/common/src/main/java/com/android/timezone/location/common/PiiLoggable.java
new file mode 100644
index 0000000..55d2cce
--- /dev/null
+++ b/common/src/main/java/com/android/timezone/location/common/PiiLoggable.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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 com.android.timezone.location.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An interface for objects that could contain user or other sensitive information. Implementers
+ * must ensure that {@link #toString()} can be used for the default / redacted / log-safe form, and
+ * {@link #toPiiString()} is provided for the form that can contains sensitive information.
+ *
+ * <p>The purpose of this interface is to ensure Java's default {@link #toString()} behavior is safe
+ * to use by default in logs, exception messages, etc. See {@link PiiLoggables} for support methods.
+ */
+public interface PiiLoggable {
+
+ /** Used for logs, should be implemented to be log-safe, not containing PII. */
+ @Override
+ @NonNull String toString();
+
+ /** Like {@link #toString()} but can include PII for debugging. */
+ @NonNull String toPiiString();
+
+ /**
+ * Returns a string representation of {@code value}. Operates like {@link
+ * String#valueOf(Object)}} but calls {@link PiiLoggable#toPiiString()} instead of
+ * {@link Object#toString()}.
+ */
+ static String toPiiString(@Nullable PiiLoggable value) {
+ return value == null ? PiiLoggables.NULL_STRING : value.toPiiString();
+ }
+}
diff --git a/common/src/main/java/com/android/timezone/location/common/PiiLoggables.java b/common/src/main/java/com/android/timezone/location/common/PiiLoggables.java
new file mode 100644
index 0000000..24699a3
--- /dev/null
+++ b/common/src/main/java/com/android/timezone/location/common/PiiLoggables.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 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 com.android.timezone.location.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Function;
+
+/** Utility methods to support {@link PiiLoggable}. */
+public final class PiiLoggables {
+
+ static final String NULL_STRING = "null";
+ static final String REDACTED_STRING = "<redacted>";
+
+ private PiiLoggables() {}
+
+ /**
+ * Returns a null-safe function that calls {@link PiiLoggable#toPiiString(PiiLoggable)} when the
+ * function's argument is not {@code null}.
+ */
+ @NonNull
+ public static <V extends PiiLoggable> Function<V, String> toPiiStringFunction() {
+ return x -> PiiLoggable.toPiiString(x);
+ }
+
+ /**
+ * An adapter / wrapper for objects that output PII as part of their normal {@link #toString()}.
+ * The referenced value can be {@code null}.
+ *
+ * <p>The implementation contract:
+ * <ul>
+ * <li>{@link #toPiiString()} must print {@code "null"} if the value is null, and may delegate
+ * to the value's {@link #toString()}.</li>
+ * <li>{@link #toString()} must print {@code "null"} if the value is null, or a PII safe string,
+ * e.g. "<redacted>", or just non-PII information.
+ * <li>Implementations may implement {@link #equals(Object)} and {@link #hashCode()}.</li>
+ * </ul>
+ *
+ * <p>See {@link #fromPiiValue(Object)} for a method that returns objects that implement a
+ * minimal contract.
+ */
+ public interface PiiLoggableValue<V> extends PiiLoggable {
+ /** Returns the held value. */
+ @Nullable V get();
+ }
+
+ /**
+ * Wraps a nullable reference in a {@link PiiLoggableValue}. {@link Object#toString()}
+ * returns the string {@code "<redacted>"}. {@link Object#equals(Object)} and {@link
+ * Object#hashCode()} delegate to {@code value} when not {@code null}.
+ */
+ @NonNull
+ public static <V> PiiLoggableValue<V> fromPiiValue(@Nullable V value) {
+ class PiiLoggableValueImpl<V> implements PiiLoggableValue<V> {
+
+ private final V mValue;
+
+ PiiLoggableValueImpl(V value) {
+ mValue = value;
+ }
+
+ @Override
+ public V get() {
+ return mValue;
+ }
+
+ @Override
+ public String toPiiString() {
+ return String.valueOf(mValue);
+ }
+
+ @Override
+ public String toString() {
+ return mValue == null ? NULL_STRING : REDACTED_STRING;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PiiLoggableValueImpl<?> that = (PiiLoggableValueImpl<?>) o;
+ return Objects.equals(mValue, that.mValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mValue);
+ }
+ }
+ return new PiiLoggableValueImpl<V>(value);
+ }
+
+ /**
+ * A convenience method for creating a {@link PiiLoggable} from a {@link String} that doesn't
+ * contain PII. Both {@link PiiLoggable#toString()} and {@link PiiLoggable#toPiiString()} will
+ * return {@code "null"} if the string is {@code null}, or the result of calling {@link
+ * Object#toString()} if the string is not {@code null}.
+ */
+ @NonNull
+ public static PiiLoggable fromString(@Nullable String s) {
+ class PiiLoggableString implements PiiLoggable {
+
+ private final String mString;
+
+ public PiiLoggableString(String string) {
+ mString = string;
+ }
+
+ @Override
+ public String toPiiString() {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mString);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PiiLoggableString that = (PiiLoggableString) o;
+ return Objects.equals(mString, that.mString);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mString);
+ }
+ }
+ return new PiiLoggableString(s);
+ }
+
+ /**
+ * Creates a {@link PiiLoggable} that generates strings using the supplied template. The
+ * resulting object uses {@link String#format(String, Object...)} for {@link
+ * PiiLoggable#toString()} and {@link PiiLoggables#formatPiiString(String, PiiLoggable...)} for
+ * {@link PiiLoggable#toPiiString()}.
+ */
+ @NonNull
+ public static PiiLoggable fromTemplate(@NonNull String template, PiiLoggable... piiLoggables) {
+ class TemplatedPiiLoggable implements PiiLoggable {
+ @NonNull private final String mTemplate;
+ @NonNull private final PiiLoggable[] mLoggables;
+
+ public TemplatedPiiLoggable(String template, PiiLoggable... loggables) {
+ mTemplate = Objects.requireNonNull(template);
+ mLoggables = Objects.requireNonNull(loggables);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(mTemplate, (Object[]) mLoggables);
+ }
+
+ @Override
+ public String toPiiString() {
+ return PiiLoggables.formatPiiString(mTemplate, mLoggables);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TemplatedPiiLoggable that = (TemplatedPiiLoggable) o;
+ return mTemplate.equals(that.mTemplate) &&
+ Arrays.equals(mLoggables, that.mLoggables);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(mTemplate);
+ result = 31 * result + Arrays.hashCode(mLoggables);
+ return result;
+ }
+ }
+
+ return new TemplatedPiiLoggable(template, piiLoggables);
+ }
+
+ /**
+ * Formats a templated string. This method operates like {@link
+ * String#format(String, Object...)} except {@code %s} will be replaced with the result of
+ * {@link PiiLoggable#toPiiString(PiiLoggable)} instead of {@link String#valueOf(Object)}.
+ */
+ @NonNull
+ public static String formatPiiString(@NonNull String format, PiiLoggable... piiLoggables) {
+ String[] strings = new String[piiLoggables.length];
+ for (int i = 0; i < strings.length; i++) {
+ PiiLoggable piiLoggable = piiLoggables[i];
+ strings[i] = PiiLoggable.toPiiString(piiLoggable);
+ }
+ return String.format(format, (Object[]) strings);
+ }
+}
diff --git a/common/src/test/AndroidManifest.xml b/common/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..cc66797
--- /dev/null
+++ b/common/src/test/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2021 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.timezone.location.common">
+
+ <application android:allowBackup="false" android:debuggable="true" />
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.timezone.location.common" />
+</manifest>
\ No newline at end of file
diff --git a/common/src/test/java/com/android/timezone/location/common/PiiLoggableTest.java b/common/src/test/java/com/android/timezone/location/common/PiiLoggableTest.java
new file mode 100644
index 0000000..855a745
--- /dev/null
+++ b/common/src/test/java/com/android/timezone/location/common/PiiLoggableTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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 com.android.timezone.location.common;
+
+import static com.android.timezone.location.common.PiiLoggables.NULL_STRING;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.timezone.location.common.PiiLoggablesTest.TestPiiLoggable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link PiiLoggable}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PiiLoggableTest {
+
+ @Test
+ public void toPiiString() {
+ assertEquals(NULL_STRING, PiiLoggable.toPiiString(null));
+ assertEquals("PII", PiiLoggable.toPiiString(new TestPiiLoggable("no PII", "PII")));
+ }
+}
diff --git a/common/src/test/java/com/android/timezone/location/common/PiiLoggablesTest.java b/common/src/test/java/com/android/timezone/location/common/PiiLoggablesTest.java
new file mode 100644
index 0000000..59e7a82
--- /dev/null
+++ b/common/src/test/java/com/android/timezone/location/common/PiiLoggablesTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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 com.android.timezone.location.common;
+
+import static com.android.timezone.location.common.PiiLoggables.NULL_STRING;
+import static com.android.timezone.location.common.PiiLoggables.REDACTED_STRING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.timezone.location.common.PiiLoggable;
+import com.android.timezone.location.common.PiiLoggables;
+import com.android.timezone.location.common.PiiLoggables.PiiLoggableValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Tests for {@link PiiLoggables}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PiiLoggablesTest {
+
+ @Test
+ public void fromString_null() {
+ PiiLoggable piiLoggable = PiiLoggables.fromString(null);
+ assertEquals(NULL_STRING, piiLoggable.toString());
+ assertEquals(NULL_STRING, piiLoggable.toPiiString());
+ assertEquals(piiLoggable, PiiLoggables.fromString(null));
+ assertNotEquals(piiLoggable, PiiLoggables.fromString("Hello"));
+ }
+
+ @Test
+ public void fromString() {
+ String string1 = "Hello";
+ String string2 = "Goodbye";
+ PiiLoggable piiLoggable = PiiLoggables.fromString(string1);
+ assertEquals(string1, piiLoggable.toString());
+ assertEquals(string1, piiLoggable.toPiiString());
+ assertEquals(piiLoggable, PiiLoggables.fromString(string1));
+ assertNotEquals(piiLoggable, PiiLoggables.fromString(string2));
+ }
+
+ @Test
+ public void fromPiiValue_null() {
+ PiiLoggableValue<String> piiLoggableValue = PiiLoggables.fromPiiValue(null);
+ assertEquals(NULL_STRING, piiLoggableValue.toString());
+ assertEquals(NULL_STRING, piiLoggableValue.toPiiString());
+ assertEquals(piiLoggableValue, PiiLoggables.fromPiiValue(null));
+ assertNotEquals(piiLoggableValue, PiiLoggables.fromPiiValue("Hello"));
+ assertNull(piiLoggableValue.get());
+ }
+
+ @Test
+ public void fromPiiValue() {
+ Object piiValue = "Classified Info";
+ Object differentPiiValue = "Different classified Info";
+ PiiLoggableValue<Object> piiLoggableValue = PiiLoggables.fromPiiValue(piiValue);
+ assertEquals(REDACTED_STRING, piiLoggableValue.toString());
+ assertEquals(piiValue.toString(), piiLoggableValue.toPiiString());
+ assertEquals(piiLoggableValue, PiiLoggables.fromPiiValue(piiValue));
+ assertNotEquals(piiLoggableValue, PiiLoggables.fromPiiValue(differentPiiValue));
+ assertEquals(piiValue, piiLoggableValue.get());
+ }
+
+ @Test
+ public void toPiiStringFunction() {
+ String piiValue = "pii";
+ PiiLoggable piiLoggable = new TestPiiLoggable("no pii", piiValue);
+ Function<PiiLoggable, String> function = PiiLoggables.toPiiStringFunction();
+ assertEquals(NULL_STRING, function.apply(null));
+ assertEquals(piiValue, function.apply(piiLoggable));
+ }
+
+ @Test
+ public void formatPiiString() {
+ PiiLoggable piiLoggable = new TestPiiLoggable("no pii", "pii");
+ String result = PiiLoggables.formatPiiString(
+ "1=%s,2=%s,3=%s", piiLoggable, null, piiLoggable);
+ assertEquals("1=pii,2=null,3=pii", result);
+
+ }
+
+ @Test
+ public void fromTemplate() {
+ PiiLoggable piiLoggable = new TestPiiLoggable("no pii", "pii");
+ PiiLoggable result =
+ PiiLoggables.fromTemplate("1=%s,2=%s,3=%s", piiLoggable, null, piiLoggable);
+ assertEquals("1=pii,2=null,3=pii", result.toPiiString());
+ assertEquals("1=no pii,2=null,3=no pii", result.toString());
+ }
+
+ @Test
+ public void fromTemplate_withPercentS() {
+ PiiLoggable piiLoggable = new TestPiiLoggable("no pii{%s}", "pii{%s}");
+ PiiLoggable result =
+ PiiLoggables.fromTemplate("1=%s,2=%s,3=%s", piiLoggable, null, piiLoggable);
+ assertEquals("1=pii{%s},2=null,3=pii{%s}", result.toPiiString());
+ assertEquals("1=no pii{%s},2=null,3=no pii{%s}", result.toString());
+ }
+
+ static class TestPiiLoggable implements PiiLoggable {
+
+ private final String mNoPii;
+ private final String mPii;
+
+ public TestPiiLoggable(String noPii, String pii) {
+ mNoPii = noPii;
+ mPii = pii;
+ }
+
+ @Override
+ public String toPiiString() {
+ return mPii;
+ }
+
+ @Override
+ public String toString() {
+ return mNoPii;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TestPiiLoggable that = (TestPiiLoggable) o;
+ return mNoPii.equals(that.mNoPii) &&
+ mPii.equals(that.mPii);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNoPii, mPii);
+ }
+ }
+}
diff --git a/geotz_lookup/Android.bp b/geotz_lookup/Android.bp
index 2e8e9c8..4b4c436 100644
--- a/geotz_lookup/Android.bp
+++ b/geotz_lookup/Android.bp
@@ -24,6 +24,7 @@
min_sdk_version: "current",
libs: ["androidx.annotation_annotation"],
static_libs: [
+ "geotz_common",
"geotz_s2storage_ro",
"s2-geometry-library-java",
],
diff --git a/geotz_lookup/src/main/java/com/android/timezone/location/lookup/GeoTimeZonesFinder.java b/geotz_lookup/src/main/java/com/android/timezone/location/lookup/GeoTimeZonesFinder.java
index caa7721..abfcf58 100644
--- a/geotz_lookup/src/main/java/com/android/timezone/location/lookup/GeoTimeZonesFinder.java
+++ b/geotz_lookup/src/main/java/com/android/timezone/location/lookup/GeoTimeZonesFinder.java
@@ -15,6 +15,8 @@
*/
package com.android.timezone.location.lookup;
+import com.android.timezone.location.common.PiiLoggable;
+
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -87,7 +89,7 @@
* <p>Depending on the implementation, it may be cheaper to obtain a {@link LocationToken} than
* doing a full lookup.
*/
- public abstract static class LocationToken {
+ public abstract static class LocationToken implements PiiLoggable {
@Override
public abstract boolean equals(Object other);
diff --git a/geotz_lookup/src/main/java/com/android/timezone/location/lookup/S2RangeFileBasedGeoTimeZonesFinder.java b/geotz_lookup/src/main/java/com/android/timezone/location/lookup/S2RangeFileBasedGeoTimeZonesFinder.java
index 089850a..681af52 100644
--- a/geotz_lookup/src/main/java/com/android/timezone/location/lookup/S2RangeFileBasedGeoTimeZonesFinder.java
+++ b/geotz_lookup/src/main/java/com/android/timezone/location/lookup/S2RangeFileBasedGeoTimeZonesFinder.java
@@ -114,6 +114,11 @@
@Override
public String toString() {
+ return "LocationToken{<redacted>}";
+ }
+
+ @Override
+ public String toPiiString() {
return "LocationToken{"
+ "mS2CellId=" + mS2CellId
+ '}';
diff --git a/locationtzprovider/Android.bp b/locationtzprovider/Android.bp
index a435ec7..63fa3d7 100644
--- a/locationtzprovider/Android.bp
+++ b/locationtzprovider/Android.bp
@@ -26,6 +26,7 @@
"androidx.annotation_annotation",
],
static_libs: [
+ "geotz_common",
"geotz_lookup",
],
apex_available: [
diff --git a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Environment.java b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Environment.java
index d1678f9..fe24651 100644
--- a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Environment.java
+++ b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Environment.java
@@ -26,8 +26,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.timezone.location.common.PiiLoggable;
+import com.android.timezone.location.common.PiiLoggables;
import com.android.timezone.location.lookup.GeoTimeZonesFinder;
import com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.ListenModeEnum;
+import com.android.timezone.location.common.PiiLoggables.PiiLoggableValue;
import java.io.IOException;
import java.time.Duration;
@@ -63,7 +66,7 @@
* <p>With active listening the {@link #getLocation() location} can be {@code null} (meaning
* "location unknown"), with passive listening it is never {@code null}.
*/
- final class LocationListeningResult {
+ final class LocationListeningResult implements PiiLoggable {
/**
* The type of listening that produced the result. This is recorded for logging / debugging.
*/
@@ -81,8 +84,8 @@
*/
private final long mResultElapsedRealtimeMillis;
- /** The location, or {@code null} if the location is not known (active only). */
- @Nullable private final Location mLocation;
+ /** Holds the location, or {@code null} if the location is not known (active only). */
+ @NonNull private final PiiLoggableValue<Location> mPiiLoggableLocation;
public LocationListeningResult(
@ListenModeEnum int listenMode,
@@ -94,7 +97,7 @@
mListeningDuration = Objects.requireNonNull(listeningDuration);
mStartElapsedRealtimeMillis = startElapsedRealtimeMillis;
mResultElapsedRealtimeMillis = resultElapsedRealtimeMillis;
- mLocation = location;
+ mPiiLoggableLocation = PiiLoggables.fromPiiValue(location);
}
/** Returns how long listening was requested for. */
@@ -105,13 +108,13 @@
/** Returns whether result of listening was a known location. */
public boolean isLocationKnown() {
- return mLocation != null;
+ return mPiiLoggableLocation.get() != null;
}
/** Returns the location. See {@link #isLocationKnown()}. */
@Nullable
public Location getLocation() {
- return mLocation;
+ return mPiiLoggableLocation.get();
}
/** Returns (an approximation) of when listening started. */
@@ -151,16 +154,28 @@
*/
@NonNull
public Duration getLocationAge(long elapsedRealtimeMillis) {
- if (mLocation == null) {
+ Location location = mPiiLoggableLocation.get();
+ if (location == null) {
throw new IllegalStateException();
}
long locationAgeMillis = elapsedRealtimeMillis
- - NANOSECONDS.toMillis(mLocation.getElapsedRealtimeNanos());
+ - NANOSECONDS.toMillis(location.getElapsedRealtimeNanos());
return Duration.ofMillis(locationAgeMillis);
}
@Override
+ public String toPiiString() {
+ String template = toStringTemplate();
+ return PiiLoggables.formatPiiString(template, mPiiLoggableLocation);
+ }
+
+ @Override
public String toString() {
+ String template = toStringTemplate();
+ return String.format(template, mPiiLoggableLocation);
+ }
+
+ private String toStringTemplate() {
return "LocationListeningResult{"
+ "mListenMode=" + prettyPrintListenModeEnum(mListenMode)
+ ", mListeningDuration=" + mListeningDuration
@@ -168,7 +183,7 @@
+ formatElapsedRealtimeMillis(mStartElapsedRealtimeMillis)
+ ", mResultElapsedRealtimeMillis="
+ formatElapsedRealtimeMillis(mResultElapsedRealtimeMillis)
- + ", mLocation=" + mLocation
+ + ", mPiiLoggableLocation=%s"
+ ", getTotalEstimatedTimeListening()=" + getTotalEstimatedTimeListening()
+ '}';
}
diff --git a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/LogUtils.java b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/LogUtils.java
index 3395a42..e330884 100644
--- a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/LogUtils.java
+++ b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/LogUtils.java
@@ -20,6 +20,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.timezone.location.common.PiiLoggable;
+
import java.time.Duration;
import java.time.Instant;
@@ -42,6 +44,13 @@
}
/** Logs at debug level when debug logging is enabled via {@link #DBG}. */
+ public static void logDebug(@NonNull PiiLoggable piiLoggable) {
+ if (DBG) {
+ Log.d(LOG_TAG, piiLoggable.toString());
+ }
+ }
+
+ /** Logs at debug level when debug logging is enabled via {@link #DBG}. */
public static void logDebug(@NonNull String msg) {
if (DBG) {
Log.d(LOG_TAG, msg);
@@ -49,11 +58,21 @@
}
/** Logs at warn level. */
+ public static void logWarn(@NonNull PiiLoggable piiLoggable) {
+ Log.w(LOG_TAG, piiLoggable.toString());
+ }
+
+ /** Logs at warn level. */
public static void logWarn(@NonNull String msg) {
Log.w(LOG_TAG, msg);
}
/** Logs at warn level. */
+ public static void logWarn(@NonNull PiiLoggable piiLoggable, @Nullable Throwable t) {
+ Log.w(LOG_TAG, piiLoggable.toString(), t);
+ }
+
+ /** Logs at warn level. */
public static void logWarn(@NonNull String msg, @Nullable Throwable t) {
Log.w(LOG_TAG, msg, t);
}
diff --git a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Mode.java b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Mode.java
index 3c37f8e..6d0eda4 100644
--- a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Mode.java
+++ b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/Mode.java
@@ -26,6 +26,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.timezone.location.common.PiiLoggable;
+import com.android.timezone.location.common.PiiLoggables;
import com.android.timezone.location.provider.core.OfflineLocationTimeZoneDelegate.ListenModeEnum;
import java.util.Objects;
@@ -62,7 +64,7 @@
* - when the provider's service is destroyed, perhaps as part of the current user changing
* </pre>
*/
-class Mode {
+class Mode implements PiiLoggable {
@IntDef({ MODE_STOPPED, MODE_STARTED, MODE_FAILED, MODE_DESTROYED })
@interface ModeEnum {}
@@ -111,7 +113,7 @@
* Debug information: Information about why the mode was entered.
*/
@NonNull
- private final String mEntryCause;
+ private final PiiLoggable mEntryCause;
/**
* Used when mModeEnum == {@link #MODE_STARTED}. The {@link Cancellable} that can be
@@ -120,15 +122,15 @@
@Nullable
private final Cancellable mLocationListenerCancellable;
- Mode(@ModeEnum int modeEnum, @NonNull String entryCause) {
+ Mode(@ModeEnum int modeEnum, @NonNull PiiLoggable entryCause) {
this(modeEnum, entryCause, LOCATION_LISTEN_MODE_NA, null);
}
- Mode(@ModeEnum int modeEnum, @NonNull String entryCause, @ListenModeEnum int listenMode,
+ Mode(@ModeEnum int modeEnum, @NonNull PiiLoggable entryCause, @ListenModeEnum int listenMode,
@Nullable Cancellable listeningCancellable) {
mModeEnum = validateModeEnum(modeEnum);
mListenMode = validateListenModeEnum(modeEnum, listenMode);
- mEntryCause = entryCause;
+ mEntryCause = Objects.requireNonNull(entryCause);
mLocationListenerCancellable = listeningCancellable;
// Information useful for logging / debugging.
@@ -138,7 +140,7 @@
/** Returns the stopped mode which is the starting state for a provider. */
@NonNull
static Mode createStoppedMode() {
- return new Mode(MODE_STOPPED, "init" /* entryCause */);
+ return new Mode(MODE_STOPPED, PiiLoggables.fromString("init") /* entryCause */);
}
/**
@@ -153,13 +155,24 @@
}
@Override
+ public String toPiiString() {
+ String template = toStringTemplate();
+ return PiiLoggables.formatPiiString(template, mEntryCause);
+ }
+
+ @Override
public String toString() {
+ String template = toStringTemplate();
+ return String.format(template, mEntryCause);
+ }
+
+ private String toStringTemplate() {
return "Mode{"
+ "mModeEnum=" + prettyPrintModeEnum(mModeEnum)
+ ", mListenMode=" + prettyPrintListenModeEnum(mListenMode)
+ ", mCreationElapsedRealtimeMillis="
+ formatElapsedRealtimeMillis(mCreationElapsedRealtimeMillis)
- + ", mEntryCause={" + mEntryCause + '}'
+ + ", mEntryCause={%s}"
+ ", mLocationListenerCancellable=" + mLocationListenerCancellable
+ '}';
}
diff --git a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegate.java b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegate.java
index 1bfb202..7d8a053 100644
--- a/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegate.java
+++ b/locationtzprovider/src/main/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegate.java
@@ -36,6 +36,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.timezone.location.common.PiiLoggable;
+import com.android.timezone.location.common.PiiLoggables;
import com.android.timezone.location.lookup.GeoTimeZonesFinder;
import com.android.timezone.location.lookup.GeoTimeZonesFinder.LocationToken;
import com.android.timezone.location.provider.core.Environment.LocationListeningResult;
@@ -117,7 +119,8 @@
/** The current mode of the provider. See {@link Mode} for details. */
@GuardedBy("mLock")
- private final ReferenceWithHistory<Mode> mCurrentMode = new ReferenceWithHistory<>(10);
+ private final ReferenceWithHistory<Mode> mCurrentMode =
+ new ReferenceWithHistory<>(10, PiiLoggables.toPiiStringFunction());
/**
* The last location listening result. Holds {@code null} if location listening hasn't started
@@ -126,7 +129,7 @@
*/
@GuardedBy("mLock")
private final ReferenceWithHistory<LocationListeningResult> mLastLocationListeningResult =
- new ReferenceWithHistory<>(10);
+ new ReferenceWithHistory<>(10, PiiLoggables.toPiiStringFunction());
/**
* A token associated with the last location time zone lookup. Used to avoid unnecessary time
@@ -171,7 +174,7 @@
}
public void onBind() {
- String entryCause = "onBind() called";
+ PiiLoggable entryCause = PiiLoggables.fromString("onBind() called");
logDebug(entryCause);
synchronized (mLock) {
@@ -188,7 +191,7 @@
}
public void onDestroy() {
- String entryCause = "onDestroy() called";
+ PiiLoggable entryCause = PiiLoggables.fromString("onDestroy() called");
logDebug(entryCause);
synchronized (mLock) {
@@ -206,8 +209,8 @@
public void onStartUpdates(@NonNull Duration initializationTimeout) {
Objects.requireNonNull(initializationTimeout);
- String debugInfo = "onStartUpdates(),"
- + " initializationTimeout=" + initializationTimeout;
+ PiiLoggable debugInfo = PiiLoggables.fromString("onStartUpdates(),"
+ + " initializationTimeout=" + initializationTimeout);
logDebug(debugInfo);
synchronized (mLock) {
@@ -234,7 +237,7 @@
}
public void onStopUpdates() {
- String debugInfo = "onStopUpdates()";
+ PiiLoggable debugInfo = PiiLoggables.fromString("onStopUpdates()");
logDebug(debugInfo);
synchronized (mLock) {
@@ -270,7 +273,9 @@
// State and constants.
pw.println("mInitializationTimeoutCancellable=" + mInitializationTimeoutCancellable);
pw.println("mLocationListeningAccountant=" + mLocationListeningAccountant);
- pw.println("mLastLocationToken=" + mLastLocationToken);
+ String locationTokenString =
+ mLastLocationToken == null ? "null" : mLastLocationToken.toPiiString();
+ pw.println("mLastLocationToken=" + locationTokenString);
pw.println();
pw.println("Mode history:");
mCurrentMode.dump(pw);
@@ -305,8 +310,8 @@
return;
}
- String debugInfo = "onActiveListeningResult()"
- + ", activeListeningResult=" + activeListeningResult;
+ PiiLoggable debugInfo = PiiLoggables.fromTemplate("onActiveListeningResult(),"
+ + " activeListeningResult=%s", activeListeningResult);
logDebug(debugInfo);
// Recover any active listening budget we didn't use.
@@ -366,19 +371,22 @@
cancelInitializationTimeout();
Mode currentMode = mCurrentMode.get();
- String debugInfo = "handleLocationKnown(), locationResult=" + locationResult
- + ", currentMode.mListenMode=" + prettyPrintListenModeEnum(currentMode.mListenMode);
+ PiiLoggable debugInfo = PiiLoggables.fromTemplate(
+ "handleLocationKnown(), locationResult=%s"
+ +", currentMode.mListenMode=" + prettyPrintListenModeEnum(currentMode.mListenMode),
+ locationResult);
logDebug(debugInfo);
try {
sendTimeZoneCertainResultIfNeeded(locationResult.getLocation());
} catch (IOException e) {
// This should never happen.
- String lookupFailureDebugInfo = "IOException while looking up location."
- + " previous debugInfo=" + debugInfo;
+ PiiLoggable lookupFailureDebugInfo = PiiLoggables.fromTemplate(
+ "IOException while looking up location. previous debugInfo=%s", debugInfo);
logWarn(lookupFailureDebugInfo, e);
- enterFailedMode(new IOException(lookupFailureDebugInfo, e));
+ enterFailedMode(new IOException(lookupFailureDebugInfo.toString(), e),
+ lookupFailureDebugInfo);
}
}
@@ -409,7 +417,8 @@
* @param duration the duration that listening took place for
*/
private void onPassiveListeningEnded(@NonNull Duration duration) {
- String debugInfo = "onPassiveListeningEnded()";
+ PiiLoggable debugInfo = PiiLoggables.fromString(
+ "onPassiveListeningEnded(), duration=" + duration);
logDebug(debugInfo);
synchronized (mLock) {
@@ -473,11 +482,11 @@
// If the location token is the same as the last lookup, there is no need to do the
// lookup / send another suggestion.
if (locationToken.equals(mLastLocationToken)) {
- logDebug("Location token=" + locationToken + " has not changed.");
+ logDebug("Location token has not changed.");
} else {
List<String> tzIds =
geoTimeZonesFinder.findTimeZonesForLocationToken(locationToken);
- logDebug("tzIds found for location=" + location + ", tzIds=" + tzIds);
+ logDebug("tzIds found for locationToken=" + locationToken + ", tzIds=" + tzIds);
// Rather than use the current elapsed realtime clock, use the time associated with
// the location since that gives a more accurate answer.
long elapsedRealtimeMillis =
@@ -564,9 +573,9 @@
@GuardedBy("mLock")
private void enterStartedMode(
- @NonNull Duration initializationTimeout, @NonNull String debugInfo) {
+ @NonNull Duration initializationTimeout, @NonNull PiiLoggable entryCause) {
Objects.requireNonNull(initializationTimeout);
- Objects.requireNonNull(debugInfo);
+ Objects.requireNonNull(entryCause);
// The request contains the initialization time in which the LTZP is given to provide the
// first result. We set a timeout to try to ensure that we do send a result.
@@ -576,24 +585,23 @@
this::onInitializationTimeout, initializationToken,
initializationTimeout);
- startNextLocationListening(debugInfo);
+ startNextLocationListening(entryCause);
}
@GuardedBy("mLock")
- private void enterFailedMode(@NonNull Throwable entryCause) {
- logDebug("Provider entering failed mode, entryCause=" + entryCause);
+ private void enterFailedMode(@NonNull Throwable failure, @NonNull PiiLoggable entryCause) {
+ logDebug(entryCause);
cancelTimeoutsAndLocationCallbacks();
- sendPermanentFailureResult(entryCause);
+ sendPermanentFailureResult(failure);
- String failureReason = entryCause.getMessage();
- Mode newMode = new Mode(MODE_FAILED, failureReason);
+ Mode newMode = new Mode(MODE_FAILED, entryCause);
mCurrentMode.set(newMode);
}
@GuardedBy("mLock")
- private void enterStoppedMode(@NonNull String entryCause) {
+ private void enterStoppedMode(@NonNull PiiLoggable entryCause) {
logDebug("Provider entering stopped mode, entryCause=" + entryCause);
cancelTimeoutsAndLocationCallbacks();
@@ -607,7 +615,7 @@
}
@GuardedBy("mLock")
- private void startNextLocationListening(@NonNull String entryCause) {
+ private void startNextLocationListening(@NonNull PiiLoggable entryCause) {
logDebug("Provider entering location listening mode entryCause=" + entryCause);
Mode currentMode = mCurrentMode.get();
diff --git a/locationtzprovider/src/test/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegateTest.java b/locationtzprovider/src/test/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegateTest.java
index 9b13d13..25aff0b 100644
--- a/locationtzprovider/src/test/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegateTest.java
+++ b/locationtzprovider/src/test/java/com/android/timezone/location/provider/core/OfflineLocationTimeZoneDelegateTest.java
@@ -494,6 +494,12 @@
@Override
public String toString() {
+ // Using the debug string is ok for test code.
+ return toPiiString();
+ }
+
+ @Override
+ public String toPiiString() {
return "FakeLocationToken{"
+ "mLngDegrees=" + mLngDegrees
+ ", mLatDegrees=" + mLatDegrees