| /* |
| * Copyright (C) 2019 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.car.setupwizardlib.util; |
| |
| import android.content.Context; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** A registry of singleton-like helpers, which can be injected by the application for testing. */ |
| public class CarHelperRegistry { |
| |
| /** |
| * Interface that creates the helper. Typically a constructor that takes a context. |
| * |
| * @param <H> The helper class. |
| */ |
| public interface HelperCreator<H> { |
| |
| /** Returns an instance of the helper class. */ |
| @NonNull |
| H createHelper(@NonNull Context appContext); |
| } |
| |
| private static final CarHelperRegistry GLOBAL_REGISTRY = new CarHelperRegistry(); |
| |
| private final ConcurrentHashMap<Class<?>, Object> mMap = new ConcurrentHashMap<>(); |
| |
| /** |
| * Query the application context for an injected registry, or return the global registry if one |
| * doesn't exist. Normal runs would not have an injected registry and therefore use the global |
| * one, while test runs will typically inject a registry so it can inject individual helpers for |
| * testing. |
| * |
| * @param context The context to get the registry from. |
| * @return The registry injected by the application context if one exists, or the global |
| * registry. |
| */ |
| @NonNull |
| @VisibleForTesting |
| static synchronized CarHelperRegistry getRegistry(@NonNull Context context) { |
| final Context applicationContext = context.getApplicationContext(); |
| if (applicationContext instanceof CarHelperInjectionContext) { |
| return ((CarHelperInjectionContext) applicationContext).getCarHelperRegistry(); |
| } |
| return GLOBAL_REGISTRY; |
| } |
| |
| /** |
| * Get the helper from the registry if it exists, or create and put one into the registry if it |
| * does not exist. |
| * |
| * <p>Since helpers are singleton-like, the context passed to the creator is always the |
| * application context so it doesn't leak local contexts pass their lifecycles. |
| * |
| * @param context The context to create the helper with. This can be any context as this method |
| * will call {@link Context#getApplicationContext()} to ensure the application context is |
| * used. |
| * @param cls The helper class. |
| * @param creator The method to create the helper. Typically if the helper has a constructor |
| * that takes a context, this creator will be {@code MyHelper::new}. |
| * @param <H> The helper class. |
| * @return The helper in the registry, either existing or newly registered. |
| */ |
| @NonNull |
| public static <H> H getOrCreateWithAppContext( |
| @NonNull Context context, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) { |
| return CarHelperRegistry.getRegistry(context) |
| .getOrCreateHelper(context.getApplicationContext(), cls, creator); |
| } |
| |
| /** |
| * Retrieve a helper from the registry. |
| * |
| * @param cls The helper class. |
| * @param <H> The helper class. |
| * @return The helper of the given {@code cls}, or null if no helper of {@code cls} is |
| * registered. |
| */ |
| @Nullable |
| @SuppressWarnings("unchecked") |
| @VisibleForTesting |
| <H> H getHelper(@NonNull Class<H> cls) { |
| return (H) mMap.get(cls); |
| } |
| |
| /** @see #getOrCreateWithAppContext(Context, Class, HelperCreator) */ |
| @NonNull |
| @VisibleForTesting |
| <H> H getOrCreateHelper( |
| @NonNull Context appContext, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) { |
| // Synchronize on the class to ensure only creator is only called once (per classloader) |
| synchronized (cls) { |
| H helper = getHelper(cls); |
| if (helper == null) { |
| helper = creator.createHelper(appContext); |
| putHelper(cls, helper); |
| } |
| return helper; |
| } |
| } |
| |
| @VisibleForTesting |
| int size() { |
| return mMap.size(); |
| } |
| |
| /** |
| * Put a helper for the given {@code cls} into the registry. Outside of this class, this should |
| * only be used to inject helper for testing purposes. Singletons should use {@link |
| * #getOrCreateWithAppContext(Context, Class, HelperCreator)} to create the instance which will |
| * |
| * @param cls The helper class. |
| * @param helper The helper instance to add to the registry. |
| * @param <H> The helper class. |
| */ |
| public <H> void putHelper(@NonNull Class<H> cls, @NonNull H helper) { |
| mMap.put(cls, helper); |
| } |
| } |