1 /* 2 * Copyright (C) 2023 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 package com.android.adservices.shared.common; 17 18 import android.content.Context; 19 20 import com.android.adservices.shared.util.LogUtil; 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.internal.util.Preconditions; 23 24 import java.util.Objects; 25 import java.util.concurrent.atomic.AtomicReference; 26 27 import javax.annotation.concurrent.ThreadSafe; 28 29 /** 30 * Entry point to get the application {@link Context} of the app. 31 * 32 * <p>The goal of this class is to make it easier to get a context in situations like static methods 33 * or singletons, although it's not meant as a crutch to keep using them. 34 */ 35 @ThreadSafe 36 public final class ApplicationContextSingleton { 37 @VisibleForTesting 38 public static final String ERROR_MESSAGE_SET_NOT_CALLED = "set() not called yet"; 39 40 private static final AtomicReference<Context> sContext = new AtomicReference<>(); 41 42 /** 43 * Gets the application context. 44 * 45 * @throws IllegalStateException if not {@link #set(Context) set} yet. 46 */ get()47 public static Context get() { 48 Context context = sContext.get(); 49 Preconditions.checkState(context != null, ERROR_MESSAGE_SET_NOT_CALLED); 50 return context; 51 } 52 53 /** 54 * Sets the application context singleton (as the {@link Context#getApplicationContext() 55 * application context} from {@code context}). 56 * 57 * @throws IllegalStateException if the singleton was already {@link #set(Context) set} and it 58 * is not the same as the {@link Context#getApplicationContext() application context} from 59 * {@code context}). 60 */ set(Context context)61 public static void set(Context context) { 62 Context appContext = 63 Objects.requireNonNull(context, "context cannot be null").getApplicationContext(); 64 65 Preconditions.checkArgument( 66 appContext != null, "Context (%s) does not have an application context", context); 67 68 // Set if it's not set yet 69 if (sContext.compareAndSet(null, appContext)) { 70 LogUtil.i("Set singleton context as %s", appContext); 71 return; 72 } 73 74 // Otherwise, check it's the same. 75 Context currentAppContext = sContext.get(); 76 if (currentAppContext != appContext) { 77 // TODO(b/309169907): log to CEL 78 throw new IllegalStateException( 79 "Trying to set app context as " 80 + appContext 81 + " (from " 82 + context 83 + "), when it was already set as " 84 + currentAppContext); 85 } 86 } 87 88 // TODO(b/285300419): make it package protected so it's only accessed by rule 89 /** 90 * Gets the application context, returning {@code null} if it's not set yet. 91 * 92 * <p>Should only be used on unit tests - production code should call {@link #get()} instead. 93 */ 94 @VisibleForTesting getForTests()95 public static Context getForTests() { 96 Context context = sContext.get(); 97 LogUtil.i("getForTests(): returning %s", context); 98 return context; 99 } 100 101 // TODO(b/285300419): make it package protected so it's only accessed by rule 102 /** 103 * Sets the application context singleton as the given {@code context}, without doing any check. 104 * 105 * <p>Should only be used on unit tests - production code should call {@link #set(Context) 106 * instead. 107 */ 108 @VisibleForTesting setForTests(Context context)109 public static void setForTests(Context context) { 110 LogUtil.i("setForTests(): from %s to %s.", sContext.get(), context); 111 sContext.set(context); 112 } 113 ApplicationContextSingleton()114 private ApplicationContextSingleton() { 115 throw new UnsupportedOperationException("provides only static methods"); 116 } 117 } 118