• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.adservices.cobalt;
18 
19 import static com.android.adservices.AdServicesCommon.ADSERVICES_APEX_NAME_SUFFIX;
20 import static com.android.adservices.AdServicesCommon.EXTSERVICES_APEX_NAME_SUFFIX;
21 import static com.android.adservices.service.measurement.rollback.MeasurementRollbackCompatManager.APEX_VERSION_WHEN_NOT_FOUND;
22 
23 import android.content.Context;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 
27 import com.android.adservices.LogUtil;
28 import com.android.adservices.concurrency.AdServicesExecutors;
29 import com.android.adservices.service.Flags;
30 import com.android.cobalt.CobaltLogger;
31 import com.android.cobalt.CobaltPeriodicJob;
32 import com.android.cobalt.CobaltPipelineType;
33 import com.android.cobalt.crypto.HpkeEncrypter;
34 import com.android.cobalt.data.DataService;
35 import com.android.cobalt.domain.Project;
36 import com.android.cobalt.domain.ReportIdentifier;
37 import com.android.cobalt.impl.CobaltLoggerImpl;
38 import com.android.cobalt.impl.CobaltPeriodicJobImpl;
39 import com.android.cobalt.observations.PrivacyGenerator;
40 import com.android.cobalt.system.SystemClockImpl;
41 import com.android.cobalt.system.SystemData;
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.annotations.VisibleForTesting;
44 
45 import com.google.common.base.Strings;
46 import com.google.common.collect.ImmutableList;
47 
48 import java.security.SecureRandom;
49 import java.time.Duration;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.concurrent.ExecutorService;
53 import java.util.concurrent.ScheduledExecutorService;
54 
55 /** Factory for Cobalt's logger and periodic job implementations. */
56 public final class CobaltFactory {
57     private static final Object SINGLETON_LOCK = new Object();
58     private static final String TAG = CobaltFactory.class.getSimpleName();
59 
60     private static final long APEX_VERSION_WHEN_NOT_FOUND = -1L;
61 
62     /*
63      * Uses the prod pipeline because AdServices' reports are for either the DEBUG or GA release
64      * stage and DEBUG is sufficient for local testing.
65      */
66     private static final CobaltPipelineType PIPELINE_TYPE = CobaltPipelineType.PROD;
67 
68     // Objects which are non-trivial to construct or need to be shared between the logger and
69     // periodic job are static.
70     private static Project sSingletonCobaltRegistryProject;
71     private static DataService sSingletonDataService;
72     private static SecureRandom sSingletonSecureRandom;
73     private static SystemData sSingletonSystemData;
74     private static ImmutableList<ReportIdentifier> sSingletonReportsToIgnore;
75 
76     @GuardedBy("SINGLETON_LOCK")
77     private static CobaltLogger sSingletonCobaltLogger;
78 
79     @GuardedBy("SINGLETON_LOCK")
80     private static CobaltPeriodicJob sSingletonCobaltPeriodicJob;
81 
82     /**
83      * Returns the static singleton CobaltLogger.
84      *
85      * @throws CobaltInitializationException if an unrecoverable errors occurs during initialization
86      */
87     // TODO(b/311183933): Remove passed in Context from static method.
88     @SuppressWarnings("AvoidStaticContext")
getCobaltLogger(Context context, Flags flags)89     public static CobaltLogger getCobaltLogger(Context context, Flags flags)
90             throws CobaltInitializationException {
91         Objects.requireNonNull(context);
92         Objects.requireNonNull(flags);
93         synchronized (SINGLETON_LOCK) {
94             if (sSingletonCobaltLogger == null) {
95                 sSingletonCobaltLogger =
96                         new CobaltLoggerImpl(
97                                 getRegistry(context, flags),
98                                 CobaltReleaseStages.getReleaseStage(
99                                         flags.getAdservicesReleaseStageForCobalt()),
100                                 getDataService(context, flags),
101                                 getSystemData(context),
102                                 getExecutor(),
103                                 new SystemClockImpl(),
104                                 getReportsToIgnore(flags),
105                                 flags.getCobaltLoggingEnabled());
106             }
107             return sSingletonCobaltLogger;
108         }
109     }
110 
111     /**
112      * Returns the static singleton CobaltPeriodicJob.
113      *
114      * <p>Note, this implementation does not result in any data being uploaded because the upload
115      * API does not exist yet and the actual uploader is blocked on it landing.
116      *
117      * @throws CobaltInitializationException if an unrecoverable errors occurs during initialization
118      */
119     // TODO(b/311183933): Remove passed in Context from static method.
120     @SuppressWarnings("AvoidStaticContext")
getCobaltPeriodicJob(Context context, Flags flags)121     public static CobaltPeriodicJob getCobaltPeriodicJob(Context context, Flags flags)
122             throws CobaltInitializationException {
123         Objects.requireNonNull(context);
124         Objects.requireNonNull(flags);
125         synchronized (SINGLETON_LOCK) {
126             if (sSingletonCobaltPeriodicJob == null) {
127                 sSingletonCobaltPeriodicJob =
128                         new CobaltPeriodicJobImpl(
129                                 getRegistry(context, flags),
130                                 CobaltReleaseStages.getReleaseStage(
131                                         flags.getAdservicesReleaseStageForCobalt()),
132                                 getDataService(context, flags),
133                                 getExecutor(),
134                                 getScheduledExecutor(),
135                                 new SystemClockImpl(),
136                                 getSystemData(context),
137                                 new PrivacyGenerator(getSecureRandom()),
138                                 getSecureRandom(),
139                                 new CobaltUploader(context, PIPELINE_TYPE),
140                                 HpkeEncrypter.createForEnvironment(
141                                         new HpkeEncryptImpl(),
142                                         PIPELINE_TYPE,
143                                         CobaltPublicKeyLoader.getInstance()),
144                                 CobaltApiKeys.copyFromHexApiKey(
145                                         flags.getCobaltAdservicesApiKeyHex()),
146                                 Duration.ofMillis(flags.getCobaltUploadServiceUnbindDelayMs()),
147                                 new CobaltOperationLoggerImpl(
148                                         flags.getCobaltOperationalLoggingEnabled()),
149                                 getReportsToIgnore(flags),
150                                 flags.getCobaltLoggingEnabled());
151             }
152             return sSingletonCobaltPeriodicJob;
153         }
154     }
155 
getExecutor()156     private static ExecutorService getExecutor() {
157         // Cobalt requires disk I/O and must run on the background executor.
158         return AdServicesExecutors.getBackgroundExecutor();
159     }
160 
getScheduledExecutor()161     private static ScheduledExecutorService getScheduledExecutor() {
162         // Cobalt requires a timeout to disconnect from the system server.
163         return AdServicesExecutors.getScheduler();
164     }
165 
getRegistry(Context context, Flags flags)166     private static Project getRegistry(Context context, Flags flags)
167             throws CobaltInitializationException {
168         if (sSingletonCobaltRegistryProject == null) {
169             sSingletonCobaltRegistryProject = CobaltRegistryLoader.getRegistry(context, flags);
170         }
171         return sSingletonCobaltRegistryProject;
172     }
173 
getDataService(Context context, Flags flags)174     private static DataService getDataService(Context context, Flags flags) {
175         Objects.requireNonNull(context);
176         if (sSingletonDataService == null) {
177             sSingletonDataService =
178                     CobaltDataServiceFactory.createDataService(
179                             context,
180                             getExecutor(),
181                             new CobaltOperationLoggerImpl(
182                                     flags.getCobaltOperationalLoggingEnabled()));
183         }
184 
185         return sSingletonDataService;
186     }
187 
getSecureRandom()188     private static SecureRandom getSecureRandom() {
189         if (sSingletonSecureRandom == null) {
190             sSingletonSecureRandom = new SecureRandom();
191         }
192 
193         return sSingletonSecureRandom;
194     }
195 
getSystemData(Context context)196     private static SystemData getSystemData(Context context) {
197         if (sSingletonSystemData == null) {
198             sSingletonSystemData = new SystemData(computeApexVersion(context));
199         }
200 
201         return sSingletonSystemData;
202     }
203 
getReportsToIgnore(Flags flags)204     private static ImmutableList<ReportIdentifier> getReportsToIgnore(Flags flags) {
205         if (sSingletonReportsToIgnore == null) {
206             sSingletonReportsToIgnore = parseReportsToIgnore(flags);
207         }
208 
209         return sSingletonReportsToIgnore;
210     }
211 
parseReportsToIgnore(Flags flags)212     static ImmutableList<ReportIdentifier> parseReportsToIgnore(Flags flags) {
213         ImmutableList.Builder<ReportIdentifier> reportsToIgnore = ImmutableList.builder();
214         String flag =
215                 Strings.nullToEmpty(flags.getCobaltIgnoredReportIdList()).replaceAll("\\s", "");
216         for (String reportToIgnore : flag.split(",")) {
217             String[] parts = reportToIgnore.split(":");
218             if (parts.length != 4) {
219                 LogUtil.e("Report to ignore '%s' skipped, contains too few parts", reportToIgnore);
220                 continue;
221             }
222 
223             try {
224                 ReportIdentifier reportIdentifier =
225                         ReportIdentifier.create(
226                                 Integer.parseInt(parts[0]),
227                                 Integer.parseInt(parts[1]),
228                                 Integer.parseInt(parts[2]),
229                                 Integer.parseInt(parts[3]));
230                 if (reportIdentifier.customerId() >= 0
231                         && reportIdentifier.projectId() >= 0
232                         && reportIdentifier.metricId() >= 0
233                         && reportIdentifier.reportId() >= 0) {
234                     reportsToIgnore.add(reportIdentifier);
235                 } else {
236                     LogUtil.e(
237                             "Report to ignore '%s' skipped, contains negative integer",
238                             reportToIgnore);
239                 }
240             } catch (NumberFormatException e) {
241                 LogUtil.e(e, "Failed to parse int from report to ignore '%s'", reportToIgnore);
242             }
243         }
244         return reportsToIgnore.build();
245     }
246 
247     /**
248      * Returns the {@code Adservices} APEX version in String. If {@code Adservices} is not
249      * available, returns {@code Extservices} APEX version. Otherwise return {@code
250      * APEX_VERSION_WHEN_NOT_FOUND} if {@code Adservices} nor {@code Extservices} are not available.
251      */
252     // TODO(b/323567786): Move this method to a common util class.
253     // TODO(b/311183933): Remove passed in Context from static method.
254     @SuppressWarnings("AvoidStaticContext")
255     @VisibleForTesting
computeApexVersion(Context context)256     public static String computeApexVersion(Context context) {
257         PackageManager packageManager = context.getPackageManager();
258         List<PackageInfo> installedPackages =
259                 packageManager.getInstalledPackages(PackageManager.MATCH_APEX);
260         long adservicesVersion = APEX_VERSION_WHEN_NOT_FOUND;
261         long extservicesVersion = APEX_VERSION_WHEN_NOT_FOUND;
262 
263         for (PackageInfo packageInfo : installedPackages) {
264             if (packageInfo.isApex
265                     && packageInfo.packageName.endsWith(ADSERVICES_APEX_NAME_SUFFIX)) {
266                 adservicesVersion = packageInfo.getLongVersionCode();
267                 return String.valueOf(adservicesVersion);
268             } else if (packageInfo.isApex
269                     && packageInfo.packageName.endsWith(EXTSERVICES_APEX_NAME_SUFFIX)) {
270                 extservicesVersion = packageInfo.getLongVersionCode();
271             }
272         }
273         return String.valueOf(extservicesVersion);
274     }
275 }
276