1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.setupwizardlib.util; 18 19 import android.content.Context; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.annotation.VisibleForTesting; 24 25 import java.util.concurrent.ConcurrentHashMap; 26 27 /** A registry of singleton-like helpers, which can be injected by the application for testing. */ 28 public class CarHelperRegistry { 29 30 /** 31 * Interface that creates the helper. Typically a constructor that takes a context. 32 * 33 * @param <H> The helper class. 34 */ 35 public interface HelperCreator<H> { 36 37 /** Returns an instance of the helper class. */ 38 @NonNull createHelper(@onNull Context appContext)39 H createHelper(@NonNull Context appContext); 40 } 41 42 private static final CarHelperRegistry GLOBAL_REGISTRY = new CarHelperRegistry(); 43 44 private final ConcurrentHashMap<Class<?>, Object> mMap = new ConcurrentHashMap<>(); 45 46 /** 47 * Query the application context for an injected registry, or return the global registry if one 48 * doesn't exist. Normal runs would not have an injected registry and therefore use the global 49 * one, while test runs will typically inject a registry so it can inject individual helpers for 50 * testing. 51 * 52 * @param context The context to get the registry from. 53 * @return The registry injected by the application context if one exists, or the global 54 * registry. 55 */ 56 @NonNull 57 @VisibleForTesting getRegistry(@onNull Context context)58 static synchronized CarHelperRegistry getRegistry(@NonNull Context context) { 59 final Context applicationContext = context.getApplicationContext(); 60 if (applicationContext instanceof CarHelperInjectionContext) { 61 return ((CarHelperInjectionContext) applicationContext).getCarHelperRegistry(); 62 } 63 return GLOBAL_REGISTRY; 64 } 65 66 /** 67 * Get the helper from the registry if it exists, or create and put one into the registry if it 68 * does not exist. 69 * 70 * <p>Since helpers are singleton-like, the context passed to the creator is always the 71 * application context so it doesn't leak local contexts pass their lifecycles. 72 * 73 * @param context The context to create the helper with. This can be any context as this method 74 * will call {@link Context#getApplicationContext()} to ensure the application context is 75 * used. 76 * @param cls The helper class. 77 * @param creator The method to create the helper. Typically if the helper has a constructor 78 * that takes a context, this creator will be {@code MyHelper::new}. 79 * @param <H> The helper class. 80 * @return The helper in the registry, either existing or newly registered. 81 */ 82 @NonNull getOrCreateWithAppContext( @onNull Context context, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator)83 public static <H> H getOrCreateWithAppContext( 84 @NonNull Context context, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) { 85 return CarHelperRegistry.getRegistry(context) 86 .getOrCreateHelper(context.getApplicationContext(), cls, creator); 87 } 88 89 /** 90 * Retrieve a helper from the registry. 91 * 92 * @param cls The helper class. 93 * @param <H> The helper class. 94 * @return The helper of the given {@code cls}, or null if no helper of {@code cls} is 95 * registered. 96 */ 97 @Nullable 98 @SuppressWarnings("unchecked") 99 @VisibleForTesting getHelper(@onNull Class<H> cls)100 <H> H getHelper(@NonNull Class<H> cls) { 101 return (H) mMap.get(cls); 102 } 103 104 /** @see #getOrCreateWithAppContext(Context, Class, HelperCreator) */ 105 @NonNull 106 @VisibleForTesting getOrCreateHelper( @onNull Context appContext, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator)107 <H> H getOrCreateHelper( 108 @NonNull Context appContext, @NonNull Class<H> cls, @NonNull HelperCreator<H> creator) { 109 // Synchronize on the class to ensure only creator is only called once (per classloader) 110 synchronized (cls) { 111 H helper = getHelper(cls); 112 if (helper == null) { 113 helper = creator.createHelper(appContext); 114 putHelper(cls, helper); 115 } 116 return helper; 117 } 118 } 119 120 @VisibleForTesting size()121 int size() { 122 return mMap.size(); 123 } 124 125 /** 126 * Put a helper for the given {@code cls} into the registry. Outside of this class, this should 127 * only be used to inject helper for testing purposes. Singletons should use {@link 128 * #getOrCreateWithAppContext(Context, Class, HelperCreator)} to create the instance which will 129 * 130 * @param cls The helper class. 131 * @param helper The helper instance to add to the registry. 132 * @param <H> The helper class. 133 */ putHelper(@onNull Class<H> cls, @NonNull H helper)134 public <H> void putHelper(@NonNull Class<H> cls, @NonNull H helper) { 135 mMap.put(cls, helper); 136 } 137 } 138