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