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