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