| /* |
| * Copyright 2022 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.android.libraries.mobiledatadownload; |
| |
| import android.content.Context; |
| import com.google.android.libraries.mobiledatadownload.account.AccountManagerAccountSource; |
| import com.google.android.libraries.mobiledatadownload.delta.DeltaDecoder; |
| import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; |
| import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.ApplicationContextModule; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.DaggerStandaloneComponent; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.DownloaderModule; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.ExecutorsModule; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.MainMddLibModule; |
| import com.google.android.libraries.mobiledatadownload.internal.dagger.StandaloneComponent; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.LogSampler; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.MddEventLogger; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.NoOpEventLogger; |
| import com.google.android.libraries.mobiledatadownload.lite.Downloader; |
| import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; |
| import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.util.concurrent.FutureCallback; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| import com.google.common.util.concurrent.MoreExecutors; |
| import java.security.SecureRandom; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * A Builder for the {@link MobileDataDownload}. |
| * |
| * <p>WARNING: Only one object should be built. Otherwise, there may be locking errors on the |
| * underlying database and unnecessary memory consumption. |
| * |
| * <p>Furthermore, there may be interference between scheduled task. |
| */ |
| public final class MobileDataDownloadBuilder { |
| private static final String TAG = "MobileDataDownloadBuilder"; |
| |
| private final DaggerStandaloneComponent.Builder componentBuilder; |
| |
| private Context context; |
| private ListeningExecutorService controlExecutor; |
| private final List<FileGroupPopulator> fileGroupPopulatorList = new ArrayList<>(); |
| private Optional<TaskScheduler> taskSchedulerOptional = Optional.absent(); |
| private SynchronousFileStorage fileStorage; |
| private NetworkUsageMonitor networkUsageMonitor; |
| private Optional<DownloadProgressMonitor> downloadMonitorOptional = Optional.absent(); |
| private Supplier<FileDownloader> fileDownloaderSupplier; |
| private Optional<DeltaDecoder> deltaDecoderOptional = Optional.absent(); |
| private Optional<Configurator> configurator = Optional.absent(); |
| private Optional<Logger> loggerOptional = Optional.absent(); |
| private Optional<SilentFeedback> silentFeedbackOptional = Optional.absent(); |
| private Optional<String> instanceIdOptional = Optional.absent(); |
| private Optional<Class<?>> foregroundDownloadServiceClassOptional = Optional.absent(); |
| private Optional<Flags> flagsOptional = Optional.absent(); |
| private Optional<AccountSource> accountSourceOptional = Optional.absent(); |
| private boolean useDefaultAccountSource = true; |
| private Optional<CustomFileGroupValidator> customFileGroupValidatorOptional = Optional.absent(); |
| private Optional<ExperimentationConfig> experimentationConfigOptional = Optional.absent(); |
| |
| public static MobileDataDownloadBuilder newBuilder() { |
| return new MobileDataDownloadBuilder(); |
| } |
| |
| private MobileDataDownloadBuilder() { |
| componentBuilder = DaggerStandaloneComponent.builder(); |
| } |
| |
| public MobileDataDownloadBuilder setContext(Context context) { |
| this.context = context.getApplicationContext(); |
| return this; |
| } |
| |
| /** |
| * Set Unique Instance ID of this instance of MobileDataDownload. Instance ID must be non-empty |
| * and [a-z] (lower case). |
| * |
| * <p>Most apps should use @Singleton MDD. If an app wants to use multiple instances of MDD, |
| * please be aware of following caveats: Each instance of MDD will have its own metadata, base |
| * directory, and periodic backbround tasks. There is no sharing and no-dedup between instances. |
| * Please talk to <internal>@ before using this. |
| */ |
| public MobileDataDownloadBuilder setInstanceIdOptional(Optional<String> instanceIdOptional) { |
| this.instanceIdOptional = instanceIdOptional; |
| return this; |
| } |
| |
| /** |
| * Set the Control Executor which will manage MDD meta data. |
| * |
| * <p>NOTE: Control Executor must not be single thread executor otherwise it could lead to |
| * deadlock or other side effects. |
| */ |
| public MobileDataDownloadBuilder setControlExecutor(ListeningExecutorService controlExecutor) { |
| Preconditions.checkNotNull(controlExecutor); |
| // Executor that will execute tasks sequentially. |
| this.controlExecutor = controlExecutor; |
| return this; |
| } |
| |
| /** |
| * Sets a config populator that will be used by MDD to periodically refresh the data file groups. |
| * |
| * <p>If this is not set, then the client is responsible for refreshing the list of file groups in |
| * MDD as and when they see fit. |
| */ |
| public MobileDataDownloadBuilder addFileGroupPopulator(FileGroupPopulator fileGroupPopulator) { |
| this.fileGroupPopulatorList.add(fileGroupPopulator); |
| return this; |
| } |
| |
| /** |
| * Add a list of config populator that will be used by MDD to periodically refresh the data file |
| * groups. |
| * |
| * <p>If this is not set, then the client is responsible for refreshing the list of file groups in |
| * MDD as and when they see fit. |
| */ |
| public MobileDataDownloadBuilder addFileGroupPopulators( |
| ImmutableList<FileGroupPopulator> fileGroupPopulators) { |
| this.fileGroupPopulatorList.addAll(fileGroupPopulators); |
| return this; |
| } |
| |
| /** |
| * Set the task scheduler that will be used by MDD to schedule periodic and one-off tasks. Clients |
| * can use GCM, FJD or Work Manager to schedule tasks, and then forward the notification to {@link |
| * MobileDataDownload#handleTask(String)}. |
| */ |
| public MobileDataDownloadBuilder setTaskScheduler(Optional<TaskScheduler> taskSchedulerOptional) { |
| this.taskSchedulerOptional = taskSchedulerOptional; |
| return this; |
| } |
| |
| /** Set the optional Configurator which if present will be used by MDD to configure its flags. */ |
| public MobileDataDownloadBuilder setConfiguratorOptional(Optional<Configurator> configurator) { |
| this.configurator = configurator; |
| return this; |
| } |
| |
| /** Set the optional Logger which if present will be used by MDD to log events. */ |
| public MobileDataDownloadBuilder setLoggerOptional(Optional<Logger> logger) { |
| this.loggerOptional = logger; |
| return this; |
| } |
| |
| /** Set the flags otherwise default values will be used only. */ |
| public MobileDataDownloadBuilder setFlagsOptional(Optional<Flags> flags) { |
| this.flagsOptional = flags; |
| return this; |
| } |
| |
| /** |
| * Set the optional SilentFeedback which if present will be used by MDD to send silent feedbacks. |
| */ |
| public MobileDataDownloadBuilder setSilentFeedbackOptional( |
| Optional<SilentFeedback> silentFeedbackOptional) { |
| this.silentFeedbackOptional = silentFeedbackOptional; |
| return this; |
| } |
| |
| /** |
| * Set the MobStore SynchronousFileStorage. Ideally this should be the same object as the one used |
| * by the client app to read files from MDD |
| */ |
| public MobileDataDownloadBuilder setFileStorage(SynchronousFileStorage fileStorage) { |
| this.fileStorage = fileStorage; |
| return this; |
| } |
| |
| /** |
| * Set the NetworkUsageMonitor. This NetworkUsageMonitor instance must be the same instance that |
| * is registered with SynchronousFileStorage. |
| */ |
| public MobileDataDownloadBuilder setNetworkUsageMonitor(NetworkUsageMonitor networkUsageMonitor) { |
| this.networkUsageMonitor = networkUsageMonitor; |
| return this; |
| } |
| |
| /** |
| * Set the DownloadProgressMonitor. This DownloadProgressMonitor instance must be the same |
| * instance that is registered with SynchronousFileStorage. |
| */ |
| public MobileDataDownloadBuilder setDownloadMonitorOptional( |
| Optional<DownloadProgressMonitor> downloadMonitorOptional) { |
| this.downloadMonitorOptional = downloadMonitorOptional; |
| return this; |
| } |
| |
| /** |
| * Set the FileDownloader Supplier. MDD takes in a Supplier of FileDownload to support lazy |
| * instantiation of the FileDownloader |
| */ |
| public MobileDataDownloadBuilder setFileDownloaderSupplier( |
| Supplier<FileDownloader> fileDownloaderSupplier) { |
| this.fileDownloaderSupplier = fileDownloaderSupplier; |
| return this; |
| } |
| |
| /** Set the Delta file decoder. */ |
| public MobileDataDownloadBuilder setDeltaDecoderOptional( |
| Optional<DeltaDecoder> deltaDecoderOptional) { |
| this.deltaDecoderOptional = deltaDecoderOptional; |
| return this; |
| } |
| |
| /** |
| * Set the Foreground Download Service. This foreground service will keep the download alive even |
| * if the user navigates away from the host app. This ensures long download can finish. |
| * |
| * <p>If the host needs to use both MDDLite and Full MDD, the Foreground Download Service can be |
| * shared as an optimization. Please talk to <internal>@ on how to setup a shared Foreground |
| * Download Service. |
| */ |
| public MobileDataDownloadBuilder setForegroundDownloadServiceOptional( |
| Optional<Class<?>> foregroundDownloadServiceClass) { |
| this.foregroundDownloadServiceClassOptional = foregroundDownloadServiceClass; |
| return this; |
| } |
| |
| /** |
| * Sets the AccountSource that's used to wipeout account-related data at maintenance time. If this |
| * method is not called, an account source based on AccountManager will be injected. |
| */ |
| public MobileDataDownloadBuilder setAccountSourceOptional( |
| Optional<AccountSource> accountSourceOptional) { |
| this.accountSourceOptional = accountSourceOptional; |
| useDefaultAccountSource = false; |
| return this; |
| } |
| |
| public MobileDataDownloadBuilder setCustomFileGroupValidatorOptional( |
| Optional<CustomFileGroupValidator> customFileGroupValidatorOptional) { |
| this.customFileGroupValidatorOptional = customFileGroupValidatorOptional; |
| return this; |
| } |
| |
| /** |
| * Sets the ExperimentationConfig that's used when propagating experiment ids to external log |
| * sources. If this is not called, experiment ids are not propagated. See <internal> for more |
| * details. |
| */ |
| public MobileDataDownloadBuilder setExperimentationConfigOptional( |
| Optional<ExperimentationConfig> experimentationConfigOptional) { |
| this.experimentationConfigOptional = experimentationConfigOptional; |
| return this; |
| } |
| |
| // We use java.util.concurrent.Executor directly to create default Control Executor and |
| // Download Executor. |
| public MobileDataDownload build() { |
| Preconditions.checkNotNull(context); |
| Preconditions.checkNotNull(taskSchedulerOptional); |
| Preconditions.checkNotNull(fileStorage); |
| |
| Preconditions.checkNotNull(networkUsageMonitor); |
| Preconditions.checkNotNull(downloadMonitorOptional); |
| Preconditions.checkNotNull(fileDownloaderSupplier); |
| Preconditions.checkNotNull(customFileGroupValidatorOptional); |
| |
| Executor sequentialControlExecutor = MoreExecutors.newSequentialExecutor(controlExecutor); |
| if (configurator.isPresent()) { |
| // Submit commit task to sequentialControlExecutor to ensure that the commit task finishes |
| // before any other API tasks can run. |
| ListenableFuture<Void> commitFuture = |
| Futures.submitAsync( |
| () -> configurator.get().commitToFlagSnapshot(), sequentialControlExecutor); |
| |
| Futures.addCallback( |
| commitFuture, |
| new FutureCallback<Void>() { |
| @Override |
| public void onSuccess(Void result) { |
| LogUtil.d("%s: Succeeded commitToFlagSnapshot.", TAG); |
| } |
| |
| @Override |
| public void onFailure(Throwable t) { |
| LogUtil.w("%s: Failed to commitToFlagSnapshot: %s", TAG, t); |
| } |
| }, |
| MoreExecutors.directExecutor() /*fine to use directExecutor since it only print logs*/); |
| } |
| |
| componentBuilder.applicationContextModule(new ApplicationContextModule(context)); |
| |
| componentBuilder.executorsModule(new ExecutorsModule(sequentialControlExecutor)); |
| |
| componentBuilder.downloaderModule( |
| new DownloaderModule(deltaDecoderOptional, fileDownloaderSupplier)); |
| |
| Flags flags = flagsOptional.or(new Flags() {}); |
| |
| // EventLogger is needed in FrameworkProtoDataStoreModule, which is a sting module. As such it |
| // cannot be constructed in our internal dagger module if we want to share the same EventLogger |
| // throughout the library. |
| final EventLogger eventLogger; |
| if (loggerOptional.isPresent()) { |
| eventLogger = |
| new MddEventLogger( |
| context, |
| loggerOptional.get(), |
| Constants.MDD_LIB_VERSION, |
| new LogSampler(flags, new SecureRandom()), |
| flags); |
| } else { |
| eventLogger = new NoOpEventLogger(); |
| } |
| |
| if (useDefaultAccountSource) { |
| accountSourceOptional = Optional.of(new AccountManagerAccountSource(context)); |
| } |
| |
| componentBuilder.mainMddLibModule( |
| new MainMddLibModule( |
| fileStorage, |
| networkUsageMonitor, |
| eventLogger, |
| downloadMonitorOptional, |
| silentFeedbackOptional, |
| instanceIdOptional, |
| accountSourceOptional, |
| flags, |
| experimentationConfigOptional)); |
| |
| StandaloneComponent component = componentBuilder.build(); |
| |
| if (eventLogger instanceof MddEventLogger) { |
| ((MddEventLogger) eventLogger).setLoggingStateStore(component.getLoggingStateStore()); |
| } |
| |
| Downloader.Builder singleFileDownloaderBuilder = |
| Downloader.newBuilder() |
| .setContext(context) |
| .setControlExecutor(sequentialControlExecutor) |
| .setFileDownloaderSupplier(fileDownloaderSupplier); |
| |
| if (downloadMonitorOptional.isPresent()) { |
| singleFileDownloaderBuilder.setDownloadMonitor(downloadMonitorOptional.get()); |
| } |
| |
| if (foregroundDownloadServiceClassOptional.isPresent()) { |
| singleFileDownloaderBuilder.setForegroundDownloadService( |
| foregroundDownloadServiceClassOptional.get()); |
| } |
| Downloader singleFileDownloader = singleFileDownloaderBuilder.build(); |
| |
| return new MobileDataDownloadImpl( |
| context, |
| component.getEventLogger(), |
| component.getMobileDataDownloadManager(), |
| sequentialControlExecutor, |
| fileGroupPopulatorList, |
| taskSchedulerOptional, |
| fileStorage, |
| downloadMonitorOptional, |
| foregroundDownloadServiceClassOptional, |
| flags, |
| singleFileDownloader, |
| customFileGroupValidatorOptional); |
| } |
| } |