Merge "Remove "mockito-robolectric-prebuilt"."
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index d0d7c94..0cb3c75 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -42,7 +42,6 @@
if (DBG) {
Log.d(TAG, "Publishing NearbyService");
}
-
}
@Override
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
new file mode 100644
index 0000000..40d77df
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
@@ -0,0 +1,288 @@
+/*
+ * 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.server.nearby.common.locator;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Collection of bindings that map service types to their respective implementation(s). */
+public class Locator {
+ private static final Object UNBOUND = new Object();
+ private final Context mContext;
+ @Nullable
+ private Locator mParent;
+ private final String mTag; // For debugging
+ private final Map<Class<?>, Object> mBindings = new HashMap<>();
+ private final ArrayList<Module> mModules = new ArrayList<>();
+
+ /** Thrown upon attempt to bind an interface twice. */
+ public static class DuplicateBindingException extends RuntimeException {
+ DuplicateBindingException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Constructor with a null parent. */
+ public Locator(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor. Supply a valid context and the Locator's parent.
+ *
+ * <p>To find a suitable parent you may want to use findLocator.
+ */
+ public Locator(Context context, @Nullable Locator parent) {
+ this.mContext = context;
+ this.mParent = parent;
+ this.mTag = context.getClass().getName();
+ }
+
+ /** Attaches the parent to the locator. */
+ public void attachParent(Locator parent) {
+ this.mParent = parent;
+ }
+
+ /** Associates the specified type with the supplied instance. */
+ public <T extends Object> Locator bind(Class<T> type, T instance) {
+ bindKeyValue(type, instance);
+ return this;
+ }
+
+ /** For tests only. Disassociates the specified type from any instance. */
+ @VisibleForTesting
+ public <T extends Object> Locator overrideBindingForTest(Class<T> type, T instance) {
+ mBindings.remove(type);
+ return bind(type, instance);
+ }
+
+ /** For tests only. Force Locator to return null when try to get an instance. */
+ @VisibleForTesting
+ public <T> Locator removeBindingForTest(Class<T> type) {
+ Locator locator = this;
+ do {
+ locator.mBindings.put(type, UNBOUND);
+ locator = locator.mParent;
+ } while (locator != null);
+ return this;
+ }
+
+ /** Binds a module. */
+ public synchronized Locator bind(Module module) {
+ mModules.add(module);
+ return this;
+ }
+
+ /**
+ * Searches the chain of locators for a binding for the given type.
+ *
+ * @throws IllegalStateException if no binding is found.
+ */
+ public <T> T get(Class<T> type) {
+ T instance = getOptional(type);
+ if (instance != null) {
+ return instance;
+ }
+
+ String errorMessage = getUnboundErrorMessage(type);
+ throw new IllegalStateException(errorMessage);
+ }
+
+ private String getUnboundErrorMessage(Class<?> type) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Unbound type: ").append(type.getName()).append("\n").append(
+ "Searched locators:\n");
+ Locator locator = this;
+ while (true) {
+ sb.append(locator.mTag);
+ locator = locator.mParent;
+ if (locator == null) {
+ break;
+ }
+ sb.append(" ->\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Searches the chain of locators for a binding for the given type. Returns null if no locator
+ * was
+ * found.
+ */
+ @Nullable
+ public <T> T getOptional(Class<T> type) {
+ Locator locator = this;
+ do {
+
+ T instance = locator.getInstance(type);
+ if (instance != null) {
+ return instance;
+ }
+ locator = locator.mParent;
+ } while (locator != null);
+ return null;
+ }
+
+ private synchronized <T extends Object> void bindKeyValue(Class<T> key, T value) {
+ Object boundInstance = mBindings.get(key);
+ if (boundInstance != null) {
+ if (boundInstance == UNBOUND) {
+ Log.w(mTag, "Bind call too late - someone already tried to get: " + key);
+ } else {
+ throw new DuplicateBindingException("Duplicate binding: " + key);
+ }
+ }
+ mBindings.put(key, value);
+ }
+
+ // Suppress warning of cast from Object -> T
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private synchronized <T> T getInstance(Class<T> type) {
+ if (mContext == null) {
+ throw new IllegalStateException("Locator not initialized yet.");
+ }
+
+ T instance = (T) mBindings.get(type);
+ if (instance != null) {
+ return instance != UNBOUND ? instance : null;
+ }
+
+ // Ask modules to supply a binding
+ int moduleCount = mModules.size();
+ for (int i = 0; i < moduleCount; i++) {
+ mModules.get(i).configure(mContext, type, this);
+ }
+
+ instance = (T) mBindings.get(type);
+ if (instance == null) {
+ mBindings.put(type, UNBOUND);
+ }
+ return instance;
+ }
+
+ /**
+ * Iterates over all bound objects and gives the modules a chance to clean up the objects they
+ * have created.
+ */
+ public synchronized void destroy() {
+ for (Class<?> type : mBindings.keySet()) {
+ Object instance = mBindings.get(type);
+ if (instance == UNBOUND) {
+ continue;
+ }
+
+ for (Module module : mModules) {
+ module.destroy(mContext, type, instance);
+ }
+ }
+ mBindings.clear();
+ }
+
+ /** Returns true if there are no bindings. */
+ public boolean isEmpty() {
+ return mBindings.isEmpty();
+ }
+
+ /** Returns the parent locator or null if no parent. */
+ @Nullable
+ public Locator getParent() {
+ return mParent;
+ }
+
+ /**
+ * Finds the first locator, then searches the chain of locators for a binding for the given
+ * type.
+ *
+ * @throws IllegalStateException if no binding is found.
+ */
+ public static <T> T get(Context context, Class<T> type) {
+ Locator locator = findLocator(context);
+ if (locator == null) {
+ throw new IllegalStateException("No locator found in context " + context);
+ }
+ return locator.get(type);
+ }
+
+ /**
+ * Finds the first locator, then searches the chain of locators for a binding for the given
+ * type.
+ * Returns null if no binding was found.
+ */
+ @Nullable
+ public static <T> T getOptional(Context context, Class<T> type) {
+ Locator locator = findLocator(context);
+ if (locator == null) {
+ return null;
+ }
+ return locator.getOptional(type);
+ }
+
+ /** Finds the first locator in the context hierarchy. */
+ @Nullable
+ public static Locator findLocator(Context context) {
+ Context applicationContext = context.getApplicationContext();
+ boolean applicationContextVisited = false;
+
+ Context searchContext = context;
+ do {
+ Locator locator = tryGetLocator(searchContext);
+ if (locator != null) {
+ return locator;
+ }
+
+ applicationContextVisited |= (searchContext == applicationContext);
+
+ if (searchContext instanceof ContextWrapper) {
+ searchContext = ((ContextWrapper) context).getBaseContext();
+
+ if (searchContext == null) {
+ throw new IllegalStateException(
+ "Invalid ContextWrapper -- If this is a Robolectric test, "
+ + "have you called ActivityController.create()?");
+ }
+ } else if (!applicationContextVisited) {
+ searchContext = applicationContext;
+ } else {
+ searchContext = null;
+ }
+ } while (searchContext != null);
+
+ return null;
+ }
+
+ @Nullable
+ private static Locator tryGetLocator(Object object) {
+ if (object instanceof LocatorContext) {
+ Locator locator = ((LocatorContext) object).getLocator();
+ if (locator == null) {
+ throw new IllegalStateException(
+ "LocatorContext must not return null Locator: " + object);
+ }
+ return locator;
+ }
+ return null;
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java
new file mode 100644
index 0000000..06eef8a
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java
@@ -0,0 +1,26 @@
+/*
+ * 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.server.nearby.common.locator;
+
+/**
+ * An object that has a {@link Locator}. The locator can be used to resolve service types to their
+ * respective implementation(s).
+ */
+public interface LocatorContext {
+ /** Returns the locator. May not return null. */
+ Locator getLocator();
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java
new file mode 100644
index 0000000..9dddcdb
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.nearby.common.locator;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.ContextWrapper;
+
+/**
+ * Wraps a Context and associates it with a Locator, optionally linking it with a parent locator.
+ */
+public class LocatorContextWrapper extends ContextWrapper implements LocatorContext {
+ private final Locator mLocator;
+ /** Constructs a context wrapper with a Locator linked to the passed locator. */
+ public LocatorContextWrapper(Context context, @Nullable Locator parentLocator) {
+ super(context);
+ // Assigning under initialization object, but it's safe, since locator is used lazily.
+ this.mLocator = new Locator(this, parentLocator);
+ }
+
+ /**
+ * Constructs a context wrapper.
+ *
+ * <p>Uses the Locator associated with the passed context as the parent.
+ */
+ public LocatorContextWrapper(Context context) {
+ this(context, Locator.findLocator(context));
+ }
+
+ @Override
+ public Locator getLocator() {
+ return mLocator;
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Module.java b/nearby/service/java/com/android/server/nearby/common/locator/Module.java
new file mode 100644
index 0000000..0131c44
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/locator/Module.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.nearby.common.locator;
+
+import android.content.Context;
+
+/** Configures late bindings of service types to their concrete implementations. */
+public abstract class Module {
+ /**
+ * Configures the binding between the {@code type} and its implementation by calling methods on
+ * the {@code locator}, for example:
+ *
+ * <pre>{@code
+ * void configure(Context context, Class<?> type, Locator locator) {
+ * if (type == MyService.class) {
+ * locator.bind(MyService.class, new MyImplementation(context));
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>If the module does not recognize the specified type, the method does not have to do
+ * anything.
+ */
+ public abstract void configure(Context context, Class<?> type, Locator locator);
+
+ /**
+ * Notifies you that a binding of class {@code type} is no longer needed and can now release
+ * everything it was holding on to, such as a database connection.
+ *
+ * <pre>{@code
+ * void destroy(Context context, Class<?> type, Object instance) {
+ * if (type == MyService.class) {
+ * ((MyService) instance).destroy();
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>If the module does not recognize the specified type, the method does not have to do
+ * anything.
+ */
+ public void destroy(Context context, Class<?> type, Object instance) {}
+}
+
diff --git a/nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
index ceaf78c..1af55b5 100644
--- a/nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
+++ b/nearby/tests/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
@@ -43,7 +43,7 @@
mEventLoop.removeRunnable(runnableToAddAndRemove);
mEventLoop.postRunnable(new NumberedRunnable(2));
- assertThat(mExecutedRunnables).containsExactly(0, 1);
+ assertThat(mExecutedRunnables).containsExactly(0, 2);
}
*/