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