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);
     }
     */