• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload;
17 
18 import android.content.Context;
19 import com.google.android.libraries.mobiledatadownload.account.AccountManagerAccountSource;
20 import com.google.android.libraries.mobiledatadownload.delta.DeltaDecoder;
21 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
22 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
23 import com.google.android.libraries.mobiledatadownload.internal.dagger.ApplicationContextModule;
24 import com.google.android.libraries.mobiledatadownload.internal.dagger.DaggerStandaloneComponent;
25 import com.google.android.libraries.mobiledatadownload.internal.dagger.DownloaderModule;
26 import com.google.android.libraries.mobiledatadownload.internal.dagger.ExecutorsModule;
27 import com.google.android.libraries.mobiledatadownload.internal.dagger.MainMddLibModule;
28 import com.google.android.libraries.mobiledatadownload.internal.dagger.StandaloneComponent;
29 import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
30 import com.google.android.libraries.mobiledatadownload.internal.logging.LogSampler;
31 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
32 import com.google.android.libraries.mobiledatadownload.internal.logging.MddEventLogger;
33 import com.google.android.libraries.mobiledatadownload.internal.logging.NoOpEventLogger;
34 import com.google.android.libraries.mobiledatadownload.lite.Downloader;
35 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
36 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor;
37 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
38 import com.google.common.base.Optional;
39 import com.google.common.base.Preconditions;
40 import com.google.common.base.Supplier;
41 import com.google.common.collect.ImmutableList;
42 import com.google.common.util.concurrent.FutureCallback;
43 import com.google.common.util.concurrent.ListenableFuture;
44 import com.google.common.util.concurrent.ListeningExecutorService;
45 import com.google.common.util.concurrent.MoreExecutors;
46 import com.google.errorprone.annotations.CanIgnoreReturnValue;
47 import java.security.SecureRandom;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.concurrent.Executor;
51 
52 /**
53  * A builder for {@link MobileDataDownload}.
54  *
55  * <p>
56  *
57  * <p>WARNING: Only one object should be built. Otherwise, there may be locking errors on the
58  * underlying database and unnecessary memory consumption.
59  *
60  * <p>Furthermore, there may be interference between scheduled task.
61  */
62 public final class MobileDataDownloadBuilder {
63   private static final String TAG = "MobileDataDownloadBuilder";
64 
65   private final DaggerStandaloneComponent.Builder componentBuilder;
66 
67   private Context context;
68   private ListeningExecutorService controlExecutor;
69   private final List<FileGroupPopulator> fileGroupPopulatorList = new ArrayList<>();
70   private Optional<TaskScheduler> taskSchedulerOptional = Optional.absent();
71   private SynchronousFileStorage fileStorage;
72   private NetworkUsageMonitor networkUsageMonitor;
73   private Optional<DownloadProgressMonitor> downloadMonitorOptional = Optional.absent();
74   private Supplier<FileDownloader> fileDownloaderSupplier;
75   private Optional<DeltaDecoder> deltaDecoderOptional = Optional.absent();
76   private Optional<Configurator> configurator = Optional.absent();
77   private Optional<Logger> loggerOptional = Optional.absent();
78   private Optional<SilentFeedback> silentFeedbackOptional = Optional.absent();
79   private Optional<String> instanceIdOptional = Optional.absent();
80   private Optional<Class<?>> foregroundDownloadServiceClassOptional = Optional.absent();
81   private Optional<Flags> flagsOptional = Optional.absent();
82   private Optional<AccountSource> accountSourceOptional = Optional.absent();
83   private boolean useDefaultAccountSource = true;
84   private Optional<CustomFileGroupValidator> customFileGroupValidatorOptional = Optional.absent();
85   private Optional<ExperimentationConfig> experimentationConfigOptional = Optional.absent();
86 
newBuilder()87   public static MobileDataDownloadBuilder newBuilder() {
88     return new MobileDataDownloadBuilder();
89   }
90 
MobileDataDownloadBuilder()91   private MobileDataDownloadBuilder() {
92     componentBuilder = DaggerStandaloneComponent.builder();
93   }
94 
95   @CanIgnoreReturnValue
setContext(Context context)96   public MobileDataDownloadBuilder setContext(Context context) {
97     this.context = context.getApplicationContext();
98     return this;
99   }
100 
101   /**
102    * Set Unique Instance ID of this instance of MobileDataDownload. Instance ID must be non-empty
103    * and [a-z] (lower case).
104    *
105    * <p>Most apps should use @Singleton MDD. If an app wants to use multiple instances of MDD,
106    * please be aware of following caveats: Each instance of MDD will have its own metadata, base
107    * directory, and periodic backbround tasks. There is no sharing and no-dedup between instances.
108    * Please talk to <internal>@ before using this.
109    */
110   @CanIgnoreReturnValue
setInstanceIdOptional(Optional<String> instanceIdOptional)111   public MobileDataDownloadBuilder setInstanceIdOptional(Optional<String> instanceIdOptional) {
112     this.instanceIdOptional = instanceIdOptional;
113     return this;
114   }
115 
116   /**
117    * Set the Control Executor which will manage MDD meta data.
118    *
119    * <p>NOTE: Control Executor must not be single thread executor otherwise it could lead to
120    * deadlock or other side effects.
121    */
122   @CanIgnoreReturnValue
setControlExecutor(ListeningExecutorService controlExecutor)123   public MobileDataDownloadBuilder setControlExecutor(ListeningExecutorService controlExecutor) {
124     Preconditions.checkNotNull(controlExecutor);
125     // Executor that will execute tasks sequentially.
126     this.controlExecutor = controlExecutor;
127     return this;
128   }
129 
130   /**
131    * Sets a config populator that will be used by MDD to periodically refresh the data file groups.
132    *
133    * <p>If this is not set, then the client is responsible for refreshing the list of file groups in
134    * MDD as and when they see fit.
135    */
136   @CanIgnoreReturnValue
addFileGroupPopulator(FileGroupPopulator fileGroupPopulator)137   public MobileDataDownloadBuilder addFileGroupPopulator(FileGroupPopulator fileGroupPopulator) {
138     this.fileGroupPopulatorList.add(fileGroupPopulator);
139     return this;
140   }
141 
142   /**
143    * Add a list of config populator that will be used by MDD to periodically refresh the data file
144    * groups.
145    *
146    * <p>If this is not set, then the client is responsible for refreshing the list of file groups in
147    * MDD as and when they see fit.
148    */
149   @CanIgnoreReturnValue
addFileGroupPopulators( ImmutableList<FileGroupPopulator> fileGroupPopulators)150   public MobileDataDownloadBuilder addFileGroupPopulators(
151       ImmutableList<FileGroupPopulator> fileGroupPopulators) {
152     this.fileGroupPopulatorList.addAll(fileGroupPopulators);
153     return this;
154   }
155 
156   /**
157    * Set the task scheduler that will be used by MDD to schedule periodic and one-off tasks. Clients
158    * can use GCM, FJD or Work Manager to schedule tasks, and then forward the notification to {@link
159    * MobileDataDownload#handleTask(String)}.
160    */
161   @CanIgnoreReturnValue
setTaskScheduler(Optional<TaskScheduler> taskSchedulerOptional)162   public MobileDataDownloadBuilder setTaskScheduler(Optional<TaskScheduler> taskSchedulerOptional) {
163     this.taskSchedulerOptional = taskSchedulerOptional;
164     return this;
165   }
166 
167   /** Set the optional Configurator which if present will be used by MDD to configure its flags. */
168   @CanIgnoreReturnValue
setConfiguratorOptional(Optional<Configurator> configurator)169   public MobileDataDownloadBuilder setConfiguratorOptional(Optional<Configurator> configurator) {
170     this.configurator = configurator;
171     return this;
172   }
173 
174   /** Set the optional Logger which if present will be used by MDD to log events. */
175   @CanIgnoreReturnValue
setLoggerOptional(Optional<Logger> logger)176   public MobileDataDownloadBuilder setLoggerOptional(Optional<Logger> logger) {
177     this.loggerOptional = logger;
178     return this;
179   }
180 
181   /** Set the flags otherwise default values will be used only. */
182   @CanIgnoreReturnValue
setFlagsOptional(Optional<Flags> flags)183   public MobileDataDownloadBuilder setFlagsOptional(Optional<Flags> flags) {
184     this.flagsOptional = flags;
185     return this;
186   }
187 
188   /**
189    * Set the optional SilentFeedback which if present will be used by MDD to send silent feedbacks.
190    */
191   @CanIgnoreReturnValue
setSilentFeedbackOptional( Optional<SilentFeedback> silentFeedbackOptional)192   public MobileDataDownloadBuilder setSilentFeedbackOptional(
193       Optional<SilentFeedback> silentFeedbackOptional) {
194     this.silentFeedbackOptional = silentFeedbackOptional;
195     return this;
196   }
197 
198   /**
199    * Set the MobStore SynchronousFileStorage. Ideally this should be the same object as the one used
200    * by the client app to read files from MDD
201    */
202   @CanIgnoreReturnValue
setFileStorage(SynchronousFileStorage fileStorage)203   public MobileDataDownloadBuilder setFileStorage(SynchronousFileStorage fileStorage) {
204     this.fileStorage = fileStorage;
205     return this;
206   }
207 
208   /**
209    * Set the NetworkUsageMonitor. This NetworkUsageMonitor instance must be the same instance that
210    * is registered with SynchronousFileStorage.
211    */
212   @CanIgnoreReturnValue
setNetworkUsageMonitor(NetworkUsageMonitor networkUsageMonitor)213   public MobileDataDownloadBuilder setNetworkUsageMonitor(NetworkUsageMonitor networkUsageMonitor) {
214     this.networkUsageMonitor = networkUsageMonitor;
215     return this;
216   }
217 
218   /**
219    * Set the DownloadProgressMonitor. This DownloadProgressMonitor instance must be the same
220    * instance that is registered with SynchronousFileStorage.
221    */
222   @CanIgnoreReturnValue
setDownloadMonitorOptional( Optional<DownloadProgressMonitor> downloadMonitorOptional)223   public MobileDataDownloadBuilder setDownloadMonitorOptional(
224       Optional<DownloadProgressMonitor> downloadMonitorOptional) {
225     this.downloadMonitorOptional = downloadMonitorOptional;
226     return this;
227   }
228 
229   /**
230    * Set the FileDownloader Supplier. MDD takes in a Supplier of FileDownload to support lazy
231    * instantiation of the FileDownloader
232    */
233   @CanIgnoreReturnValue
setFileDownloaderSupplier( Supplier<FileDownloader> fileDownloaderSupplier)234   public MobileDataDownloadBuilder setFileDownloaderSupplier(
235       Supplier<FileDownloader> fileDownloaderSupplier) {
236     this.fileDownloaderSupplier = fileDownloaderSupplier;
237     return this;
238   }
239 
240   /** Set the Delta file decoder. */
241   @CanIgnoreReturnValue
setDeltaDecoderOptional( Optional<DeltaDecoder> deltaDecoderOptional)242   public MobileDataDownloadBuilder setDeltaDecoderOptional(
243       Optional<DeltaDecoder> deltaDecoderOptional) {
244     this.deltaDecoderOptional = deltaDecoderOptional;
245     return this;
246   }
247 
248   /**
249    * Set the Foreground Download Service. This foreground service will keep the download alive even
250    * if the user navigates away from the host app. This ensures long download can finish.
251    *
252    * <p>If the host needs to use both MDDLite and Full MDD, the Foreground Download Service can be
253    * shared as an optimization. Please talk to <internal>@ on how to setup a shared Foreground
254    * Download Service.
255    */
256   @CanIgnoreReturnValue
setForegroundDownloadServiceOptional( Optional<Class<?>> foregroundDownloadServiceClass)257   public MobileDataDownloadBuilder setForegroundDownloadServiceOptional(
258       Optional<Class<?>> foregroundDownloadServiceClass) {
259     this.foregroundDownloadServiceClassOptional = foregroundDownloadServiceClass;
260     return this;
261   }
262 
263   /**
264    * Sets the AccountSource that's used to wipeout account-related data at maintenance time. If this
265    * method is not called, an account source based on AccountManager will be injected.
266    */
267   @CanIgnoreReturnValue
setAccountSourceOptional( Optional<AccountSource> accountSourceOptional)268   public MobileDataDownloadBuilder setAccountSourceOptional(
269       Optional<AccountSource> accountSourceOptional) {
270     this.accountSourceOptional = accountSourceOptional;
271     useDefaultAccountSource = false;
272     return this;
273   }
274 
275   @CanIgnoreReturnValue
setCustomFileGroupValidatorOptional( Optional<CustomFileGroupValidator> customFileGroupValidatorOptional)276   public MobileDataDownloadBuilder setCustomFileGroupValidatorOptional(
277       Optional<CustomFileGroupValidator> customFileGroupValidatorOptional) {
278     this.customFileGroupValidatorOptional = customFileGroupValidatorOptional;
279     return this;
280   }
281 
282   /**
283    * Sets the ExperimentationConfig that's used when propagating experiment ids to external log
284    * sources. If this is not called, experiment ids are not propagated. See <internal> for more
285    * details.
286    */
287   @CanIgnoreReturnValue
setExperimentationConfigOptional( Optional<ExperimentationConfig> experimentationConfigOptional)288   public MobileDataDownloadBuilder setExperimentationConfigOptional(
289       Optional<ExperimentationConfig> experimentationConfigOptional) {
290     this.experimentationConfigOptional = experimentationConfigOptional;
291     return this;
292   }
293 
294   // We use java.util.concurrent.Executor directly to create default Control Executor and
295   // Download Executor.
296 
build()297   public MobileDataDownload build() {
298     Preconditions.checkNotNull(context);
299     Preconditions.checkNotNull(taskSchedulerOptional);
300     Preconditions.checkNotNull(fileStorage);
301 
302     Preconditions.checkNotNull(networkUsageMonitor);
303     Preconditions.checkNotNull(downloadMonitorOptional);
304     Preconditions.checkNotNull(fileDownloaderSupplier);
305     Preconditions.checkNotNull(customFileGroupValidatorOptional);
306 
307     Executor sequentialControlExecutor = MoreExecutors.newSequentialExecutor(controlExecutor);
308     if (configurator.isPresent()) {
309       // Submit commit task to sequentialControlExecutor to ensure that the commit task finishes
310       // before any other API tasks can run.
311       ListenableFuture<Void> commitFuture =
312           PropagatedFutures.submitAsync(
313               () -> configurator.get().commitToFlagSnapshot(), sequentialControlExecutor);
314 
315       PropagatedFutures.addCallback(
316           commitFuture,
317           new FutureCallback<Void>() {
318             @Override
319             public void onSuccess(Void result) {
320               LogUtil.d("%s: Succeeded commitToFlagSnapshot.", TAG);
321             }
322 
323             @Override
324             public void onFailure(Throwable t) {
325               LogUtil.w("%s: Failed to commitToFlagSnapshot: %s", TAG, t);
326             }
327           },
328           MoreExecutors.directExecutor() /*fine to use directExecutor since it only print logs*/);
329     }
330 
331     componentBuilder.applicationContextModule(new ApplicationContextModule(context));
332 
333     componentBuilder.executorsModule(new ExecutorsModule(sequentialControlExecutor));
334 
335     componentBuilder.downloaderModule(
336         new DownloaderModule(deltaDecoderOptional, fileDownloaderSupplier));
337 
338     Flags flags = flagsOptional.or(new Flags() {});
339 
340     // EventLogger is needed in FrameworkProtoDataStoreModule, which is a sting module. As such it
341     // cannot be constructed in our internal dagger module if we want to share the same EventLogger
342     // throughout the library.
343     final EventLogger eventLogger;
344     if (loggerOptional.isPresent()) {
345       eventLogger =
346           new MddEventLogger(
347               context,
348               loggerOptional.get(),
349               Constants.MDD_LIB_VERSION,
350               new LogSampler(flags, new SecureRandom()),
351               flags);
352     } else {
353       eventLogger = new NoOpEventLogger();
354     }
355 
356     if (useDefaultAccountSource) {
357       accountSourceOptional = Optional.of(new AccountManagerAccountSource(context));
358     }
359 
360     componentBuilder.mainMddLibModule(
361         new MainMddLibModule(
362             fileStorage,
363             networkUsageMonitor,
364             eventLogger,
365             downloadMonitorOptional,
366             silentFeedbackOptional,
367             instanceIdOptional,
368             accountSourceOptional,
369             flags,
370             experimentationConfigOptional));
371 
372     StandaloneComponent component = componentBuilder.build();
373 
374     if (eventLogger instanceof MddEventLogger) {
375       ((MddEventLogger) eventLogger).setLoggingStateStore(component.getLoggingStateStore());
376     }
377 
378     Downloader.Builder singleFileDownloaderBuilder =
379         Downloader.newBuilder()
380             .setContext(context)
381             .setControlExecutor(sequentialControlExecutor)
382             .setFileDownloaderSupplier(fileDownloaderSupplier);
383 
384     if (downloadMonitorOptional.isPresent()) {
385       singleFileDownloaderBuilder.setDownloadMonitor(downloadMonitorOptional.get());
386     }
387 
388     if (foregroundDownloadServiceClassOptional.isPresent()) {
389       singleFileDownloaderBuilder.setForegroundDownloadService(
390           foregroundDownloadServiceClassOptional.get());
391     }
392     Downloader singleFileDownloader = singleFileDownloaderBuilder.build();
393 
394     return new MobileDataDownloadImpl(
395         context,
396         component.getEventLogger(),
397         component.getMobileDataDownloadManager(),
398         sequentialControlExecutor,
399         fileGroupPopulatorList,
400         taskSchedulerOptional,
401         fileStorage,
402         downloadMonitorOptional,
403         foregroundDownloadServiceClassOptional,
404         flags,
405         singleFileDownloader,
406         customFileGroupValidatorOptional,
407         component.getTimeSource());
408   }
409 }
410