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.concurrency.AdServicesExecutors;
28 import com.android.adservices.service.Flags;
29 import com.android.cobalt.CobaltLogger;
30 import com.android.cobalt.CobaltPeriodicJob;
31 import com.android.cobalt.CobaltPipelineType;
32 import com.android.cobalt.crypto.HpkeEncrypter;
33 import com.android.cobalt.data.DataService;
34 import com.android.cobalt.domain.Project;
35 import com.android.cobalt.impl.CobaltLoggerImpl;
36 import com.android.cobalt.impl.CobaltPeriodicJobImpl;
37 import com.android.cobalt.observations.PrivacyGenerator;
38 import com.android.cobalt.system.SystemClockImpl;
39 import com.android.cobalt.system.SystemData;
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.security.SecureRandom;
44 import java.time.Duration;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.concurrent.ExecutorService;
48 import java.util.concurrent.ScheduledExecutorService;
49 
50 /** Factory for Cobalt's logger and periodic job implementations. */
51 public final class CobaltFactory {
52     private static final Object SINGLETON_LOCK = new Object();
53 
54     private static final long APEX_VERSION_WHEN_NOT_FOUND = -1L;
55 
56     /*
57      * Uses the prod pipeline because AdServices' reports are for either the DEBUG or GA release
58      * stage and DEBUG is sufficient for local testing.
59      */
60     private static final CobaltPipelineType PIPELINE_TYPE = CobaltPipelineType.PROD;
61 
62     // Objects which are non-trivial to construct or need to be shared between the logger and
63     // periodic job are static.
64     private static Project sSingletonCobaltRegistryProject;
65     private static DataService sSingletonDataService;
66     private static SecureRandom sSingletonSecureRandom;
67     private static SystemData sSingletonSystemData;
68 
69     @GuardedBy("SINGLETON_LOCK")
70     private static CobaltLogger sSingletonCobaltLogger;
71 
72     @GuardedBy("SINGLETON_LOCK")
73     private static CobaltPeriodicJob sSingletonCobaltPeriodicJob;
74 
75     /**
76      * Returns the static singleton CobaltLogger.
77      *
78      * @throws CobaltInitializationException if an unrecoverable errors occurs during initialization
79      */
getCobaltLogger(Context context, Flags flags)80     public static CobaltLogger getCobaltLogger(Context context, Flags flags)
81             throws CobaltInitializationException {
82         Objects.requireNonNull(context);
83         Objects.requireNonNull(flags);
84         synchronized (SINGLETON_LOCK) {
85             if (sSingletonCobaltLogger == null) {
86                 sSingletonCobaltLogger =
87                         new CobaltLoggerImpl(
88                                 getRegistry(context),
89                                 CobaltReleaseStages.getReleaseStage(
90                                         flags.getAdservicesReleaseStageForCobalt()),
91                                 getDataService(context),
92                                 getSystemData(context),
93                                 getExecutor(),
94                                 new SystemClockImpl(),
95                                 flags.getCobaltLoggingEnabled());
96             }
97             return sSingletonCobaltLogger;
98         }
99     }
100 
101     /**
102      * Returns the static singleton CobaltPeriodicJob.
103      *
104      * <p>Note, this implementation does not result in any data being uploaded because the upload
105      * API does not exist yet and the actual uploader is blocked on it landing.
106      *
107      * @throws CobaltInitializationException if an unrecoverable errors occurs during initialization
108      */
getCobaltPeriodicJob(Context context, Flags flags)109     public static CobaltPeriodicJob getCobaltPeriodicJob(Context context, Flags flags)
110             throws CobaltInitializationException {
111         Objects.requireNonNull(context);
112         Objects.requireNonNull(flags);
113         synchronized (SINGLETON_LOCK) {
114             if (sSingletonCobaltPeriodicJob == null) {
115                 sSingletonCobaltPeriodicJob =
116                         new CobaltPeriodicJobImpl(
117                                 getRegistry(context),
118                                 CobaltReleaseStages.getReleaseStage(
119                                         flags.getAdservicesReleaseStageForCobalt()),
120                                 getDataService(context),
121                                 getExecutor(),
122                                 getScheduledExecutor(),
123                                 new SystemClockImpl(),
124                                 getSystemData(context),
125                                 new PrivacyGenerator(getSecureRandom()),
126                                 getSecureRandom(),
127                                 new CobaltUploader(context, PIPELINE_TYPE),
128                                 HpkeEncrypter.createForEnvironment(
129                                         new HpkeEncryptImpl(), PIPELINE_TYPE),
130                                 CobaltApiKeys.copyFromHexApiKey(
131                                         flags.getCobaltAdservicesApiKeyHex()),
132                                 Duration.ofMillis(flags.getCobaltUploadServiceUnbindDelayMs()),
133                                 flags.getCobaltLoggingEnabled());
134             }
135             return sSingletonCobaltPeriodicJob;
136         }
137     }
138 
getExecutor()139     private static ExecutorService getExecutor() {
140         // Cobalt requires disk I/O and must run on the background executor.
141         return AdServicesExecutors.getBackgroundExecutor();
142     }
143 
getScheduledExecutor()144     private static ScheduledExecutorService getScheduledExecutor() {
145         // Cobalt requires a timeout to disconnect from the system server.
146         return AdServicesExecutors.getScheduler();
147     }
148 
getRegistry(Context context)149     private static Project getRegistry(Context context) throws CobaltInitializationException {
150         if (sSingletonCobaltRegistryProject == null) {
151             sSingletonCobaltRegistryProject = CobaltRegistryLoader.getRegistry(context);
152         }
153         return sSingletonCobaltRegistryProject;
154     }
155 
getDataService(Context context)156     private static DataService getDataService(Context context) {
157         Objects.requireNonNull(context);
158         if (sSingletonDataService == null) {
159             sSingletonDataService =
160                     CobaltDataServiceFactory.createDataService(context, getExecutor());
161         }
162 
163         return sSingletonDataService;
164     }
165 
getSecureRandom()166     private static SecureRandom getSecureRandom() {
167         if (sSingletonSecureRandom == null) {
168             sSingletonSecureRandom = new SecureRandom();
169         }
170 
171         return sSingletonSecureRandom;
172     }
173 
getSystemData(Context context)174     private static SystemData getSystemData(Context context) {
175         if (sSingletonSystemData == null) {
176             sSingletonSystemData = new SystemData(computeApexVersion(context));
177         }
178 
179         return sSingletonSystemData;
180     }
181 
182     /**
183      * Returns the {@code Adservices} APEX version in String. If {@code Adservices} is not
184      * available, returns {@code Extservices} APEX version. Otherwise return {@code
185      * APEX_VERSION_WHEN_NOT_FOUND} if {@code Adservices} nor {@code Extservices} are not available.
186      */
187     // TODO(b/323567786): Move this method to a common util class.
188     @VisibleForTesting
computeApexVersion(Context context)189     public static String computeApexVersion(Context context) {
190         PackageManager packageManager = context.getPackageManager();
191         List<PackageInfo> installedPackages =
192                 packageManager.getInstalledPackages(PackageManager.MATCH_APEX);
193         long adservicesVersion = APEX_VERSION_WHEN_NOT_FOUND;
194         long extservicesVersion = APEX_VERSION_WHEN_NOT_FOUND;
195 
196         for (PackageInfo packageInfo : installedPackages) {
197             if (packageInfo.isApex
198                     && packageInfo.packageName.endsWith(ADSERVICES_APEX_NAME_SUFFIX)) {
199                 adservicesVersion = packageInfo.getLongVersionCode();
200                 return String.valueOf(adservicesVersion);
201             } else if (packageInfo.isApex
202                     && packageInfo.packageName.endsWith(EXTSERVICES_APEX_NAME_SUFFIX)) {
203                 extservicesVersion = packageInfo.getLongVersionCode();
204             }
205         }
206         return String.valueOf(extservicesVersion);
207     }
208 }
209