1 /*
2  * Copyright (C) 2022 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 android.adservices.clients.topics;
17 
18 import android.adservices.topics.GetTopicsRequest;
19 import android.adservices.topics.GetTopicsResponse;
20 import android.adservices.topics.TopicsManager;
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.os.Build;
24 import android.os.OutcomeReceiver;
25 
26 import androidx.concurrent.futures.CallbackToFutureAdapter;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import com.google.common.util.concurrent.ListenableFuture;
31 
32 import java.util.Objects;
33 import java.util.concurrent.Executor;
34 
35 /** AdvertisingTopicsClient. Add more java doc here. */
36 // TODO: This should be in JetPack code.
37 public class AdvertisingTopicsClient {
38 
39     private String mSdkName;
40     private boolean mRecordObservation;
41     private TopicsManager mTopicsManager;
42     private Context mContext;
43     private Executor mExecutor;
44 
AdvertisingTopicsClient( @onNull Context context, @NonNull Executor executor, @NonNull String sdkName, boolean recordObservation, @NonNull TopicsManager topicsManager)45     private AdvertisingTopicsClient(
46             @NonNull Context context,
47             @NonNull Executor executor,
48             @NonNull String sdkName,
49             boolean recordObservation,
50             @NonNull TopicsManager topicsManager) {
51         mContext = context;
52         mSdkName = sdkName;
53         mRecordObservation = recordObservation;
54         mExecutor = executor;
55         mTopicsManager = topicsManager;
56     }
57 
58     /** Gets the SdkName. */
59     @NonNull
getSdkName()60     public String getSdkName() {
61         return mSdkName;
62     }
63 
64     /** Gets the context. */
65     @NonNull
getContext()66     public Context getContext() {
67         return mContext;
68     }
69 
70     /** Gets the worker executor. */
71     @NonNull
getExecutor()72     public Executor getExecutor() {
73         return mExecutor;
74     }
75 
76     /** Get Record Observation. */
shouldRecordObservation()77     public boolean shouldRecordObservation() {
78         return mRecordObservation;
79     }
80 
81     /** Gets the topics. */
getTopics()82     public @NonNull ListenableFuture<GetTopicsResponse> getTopics() {
83         return CallbackToFutureAdapter.getFuture(
84                 completer -> {
85                     GetTopicsRequest.Builder builder = new GetTopicsRequest.Builder();
86                     if (mSdkName != null) {
87                         builder = builder.setAdsSdkName(mSdkName);
88                     }
89                     if (!mRecordObservation) {
90                         builder.setShouldRecordObservation(false);
91                     }
92                     GetTopicsRequest request = builder.build();
93 
94                     mTopicsManager.getTopics(
95                             request,
96                             mExecutor,
97                             new OutcomeReceiver<GetTopicsResponse, Exception>() {
98                                 @Override
99                                 public void onResult(@NonNull GetTopicsResponse result) {
100                                     completer.set(result);
101                                 }
102 
103                                 @Override
104                                 public void onError(@NonNull Exception error) {
105                                     completer.setException(error);
106                                 }
107                             });
108                     // This value is used only for debug purposes: it will be used in toString()
109                     // of returned future or error cases.
110                     return "getTopics";
111                 });
112     }
113 
114     /** Builder class. */
115     public static final class Builder {
116         private String mSdkName;
117         private boolean mRecordObservation = true;
118         private Context mContext;
119         private Executor mExecutor;
120         private boolean mUseGetMethodToCreateManagerInstance;
121 
122         /** Empty-arg constructor with an empty body for Builder */
Builder()123         public Builder() {}
124 
125         /** Sets the context. */
setContext(@onNull Context context)126         public @NonNull AdvertisingTopicsClient.Builder setContext(@NonNull Context context) {
127             mContext = context;
128             return this;
129         }
130 
131         /** Sets the SdkName. */
setSdkName(@onNull String sdkName)132         public @NonNull Builder setSdkName(@NonNull String sdkName) {
133             mSdkName = sdkName;
134             return this;
135         }
136 
137         /**
138          * Set the Record Observation.
139          *
140          * @param recordObservation whether to record that the caller has observed the topics of the
141          *     host app or not. This will be used to determine if the caller can receive the topic
142          *     in the next epoch.
143          */
setShouldRecordObservation(boolean recordObservation)144         public @NonNull Builder setShouldRecordObservation(boolean recordObservation) {
145             mRecordObservation = recordObservation;
146             return this;
147         }
148 
149         /**
150          * Sets the worker executor.
151          *
152          * <p>If an executor is not provided, the AdvertisingTopicsClient default executor will be
153          * used.
154          *
155          * @param executor the worker executor used to run heavy background tasks.
156          */
157         @NonNull
setExecutor(@onNull Executor executor)158         public Builder setExecutor(@NonNull Executor executor) {
159             Objects.requireNonNull(executor);
160             mExecutor = executor;
161             return this;
162         }
163 
164         /**
165          * Sets whether to use the TopicsManager.get(context) method explicitly.
166          *
167          * @param value flag indicating whether to use the TopicsManager.get(context) method
168          *     explicitly. Default is {@code false}.
169          */
170         @VisibleForTesting
171         @NonNull
setUseGetMethodToCreateManagerInstance(boolean value)172         public Builder setUseGetMethodToCreateManagerInstance(boolean value) {
173             mUseGetMethodToCreateManagerInstance = value;
174             return this;
175         }
176 
177         /** Builds a {@link AdvertisingTopicsClient} instance */
build()178         public @NonNull AdvertisingTopicsClient build() {
179             if (mExecutor == null) {
180                 throw new NullPointerException("Executor is not set");
181             }
182 
183             if (mContext == null) {
184                 throw new NullPointerException("Context is not set");
185             }
186 
187             TopicsManager topicsManager = createManager();
188             return new AdvertisingTopicsClient(
189                     mContext, mExecutor, mSdkName, mRecordObservation, topicsManager);
190         }
191 
createManager()192         private TopicsManager createManager() {
193             if (mUseGetMethodToCreateManagerInstance) {
194                 return TopicsManager.get(mContext);
195             }
196 
197             // By default, use getSystemService for T+ and get(context) for S-.
198             return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
199                     ? mContext.getSystemService(TopicsManager.class)
200                     : TopicsManager.get(mContext);
201         }
202     }
203 }
204