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 17 package com.android.adservices.download; 18 19 import static com.android.adservices.service.topics.classifier.ModelManager.BUNDLED_CLASSIFIER_ASSETS_METADATA_FILE_PATH; 20 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.os.Build; 24 import android.os.SystemClock; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.RequiresApi; 28 29 import com.android.adservices.LogUtil; 30 import com.android.adservices.concurrency.AdServicesExecutors; 31 import com.android.adservices.service.Flags; 32 import com.android.adservices.service.FlagsFactory; 33 import com.android.adservices.service.consent.AdServicesApiType; 34 import com.android.adservices.service.consent.ConsentManager; 35 import com.android.adservices.service.topics.classifier.CommonClassifierHelper; 36 37 import com.google.android.downloader.AndroidDownloaderLogger; 38 import com.google.android.downloader.ConnectivityHandler; 39 import com.google.android.downloader.DownloadConstraints; 40 import com.google.android.downloader.Downloader; 41 import com.google.android.downloader.PlatformUrlEngine; 42 import com.google.android.downloader.UrlEngine; 43 import com.google.android.libraries.mobiledatadownload.Logger; 44 import com.google.android.libraries.mobiledatadownload.MobileDataDownload; 45 import com.google.android.libraries.mobiledatadownload.MobileDataDownloadBuilder; 46 import com.google.android.libraries.mobiledatadownload.TimeSource; 47 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; 48 import com.google.android.libraries.mobiledatadownload.downloader.offroad.ExceptionHandler; 49 import com.google.android.libraries.mobiledatadownload.downloader.offroad.Offroad2FileDownloader; 50 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 51 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; 52 import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend; 53 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadMetadataStore; 54 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.SharedPreferencesDownloadMetadata; 55 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; 56 import com.google.android.libraries.mobiledatadownload.populator.ManifestConfigFileParser; 57 import com.google.android.libraries.mobiledatadownload.populator.ManifestConfigOverrider; 58 import com.google.android.libraries.mobiledatadownload.populator.ManifestFileGroupPopulator; 59 import com.google.android.libraries.mobiledatadownload.populator.SharedPreferencesManifestFileMetadata; 60 import com.google.common.annotations.VisibleForTesting; 61 import com.google.common.base.Optional; 62 import com.google.common.collect.ImmutableList; 63 import com.google.common.util.concurrent.Futures; 64 import com.google.common.util.concurrent.ListenableFuture; 65 import com.google.common.util.concurrent.ListeningExecutorService; 66 import com.google.mobiledatadownload.DownloadConfigProto; 67 import com.google.mobiledatadownload.DownloadConfigProto.ManifestFileFlag; 68 import com.google.protobuf.MessageLite; 69 70 import java.io.PrintWriter; 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.concurrent.Executor; 74 75 /** Mobile Data Download Factory. */ 76 // TODO(b/269798827): Enable for R. 77 @RequiresApi(Build.VERSION_CODES.S) 78 public class MobileDataDownloadFactory { 79 private static MobileDataDownload sSingletonMdd; 80 private static SynchronousFileStorage sSynchronousFileStorage; 81 82 private static final String MDD_METADATA_SHARED_PREFERENCES = "mdd_metadata_store"; 83 private static final String TOPICS_MANIFEST_ID = "TopicsManifestId"; 84 private static final String MEASUREMENT_MANIFEST_ID = "MeasurementManifestId"; 85 private static final String UI_OTA_STRINGS_MANIFEST_ID = "UiOtaStringsManifestId"; 86 87 private static final int MAX_ADB_LOGCAT_SIZE = 4000; 88 89 /** Returns a singleton of MobileDataDownload for the whole PPAPI app. */ 90 @NonNull getMdd(@onNull Context context, @NonNull Flags flags)91 public static MobileDataDownload getMdd(@NonNull Context context, @NonNull Flags flags) { 92 synchronized (MobileDataDownloadFactory.class) { 93 if (sSingletonMdd == null) { 94 // TODO(b/236761740): This only adds the core MDD code. We still need other 95 // components: 96 // Add Logger 97 // Add Configurator. 98 99 context = context.getApplicationContext(); 100 SynchronousFileStorage fileStorage = getFileStorage(context); 101 FileDownloader fileDownloader = getFileDownloader(context, flags, fileStorage); 102 NetworkUsageMonitor networkUsageMonitor = 103 new NetworkUsageMonitor( 104 context, 105 new TimeSource() { 106 @Override 107 public long currentTimeMillis() { 108 return System.currentTimeMillis(); 109 } 110 111 @Override 112 public long elapsedRealtimeNanos() { 113 return SystemClock.elapsedRealtimeNanos(); 114 } 115 }); 116 117 sSingletonMdd = 118 MobileDataDownloadBuilder.newBuilder() 119 .setContext(context) 120 .setControlExecutor(getControlExecutor()) 121 .setNetworkUsageMonitor(networkUsageMonitor) 122 .setFileStorage(fileStorage) 123 .setFileDownloaderSupplier(() -> fileDownloader) 124 .addFileGroupPopulator( 125 getTopicsManifestPopulator( 126 context, flags, fileStorage, fileDownloader)) 127 .addFileGroupPopulator( 128 getMeasurementManifestPopulator( 129 context, flags, fileStorage, fileDownloader)) 130 .addFileGroupPopulator( 131 getUiOtaStringsManifestPopulator( 132 context, flags, fileStorage, fileDownloader)) 133 .setLoggerOptional(getMddLogger(flags)) 134 .setFlagsOptional(Optional.of(MddFlags.getInstance())) 135 .build(); 136 } 137 138 return sSingletonMdd; 139 } 140 } 141 142 // Connectivity constraints will be checked by JobScheduler/WorkManager instead. 143 private static class NoOpConnectivityHandler implements ConnectivityHandler { 144 @Override checkConnectivity(DownloadConstraints constraints)145 public ListenableFuture<Void> checkConnectivity(DownloadConstraints constraints) { 146 return Futures.immediateVoidFuture(); 147 } 148 } 149 150 /** Return a singleton of {@link SynchronousFileStorage}. */ 151 @NonNull getFileStorage(@onNull Context context)152 public static SynchronousFileStorage getFileStorage(@NonNull Context context) { 153 synchronized (MobileDataDownloadFactory.class) { 154 if (sSynchronousFileStorage == null) { 155 sSynchronousFileStorage = 156 new SynchronousFileStorage( 157 ImmutableList.of( 158 /*backends*/ AndroidFileBackend.builder(context).build(), 159 new JavaFileBackend()), 160 ImmutableList.of(/*transforms*/ ), 161 ImmutableList.of(/*monitors*/ )); 162 } 163 return sSynchronousFileStorage; 164 } 165 } 166 167 @NonNull 168 @VisibleForTesting getControlExecutor()169 static ListeningExecutorService getControlExecutor() { 170 return AdServicesExecutors.getBackgroundExecutor(); 171 } 172 173 @NonNull getDownloadExecutor()174 private static Executor getDownloadExecutor() { 175 return AdServicesExecutors.getBackgroundExecutor(); 176 } 177 178 @NonNull getUrlEngine(@onNull Flags flags)179 private static UrlEngine getUrlEngine(@NonNull Flags flags) { 180 // TODO(b/219594618): Switch to use CronetUrlEngine. 181 return new PlatformUrlEngine( 182 AdServicesExecutors.getBlockingExecutor(), 183 /* connectTimeoutMs= */ flags.getDownloaderConnectionTimeoutMs(), 184 /* readTimeoutMs= */ flags.getDownloaderReadTimeoutMs()); 185 } 186 187 @NonNull getExceptionHandler()188 private static ExceptionHandler getExceptionHandler() { 189 return ExceptionHandler.withDefaultHandling(); 190 } 191 192 @NonNull 193 @VisibleForTesting getFileDownloader( @onNull Context context, @NonNull Flags flags, @NonNull SynchronousFileStorage fileStorage)194 static FileDownloader getFileDownloader( 195 @NonNull Context context, 196 @NonNull Flags flags, 197 @NonNull SynchronousFileStorage fileStorage) { 198 DownloadMetadataStore downloadMetadataStore = getDownloadMetadataStore(context); 199 200 Downloader downloader = 201 new Downloader.Builder() 202 .withIOExecutor(AdServicesExecutors.getBlockingExecutor()) 203 .withConnectivityHandler(new NoOpConnectivityHandler()) 204 .withMaxConcurrentDownloads(flags.getDownloaderMaxDownloadThreads()) 205 .withLogger(new AndroidDownloaderLogger()) 206 .addUrlEngine("https", getUrlEngine(flags)) 207 .build(); 208 209 return new Offroad2FileDownloader( 210 downloader, 211 fileStorage, 212 getDownloadExecutor(), 213 /* authTokenProvider */ null, 214 downloadMetadataStore, 215 getExceptionHandler(), 216 Optional.absent()); 217 } 218 219 @NonNull getDownloadMetadataStore(@onNull Context context)220 private static DownloadMetadataStore getDownloadMetadataStore(@NonNull Context context) { 221 SharedPreferences sharedPrefs = 222 context.getSharedPreferences(MDD_METADATA_SHARED_PREFERENCES, Context.MODE_PRIVATE); 223 DownloadMetadataStore downloadMetadataStore = 224 new SharedPreferencesDownloadMetadata( 225 sharedPrefs, AdServicesExecutors.getBackgroundExecutor()); 226 return downloadMetadataStore; 227 } 228 229 // Create the Manifest File Group Populator for Topics Classifier. 230 @NonNull 231 @VisibleForTesting getTopicsManifestPopulator( @onNull Context context, @NonNull Flags flags, @NonNull SynchronousFileStorage fileStorage, @NonNull FileDownloader fileDownloader)232 static ManifestFileGroupPopulator getTopicsManifestPopulator( 233 @NonNull Context context, 234 @NonNull Flags flags, 235 @NonNull SynchronousFileStorage fileStorage, 236 @NonNull FileDownloader fileDownloader) { 237 238 ManifestFileFlag manifestFileFlag = 239 ManifestFileFlag.newBuilder() 240 .setManifestId(TOPICS_MANIFEST_ID) 241 .setManifestFileUrl(flags.getMddTopicsClassifierManifestFileUrl()) 242 .build(); 243 244 ManifestConfigFileParser manifestConfigFileParser = 245 new ManifestConfigFileParser( 246 fileStorage, AdServicesExecutors.getBackgroundExecutor()); 247 248 // Only download Topics classifier model when Mdd model build_id is greater than bundled 249 // model. 250 ManifestConfigOverrider manifestConfigOverrider = 251 manifestConfig -> { 252 List<DownloadConfigProto.DataFileGroup> groups = new ArrayList<>(); 253 for (DownloadConfigProto.ManifestConfig.Entry entry : 254 manifestConfig.getEntryList()) { 255 long dataFileGroupBuildId = entry.getDataFileGroup().getBuildId(); 256 long bundledModelBuildId = 257 CommonClassifierHelper.getBundledModelBuildId( 258 context, BUNDLED_CLASSIFIER_ASSETS_METADATA_FILE_PATH); 259 if (dataFileGroupBuildId > bundledModelBuildId) { 260 groups.add(entry.getDataFileGroup()); 261 LogUtil.d("Added topics classifier file group to MDD"); 262 } else { 263 LogUtil.d( 264 "Topics Classifier's Bundled BuildId = %d is bigger than or" 265 + " equal to the BuildId = %d from Server side, skipping" 266 + " the downloading.", 267 bundledModelBuildId, dataFileGroupBuildId); 268 } 269 } 270 return Futures.immediateFuture(groups); 271 }; 272 273 return ManifestFileGroupPopulator.builder() 274 .setContext(context) 275 // topics resources should not be downloaded pre-consent 276 .setEnabledSupplier( 277 () -> { 278 if (flags.getGaUxFeatureEnabled()) { 279 return ConsentManager.getInstance(context) 280 .getConsent(AdServicesApiType.TOPICS) 281 .isGiven(); 282 } else { 283 return ConsentManager.getInstance(context).getConsent().isGiven(); 284 } 285 }) 286 .setBackgroundExecutor(AdServicesExecutors.getBackgroundExecutor()) 287 .setFileDownloader(() -> fileDownloader) 288 .setFileStorage(fileStorage) 289 .setManifestFileFlagSupplier(() -> manifestFileFlag) 290 .setManifestConfigParser(manifestConfigFileParser) 291 .setMetadataStore( 292 SharedPreferencesManifestFileMetadata.createFromContext( 293 context, /*InstanceId*/ 294 Optional.absent(), 295 AdServicesExecutors.getBackgroundExecutor())) 296 // TODO(b/239265537): Enable Dedup using Etag. 297 .setDedupDownloadWithEtag(false) 298 .setOverriderOptional(Optional.of(manifestConfigOverrider)) 299 // TODO(b/243829623): use proper Logger. 300 .setLogger( 301 new Logger() { 302 @Override 303 public void log(MessageLite event, int eventCode) { 304 // A no-op logger. 305 } 306 }) 307 .build(); 308 } 309 310 @NonNull 311 @VisibleForTesting 312 static ManifestFileGroupPopulator getUiOtaStringsManifestPopulator( 313 @NonNull Context context, 314 @NonNull Flags flags, 315 @NonNull SynchronousFileStorage fileStorage, 316 @NonNull FileDownloader fileDownloader) { 317 318 ManifestFileFlag manifestFileFlag = 319 ManifestFileFlag.newBuilder() 320 .setManifestId(UI_OTA_STRINGS_MANIFEST_ID) 321 .setManifestFileUrl(flags.getUiOtaStringsManifestFileUrl()) 322 .build(); 323 324 ManifestConfigFileParser manifestConfigFileParser = 325 new ManifestConfigFileParser( 326 fileStorage, AdServicesExecutors.getBackgroundExecutor()); 327 328 return ManifestFileGroupPopulator.builder() 329 .setContext(context) 330 // OTA resources can be downloaded pre-consent before notification, or with consent 331 .setEnabledSupplier( 332 () -> 333 !ConsentManager.getInstance(context).wasGaUxNotificationDisplayed() 334 || isAnyConsentGiven(context, flags)) 335 .setBackgroundExecutor(AdServicesExecutors.getBackgroundExecutor()) 336 .setFileDownloader(() -> fileDownloader) 337 .setFileStorage(fileStorage) 338 .setManifestFileFlagSupplier(() -> manifestFileFlag) 339 .setManifestConfigParser(manifestConfigFileParser) 340 .setMetadataStore( 341 SharedPreferencesManifestFileMetadata.createFromContext( 342 context, /*InstanceId*/ 343 Optional.absent(), 344 AdServicesExecutors.getBackgroundExecutor())) 345 // TODO(b/239265537): Enable dedup using etag. 346 .setDedupDownloadWithEtag(false) 347 // TODO(b/236761740): user proper Logger. 348 .setLogger( 349 new Logger() { 350 @Override 351 public void log(MessageLite event, int eventCode) { 352 // A no-op logger. 353 } 354 }) 355 .build(); 356 } 357 358 private static boolean isAnyConsentGiven(@NonNull Context context, Flags flags) { 359 ConsentManager instance = ConsentManager.getInstance(context); 360 if (flags.getGaUxFeatureEnabled() 361 && (instance.getConsent(AdServicesApiType.MEASUREMENTS).isGiven() 362 || instance.getConsent(AdServicesApiType.TOPICS).isGiven() 363 || instance.getConsent(AdServicesApiType.FLEDGE).isGiven())) { 364 return true; 365 } 366 367 return instance.getConsent().isGiven(); 368 } 369 370 @NonNull 371 @VisibleForTesting 372 static ManifestFileGroupPopulator getMeasurementManifestPopulator( 373 @NonNull Context context, 374 @NonNull Flags flags, 375 @NonNull SynchronousFileStorage fileStorage, 376 @NonNull FileDownloader fileDownloader) { 377 378 ManifestFileFlag manifestFileFlag = 379 ManifestFileFlag.newBuilder() 380 .setManifestId(MEASUREMENT_MANIFEST_ID) 381 .setManifestFileUrl(flags.getMeasurementManifestFileUrl()) 382 .build(); 383 384 ManifestConfigFileParser manifestConfigFileParser = 385 new ManifestConfigFileParser( 386 fileStorage, AdServicesExecutors.getBackgroundExecutor()); 387 388 return ManifestFileGroupPopulator.builder() 389 .setContext(context) 390 // measurement resources should not be downloaded pre-consent 391 .setEnabledSupplier( 392 () -> { 393 if (flags.getGaUxFeatureEnabled()) { 394 return isAnyConsentGiven(context, flags); 395 } else { 396 return ConsentManager.getInstance(context).getConsent().isGiven(); 397 } 398 }) 399 .setBackgroundExecutor(AdServicesExecutors.getBackgroundExecutor()) 400 .setFileDownloader(() -> fileDownloader) 401 .setFileStorage(fileStorage) 402 .setManifestFileFlagSupplier(() -> manifestFileFlag) 403 .setManifestConfigParser(manifestConfigFileParser) 404 .setMetadataStore( 405 SharedPreferencesManifestFileMetadata.createFromContext( 406 context, /*InstanceId*/ 407 Optional.absent(), 408 AdServicesExecutors.getBackgroundExecutor())) 409 // TODO(b/239265537): Enable dedup using etag. 410 .setDedupDownloadWithEtag(false) 411 // TODO(b/243829623): use proper Logger. 412 .setLogger( 413 new Logger() { 414 @Override 415 public void log(MessageLite event, int eventCode) { 416 // A no-op logger. 417 } 418 }) 419 .build(); 420 } 421 422 // Check killswitch is on or off. True means do not use MddLogger. 423 @NonNull 424 @VisibleForTesting 425 static Optional<Logger> getMddLogger(@NonNull Flags flags) { 426 return flags.getMddLoggerKillSwitch() ? Optional.absent() : Optional.of(new MddLogger()); 427 } 428 429 /** Dump MDD Debug Info. */ 430 public static void dump(Context context, @NonNull PrintWriter writer) { 431 String debugString = 432 MobileDataDownloadFactory.getMdd(context, FlagsFactory.getFlags()) 433 .getDebugInfoAsString(); 434 writer.println("***====*** MDD Lib dump: ***====***"); 435 436 for (int i = 0; i <= debugString.length() / MAX_ADB_LOGCAT_SIZE; i++) { 437 int start = i * MAX_ADB_LOGCAT_SIZE; 438 int end = (i + 1) * MAX_ADB_LOGCAT_SIZE; 439 end = Math.min(debugString.length(), end); 440 writer.println(debugString.substring(start, end)); 441 } 442 } 443 } 444