• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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