summaryrefslogtreecommitdiff
path: root/java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java')
-rw-r--r--java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java3431
1 files changed, 1830 insertions, 1601 deletions
diff --git a/java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java b/java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java
index 04abda1..c45ef1f 100644
--- a/java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java
+++ b/java/com/google/android/libraries/mobiledatadownload/MobileDataDownloadImpl.java
@@ -28,8 +28,10 @@ import android.accounts.Account;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
+
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+
import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
import com.google.android.libraries.mobiledatadownload.TaskScheduler.ConstraintOverrides;
import com.google.android.libraries.mobiledatadownload.TaskScheduler.NetworkState;
@@ -66,14 +68,18 @@ import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
import com.google.mobiledatadownload.DownloadConfigProto;
import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
+import com.google.mobiledatadownload.LogEnumsProto.MddLibApiName;
+import com.google.mobiledatadownload.LogEnumsProto.MddLibApiResult;
+import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
+import com.google.mobiledatadownload.LogProto.MddLibApiResultLog;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;
import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
-import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -84,6 +90,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+
import javax.annotation.Nullable;
/**
@@ -92,1711 +99,1933 @@ import javax.annotation.Nullable;
*/
class MobileDataDownloadImpl implements MobileDataDownload {
- private static final String TAG = "MobileDataDownload";
- private static final long DUMP_DEBUG_INFO_TIMEOUT = 3;
-
- private final Context context;
- private final EventLogger eventLogger;
- private final List<FileGroupPopulator> fileGroupPopulatorList;
- private final Optional<TaskScheduler> taskSchedulerOptional;
- private final MobileDataDownloadManager mobileDataDownloadManager;
- private final SynchronousFileStorage fileStorage;
- private final Flags flags;
- private final Downloader singleFileDownloader;
-
- // Track all the on-going foreground downloads. This map is keyed by ForegroundDownloadKey.
- private final DownloadFutureMap<ClientFileGroup> foregroundDownloadFutureMap;
-
- // Track all on-going background download requests started by downloadFileGroup. This map is keyed
- // by ForegroundDownloadKey so request can be kept in sync with foregroundDownloadFutureMap.
- private final DownloadFutureMap<ClientFileGroup> downloadFutureMap;
-
- // This executor will execute tasks sequentially.
- private final Executor sequentialControlExecutor;
- // ExecutionSequencer will execute a ListenableFuture and its Futures.transforms before taking the
- // next task (<internal>). Most of MDD API should use
- // ExecutionSequencer to guarantee Metadata synchronization. Currently only downloadFileGroup and
- // handleTask APIs do not use ExecutionSequencer since their execution could take long time and
- // using ExecutionSequencer would block other APIs.
- private final PropagatedExecutionSequencer futureSerializer =
- PropagatedExecutionSequencer.create();
- private final Optional<DownloadProgressMonitor> downloadMonitorOptional;
- private final Optional<Class<?>> foregroundDownloadServiceClassOptional;
- private final AsyncFunction<DataFileGroupInternal, Boolean> customFileGroupValidator;
- private final TimeSource timeSource;
-
- MobileDataDownloadImpl(
- Context context,
- EventLogger eventLogger,
- MobileDataDownloadManager mobileDataDownloadManager,
- Executor sequentialControlExecutor,
- List<FileGroupPopulator> fileGroupPopulatorList,
- Optional<TaskScheduler> taskSchedulerOptional,
- SynchronousFileStorage fileStorage,
- Optional<DownloadProgressMonitor> downloadMonitorOptional,
- Optional<Class<?>> foregroundDownloadServiceClassOptional,
- Flags flags,
- Downloader singleFileDownloader,
- Optional<CustomFileGroupValidator> customValidatorOptional,
- TimeSource timeSource) {
- this.context = context;
- this.eventLogger = eventLogger;
- this.fileGroupPopulatorList = fileGroupPopulatorList;
- this.taskSchedulerOptional = taskSchedulerOptional;
- this.sequentialControlExecutor = sequentialControlExecutor;
- this.mobileDataDownloadManager = mobileDataDownloadManager;
- this.fileStorage = fileStorage;
- this.downloadMonitorOptional = downloadMonitorOptional;
- this.foregroundDownloadServiceClassOptional = foregroundDownloadServiceClassOptional;
- this.flags = flags;
- this.singleFileDownloader = singleFileDownloader;
- this.customFileGroupValidator =
- createCustomFileGroupValidator(
- customValidatorOptional,
- mobileDataDownloadManager,
- sequentialControlExecutor,
- fileStorage);
- this.downloadFutureMap = DownloadFutureMap.create(sequentialControlExecutor);
- this.foregroundDownloadFutureMap =
- DownloadFutureMap.create(
- sequentialControlExecutor,
- createCallbacksForForegroundService(context, foregroundDownloadServiceClassOptional));
- this.timeSource = timeSource;
- }
-
- // Wraps the custom validator because the validation at a lower level of the stack where
- // the ClientFileGroup is not available, yet ClientFileGroup is the client-facing API we'd
- // like to expose.
- private static AsyncFunction<DataFileGroupInternal, Boolean> createCustomFileGroupValidator(
- Optional<CustomFileGroupValidator> validatorOptional,
- MobileDataDownloadManager mobileDataDownloadManager,
- Executor executor,
- SynchronousFileStorage fileStorage) {
- if (!validatorOptional.isPresent()) {
- return unused -> immediateFuture(true);
+ private static final String TAG = "MobileDataDownload";
+ private static final long DUMP_DEBUG_INFO_TIMEOUT = 3;
+
+ private final Context context;
+ private final EventLogger eventLogger;
+ private final List<FileGroupPopulator> fileGroupPopulatorList;
+ private final Optional<TaskScheduler> taskSchedulerOptional;
+ private final MobileDataDownloadManager mobileDataDownloadManager;
+ private final SynchronousFileStorage fileStorage;
+ private final Flags flags;
+ private final Downloader singleFileDownloader;
+
+ // Track all the on-going foreground downloads. This map is keyed by ForegroundDownloadKey.
+ private final DownloadFutureMap<ClientFileGroup> foregroundDownloadFutureMap;
+
+ // Track all on-going background download requests started by downloadFileGroup. This map is
+ // keyed
+ // by ForegroundDownloadKey so request can be kept in sync with foregroundDownloadFutureMap.
+ private final DownloadFutureMap<ClientFileGroup> downloadFutureMap;
+
+ // This executor will execute tasks sequentially.
+ private final Executor sequentialControlExecutor;
+ // ExecutionSequencer will execute a ListenableFuture and its Futures.transforms before
+ // taking the
+ // next task (<internal>). Most of MDD API should use
+ // ExecutionSequencer to guarantee Metadata synchronization. Currently only downloadFileGroup
+ // and
+ // handleTask APIs do not use ExecutionSequencer since their execution could take long time and
+ // using ExecutionSequencer would block other APIs.
+ private final PropagatedExecutionSequencer futureSerializer =
+ PropagatedExecutionSequencer.create();
+ private final Optional<DownloadProgressMonitor> downloadMonitorOptional;
+ private final Optional<Class<?>> foregroundDownloadServiceClassOptional;
+ private final AsyncFunction<DataFileGroupInternal, Boolean> customFileGroupValidator;
+ private final TimeSource timeSource;
+
+ MobileDataDownloadImpl(
+ Context context,
+ EventLogger eventLogger,
+ MobileDataDownloadManager mobileDataDownloadManager,
+ Executor sequentialControlExecutor,
+ List<FileGroupPopulator> fileGroupPopulatorList,
+ Optional<TaskScheduler> taskSchedulerOptional,
+ SynchronousFileStorage fileStorage,
+ Optional<DownloadProgressMonitor> downloadMonitorOptional,
+ Optional<Class<?>> foregroundDownloadServiceClassOptional,
+ Flags flags,
+ Downloader singleFileDownloader,
+ Optional<CustomFileGroupValidator> customValidatorOptional,
+ TimeSource timeSource) {
+ this.context = context;
+ this.eventLogger = eventLogger;
+ this.fileGroupPopulatorList = fileGroupPopulatorList;
+ this.taskSchedulerOptional = taskSchedulerOptional;
+ this.sequentialControlExecutor = sequentialControlExecutor;
+ this.mobileDataDownloadManager = mobileDataDownloadManager;
+ this.fileStorage = fileStorage;
+ this.downloadMonitorOptional = downloadMonitorOptional;
+ this.foregroundDownloadServiceClassOptional = foregroundDownloadServiceClassOptional;
+ this.flags = flags;
+ this.singleFileDownloader = singleFileDownloader;
+ this.customFileGroupValidator =
+ createCustomFileGroupValidator(
+ customValidatorOptional,
+ mobileDataDownloadManager,
+ sequentialControlExecutor,
+ fileStorage);
+ this.downloadFutureMap = DownloadFutureMap.create(sequentialControlExecutor);
+ this.foregroundDownloadFutureMap =
+ DownloadFutureMap.create(
+ sequentialControlExecutor,
+ createCallbacksForForegroundService(context,
+ foregroundDownloadServiceClassOptional));
+ this.timeSource = timeSource;
}
- return internalFileGroup ->
- PropagatedFutures.transformAsync(
- createClientFileGroup(
- internalFileGroup,
- /* account= */ null,
- ClientFileGroup.Status.PENDING_CUSTOM_VALIDATION,
- /* preserveZipDirectories= */ false,
- /* verifyIsolatedStructure= */ true,
- mobileDataDownloadManager,
- executor,
- fileStorage),
- propagateAsyncFunction(
- clientFileGroup -> validatorOptional.get().validateFileGroup(clientFileGroup)),
- executor);
- }
-
- /**
- * Functional interface used as callback for logging file group stats. Used to create file group
- * stats from the result of the future.
- *
- * @see attachMddApiLogging
- */
- private interface StatsFromApiResultCreator<T> {
- DataDownloadFileGroupStats create(T result);
- }
-
- /**
- * Functional interface used as callback when logging API result. Used to get the API result code
- * from the result of the API future if it succeeds.
- *
- * <p>Note: The need for this is due to {@link addFileGroup} returning false instead of an
- * exception if it fails. For other APIs with proper exception handling, it should suffice to
- * immediately return the success code.
- *
- * <p>TODO(b/143572409): Remove once addGroupForDownload is updated to return void.
- *
- * @see attachMddApiLogging
- */
- private interface ResultCodeFromApiResultGetter<T> {
- int get(T result);
- }
-
- /**
- * Helper function used to log mdd api stats. Adds FutureCallback to the {@code resultFuture}
- * which is the result of mdd api call and logs in onSuccess and onFailure functions of callback.
- *
- * @param apiName Code of the api being logged.
- * @param resultFuture Future result of the api call.
- * @param startTimeNs start time in ns.
- * @param defaultFileGroupStats Initial file group stats.
- * @param statsCreator This functional interface is invoked from the onSuccess of FutureCallback
- * with the result of the future. File group stats returned here is merged with the initial
- * stats and logged.
- */
- private <T> void attachMddApiLogging(
- int apiName,
- ListenableFuture<T> resultFuture,
- long startTimeNs,
- DataDownloadFileGroupStats defaultFileGroupStats,
- StatsFromApiResultCreator<T> statsCreator,
- ResultCodeFromApiResultGetter<T> resultCodeGetter) {
- // Using listener instead of transform since we need to log even if the future fails.
- // Note: Listener is being registered on directexecutor for accurate latency measurement.
- resultFuture.addListener(
- propagateRunnable(
- () -> {
- long latencyNs = timeSource.elapsedRealtimeNanos() - startTimeNs;
- // Log the stats asynchronously.
- // Note: To avoid adding latency to mdd api calls, log asynchronously.
- var unused =
- PropagatedFutures.submit(
- () -> {
- int resultCode;
- T result = null;
- DataDownloadFileGroupStats fileGroupStats = defaultFileGroupStats;
- try {
- result = Futures.getDone(resultFuture);
- resultCode = resultCodeGetter.get(result);
- } catch (Throwable t) {
- resultCode = ExceptionToMddResultMapper.map(t);
- }
-
- // Merge stats created from result of api with the default stats.
- if (result != null) {
- fileGroupStats =
- fileGroupStats.toBuilder()
- .mergeFrom(statsCreator.create(result))
- .build();
- }
+ // Wraps the custom validator because the validation at a lower level of the stack where
+ // the ClientFileGroup is not available, yet ClientFileGroup is the client-facing API we'd
+ // like to expose.
+ private static AsyncFunction<DataFileGroupInternal, Boolean> createCustomFileGroupValidator(
+ Optional<CustomFileGroupValidator> validatorOptional,
+ MobileDataDownloadManager mobileDataDownloadManager,
+ Executor executor,
+ SynchronousFileStorage fileStorage) {
+ if (!validatorOptional.isPresent()) {
+ return unused -> immediateFuture(true);
+ }
- Void resultLog = null;
-
- eventLogger.logMddLibApiResultLog(resultLog);
- },
- sequentialControlExecutor);
- }),
- directExecutor());
- }
-
- @Override
- public ListenableFuture<Boolean> addFileGroup(AddFileGroupRequest addFileGroupRequest) {
- long startTimeNs = timeSource.elapsedRealtimeNanos();
-
- ListenableFuture<Boolean> resultFuture =
- futureSerializer.submitAsync(
- () -> addFileGroupHelper(addFileGroupRequest), sequentialControlExecutor);
-
- DataDownloadFileGroupStats defaultFileGroupStats =
- DataDownloadFileGroupStats.newBuilder()
- .setFileGroupName(addFileGroupRequest.dataFileGroup().getGroupName())
- .setBuildId(addFileGroupRequest.dataFileGroup().getBuildId())
- .setVariantId(addFileGroupRequest.dataFileGroup().getVariantId())
- .setHasAccount(addFileGroupRequest.accountOptional().isPresent())
- .setFileGroupVersionNumber(
- addFileGroupRequest.dataFileGroup().getFileGroupVersionNumber())
- .setOwnerPackage(addFileGroupRequest.dataFileGroup().getOwnerPackage())
- .setFileCount(addFileGroupRequest.dataFileGroup().getFileCount())
- .build();
- attachMddApiLogging(
- 0,
- resultFuture,
- startTimeNs,
- defaultFileGroupStats,
- /* statsCreator= */ unused -> defaultFileGroupStats,
- /* resultCodeGetter= */ succeeded -> succeeded ? 0 : 0);
-
- return resultFuture;
- }
-
- private ListenableFuture<Boolean> addFileGroupHelper(AddFileGroupRequest addFileGroupRequest) {
- LogUtil.d(
- "%s: Adding for download group = '%s', variant = '%s', buildId = '%d' and"
- + " associating it with account = '%s', variant = '%s'",
- TAG,
- addFileGroupRequest.dataFileGroup().getGroupName(),
- addFileGroupRequest.dataFileGroup().getVariantId(),
- addFileGroupRequest.dataFileGroup().getBuildId(),
- String.valueOf(addFileGroupRequest.accountOptional().orNull()),
- String.valueOf(addFileGroupRequest.variantIdOptional().orNull()));
-
- DataFileGroup dataFileGroup = addFileGroupRequest.dataFileGroup();
-
- // Ensure that the owner package is always set as the host app.
- if (!dataFileGroup.hasOwnerPackage()) {
- dataFileGroup = dataFileGroup.toBuilder().setOwnerPackage(context.getPackageName()).build();
- } else if (!context.getPackageName().equals(dataFileGroup.getOwnerPackage())) {
- LogUtil.e(
- "%s: Added group = '%s' with wrong owner package: '%s' v.s. '%s' ",
- TAG,
- dataFileGroup.getGroupName(),
- context.getPackageName(),
- dataFileGroup.getOwnerPackage());
- return immediateFuture(false);
+ return internalFileGroup ->
+ PropagatedFutures.transformAsync(
+ createClientFileGroup(
+ internalFileGroup,
+ /* account= */ null,
+ ClientFileGroup.Status.PENDING_CUSTOM_VALIDATION,
+ /* preserveZipDirectories= */ false,
+ /* verifyIsolatedStructure= */ true,
+ mobileDataDownloadManager,
+ executor,
+ fileStorage),
+ propagateAsyncFunction(
+ clientFileGroup -> validatorOptional.get().validateFileGroup(
+ clientFileGroup)),
+ executor);
}
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder()
- .setGroupName(dataFileGroup.getGroupName())
- .setOwnerPackage(dataFileGroup.getOwnerPackage());
+ /**
+ * Functional interface used as callback for logging file group stats. Used to create file group
+ * stats from the result of the future.
+ *
+ * @see attachMddApiLogging
+ */
+ private interface StatsFromApiResultCreator<T> {
+ DataDownloadFileGroupStats create(T result);
+ }
- if (addFileGroupRequest.accountOptional().isPresent()) {
- groupKeyBuilder.setAccount(
- AccountUtil.serialize(addFileGroupRequest.accountOptional().get()));
+ /**
+ * Functional interface used as callback when logging API result. Used to get the API result
+ * code
+ * from the result of the API future if it succeeds.
+ *
+ * <p>Note: The need for this is due to {@link addFileGroup} returning false instead of an
+ * exception if it fails. For other APIs with proper exception handling, it should suffice to
+ * immediately return the success code.
+ *
+ * <p>TODO(b/143572409): Remove once addGroupForDownload is updated to return void.
+ *
+ * @see attachMddApiLogging
+ */
+ private interface ResultCodeFromApiResultGetter<T> {
+ MddLibApiResult.Code get(T result);
}
- if (addFileGroupRequest.variantIdOptional().isPresent()) {
- groupKeyBuilder.setVariantId(addFileGroupRequest.variantIdOptional().get());
+ /**
+ * Helper function used to log mdd api stats. Adds FutureCallback to the {@code resultFuture}
+ * which is the result of mdd api call and logs in onSuccess and onFailure functions of
+ * callback.
+ *
+ * @param apiName Code of the api being logged.
+ * @param resultFuture Future result of the api call.
+ * @param startTimeNs start time in ns.
+ * @param defaultFileGroupStats Initial file group stats.
+ * @param statsCreator This functional interface is invoked from the onSuccess of
+ * FutureCallback
+ * with the result of the future. File group stats returned here is
+ * merged with the initial
+ * stats and logged.
+ */
+ private <T> void attachMddApiLogging(
+ MddLibApiName.Code apiName,
+ ListenableFuture<T> resultFuture,
+ long startTimeNs,
+ DataDownloadFileGroupStats defaultFileGroupStats,
+ StatsFromApiResultCreator<T> statsCreator,
+ ResultCodeFromApiResultGetter<T> resultCodeGetter) {
+ // Using listener instead of transform since we need to log even if the future fails.
+ // Note: Listener is being registered on directexecutor for accurate latency measurement.
+ resultFuture.addListener(
+ propagateRunnable(
+ () -> {
+ long latencyNs = timeSource.elapsedRealtimeNanos() - startTimeNs;
+ // Log the stats asynchronously.
+ // Note: To avoid adding latency to mdd api calls, log asynchronously.
+ var unused =
+ PropagatedFutures.submit(
+ () -> {
+ MddLibApiResult.Code resultCode;
+ T result = null;
+ DataDownloadFileGroupStats fileGroupStats =
+ defaultFileGroupStats;
+ try {
+ result = Futures.getDone(resultFuture);
+ resultCode = resultCodeGetter.get(result);
+ } catch (Throwable t) {
+ resultCode = ExceptionToMddResultMapper.map(t);
+ }
+
+ // Merge stats created from result of api with
+ // the default stats.
+ if (result != null) {
+ fileGroupStats =
+ fileGroupStats.toBuilder()
+ .mergeFrom(statsCreator.create(
+ result))
+ .build();
+ }
+
+ MddLibApiResultLog resultLog =
+ MddLibApiResultLog.newBuilder()
+ .setApiUsed(apiName)
+ .setResult(resultCode)
+ .setLatencyNs(latencyNs)
+ .addDataDownloadFileGroupStats(
+ fileGroupStats)
+ .build();
+
+ eventLogger.logMddLibApiResultLog(resultLog);
+ },
+ sequentialControlExecutor);
+ }),
+ directExecutor());
}
- try {
- DataFileGroupInternal dataFileGroupInternal = ProtoConversionUtil.convert(dataFileGroup);
- return mobileDataDownloadManager.addGroupForDownloadInternal(
- groupKeyBuilder.build(), dataFileGroupInternal, customFileGroupValidator);
- } catch (InvalidProtocolBufferException e) {
- // TODO(b/118137672): Consider rethrow exception instead of returning false.
- LogUtil.e(e, "%s: Unable to convert from DataFileGroup to DataFileGroupInternal.", TAG);
- return immediateFuture(false);
+ @Override
+ public ListenableFuture<Boolean> addFileGroup(AddFileGroupRequest addFileGroupRequest) {
+ long startTimeNs = timeSource.elapsedRealtimeNanos();
+
+ ListenableFuture<Boolean> resultFuture =
+ futureSerializer.submitAsync(
+ () -> addFileGroupHelper(addFileGroupRequest), sequentialControlExecutor);
+
+ DataDownloadFileGroupStats defaultFileGroupStats =
+ DataDownloadFileGroupStats.newBuilder()
+ .setFileGroupName(addFileGroupRequest.dataFileGroup().getGroupName())
+ .setBuildId(addFileGroupRequest.dataFileGroup().getBuildId())
+ .setVariantId(addFileGroupRequest.dataFileGroup().getVariantId())
+ .setHasAccount(addFileGroupRequest.accountOptional().isPresent())
+ .setFileGroupVersionNumber(
+ addFileGroupRequest.dataFileGroup().getFileGroupVersionNumber())
+ .setOwnerPackage(addFileGroupRequest.dataFileGroup().getOwnerPackage())
+ .setFileCount(addFileGroupRequest.dataFileGroup().getFileCount())
+ .build();
+ attachMddApiLogging(
+ MddLibApiName.Code.ADD_FILE_GROUP,
+ resultFuture,
+ startTimeNs,
+ defaultFileGroupStats,
+ /* statsCreator= */ unused -> defaultFileGroupStats,
+ /* resultCodeGetter= */ succeeded ->
+ succeeded ? MddLibApiResult.Code.RESULT_SUCCESS
+ : MddLibApiResult.Code.RESULT_FAILURE);
+
+ return resultFuture;
}
- }
-
- // TODO: Change to return ListenableFuture<Void>.
- @Override
- public ListenableFuture<Boolean> removeFileGroup(RemoveFileGroupRequest removeFileGroupRequest) {
- return futureSerializer.submitAsync(
- () -> {
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder()
- .setGroupName(removeFileGroupRequest.groupName())
- .setOwnerPackage(context.getPackageName());
- if (removeFileGroupRequest.accountOptional().isPresent()) {
+
+ private ListenableFuture<Boolean> addFileGroupHelper(AddFileGroupRequest addFileGroupRequest) {
+ LogUtil.d(
+ "%s: Adding for download group = '%s', variant = '%s', buildId = '%d' and"
+ + " associating it with account = '%s', variant = '%s'",
+ TAG,
+ addFileGroupRequest.dataFileGroup().getGroupName(),
+ addFileGroupRequest.dataFileGroup().getVariantId(),
+ addFileGroupRequest.dataFileGroup().getBuildId(),
+ String.valueOf(addFileGroupRequest.accountOptional().orNull()),
+ String.valueOf(addFileGroupRequest.variantIdOptional().orNull()));
+
+ DataFileGroup dataFileGroup = addFileGroupRequest.dataFileGroup();
+
+ // Ensure that the owner package is always set as the host app.
+ if (!dataFileGroup.hasOwnerPackage()) {
+ dataFileGroup = dataFileGroup.toBuilder().setOwnerPackage(
+ context.getPackageName()).build();
+ } else if (!context.getPackageName().equals(dataFileGroup.getOwnerPackage())) {
+ LogUtil.e(
+ "%s: Added group = '%s' with wrong owner package: '%s' v.s. '%s' ",
+ TAG,
+ dataFileGroup.getGroupName(),
+ context.getPackageName(),
+ dataFileGroup.getOwnerPackage());
+ return immediateFuture(false);
+ }
+
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder()
+ .setGroupName(dataFileGroup.getGroupName())
+ .setOwnerPackage(dataFileGroup.getOwnerPackage());
+
+ if (addFileGroupRequest.accountOptional().isPresent()) {
groupKeyBuilder.setAccount(
- AccountUtil.serialize(removeFileGroupRequest.accountOptional().get()));
- }
- if (removeFileGroupRequest.variantIdOptional().isPresent()) {
- groupKeyBuilder.setVariantId(removeFileGroupRequest.variantIdOptional().get());
- }
-
- GroupKey groupKey = groupKeyBuilder.build();
- return PropagatedFutures.transform(
- mobileDataDownloadManager.removeFileGroup(
- groupKey, removeFileGroupRequest.pendingOnly()),
- voidArg -> true,
- sequentialControlExecutor);
- },
- sequentialControlExecutor);
- }
-
- @Override
- public ListenableFuture<RemoveFileGroupsByFilterResponse> removeFileGroupsByFilter(
- RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest) {
- return futureSerializer.submitAsync(
- () ->
- PropagatedFluentFuture.from(mobileDataDownloadManager.getAllFreshGroups())
- .transformAsync(
- allFreshGroupKeyAndGroups -> {
- ImmutableSet.Builder<GroupKey> groupKeysToRemoveBuilder =
- ImmutableSet.builder();
- for (GroupKeyAndGroup groupKeyAndGroup : allFreshGroupKeyAndGroups) {
- if (applyRemoveFileGroupsFilter(
- removeFileGroupsByFilterRequest, groupKeyAndGroup)) {
- // Remove downloaded status so pending/downloaded versions of the same
- // group are treated as one.
- groupKeysToRemoveBuilder.add(
- groupKeyAndGroup.groupKey().toBuilder().clearDownloaded().build());
- }
- }
- ImmutableSet<GroupKey> groupKeysToRemove = groupKeysToRemoveBuilder.build();
- if (groupKeysToRemove.isEmpty()) {
- return immediateFuture(
- RemoveFileGroupsByFilterResponse.newBuilder()
- .setRemovedFileGroupsCount(0)
- .build());
- }
- return PropagatedFutures.transform(
- mobileDataDownloadManager.removeFileGroups(groupKeysToRemove.asList()),
- unused ->
- RemoveFileGroupsByFilterResponse.newBuilder()
- .setRemovedFileGroupsCount(groupKeysToRemove.size())
- .build(),
- sequentialControlExecutor);
- },
- sequentialControlExecutor),
- sequentialControlExecutor);
- }
-
- // Perform filtering using options from RemoveFileGroupsByFilterRequest
- private static boolean applyRemoveFileGroupsFilter(
- RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest,
- GroupKeyAndGroup groupKeyAndGroup) {
- // If request filters by account, ensure account is present and is equal
- Optional<Account> accountOptional = removeFileGroupsByFilterRequest.accountOptional();
- if (!accountOptional.isPresent() && groupKeyAndGroup.groupKey().hasAccount()) {
- // Account must explicitly be provided in order to remove account associated file groups.
- return false;
+ AccountUtil.serialize(addFileGroupRequest.accountOptional().get()));
+ }
+
+ if (addFileGroupRequest.variantIdOptional().isPresent()) {
+ groupKeyBuilder.setVariantId(addFileGroupRequest.variantIdOptional().get());
+ }
+
+ try {
+ DataFileGroupInternal dataFileGroupInternal = ProtoConversionUtil.convert(
+ dataFileGroup);
+ return mobileDataDownloadManager.addGroupForDownloadInternal(
+ groupKeyBuilder.build(), dataFileGroupInternal, customFileGroupValidator);
+ } catch (InvalidProtocolBufferException e) {
+ // TODO(b/118137672): Consider rethrow exception instead of returning false.
+ LogUtil.e(e, "%s: Unable to convert from DataFileGroup to DataFileGroupInternal.", TAG);
+ return immediateFuture(false);
+ }
}
- if (accountOptional.isPresent()
- && !AccountUtil.serialize(accountOptional.get())
- .equals(groupKeyAndGroup.groupKey().getAccount())) {
- return false;
+
+ // TODO: Change to return ListenableFuture<Void>.
+ @Override
+ public ListenableFuture<Boolean> removeFileGroup(
+ RemoveFileGroupRequest removeFileGroupRequest) {
+ return futureSerializer.submitAsync(
+ () -> {
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder()
+ .setGroupName(removeFileGroupRequest.groupName())
+ .setOwnerPackage(context.getPackageName());
+ if (removeFileGroupRequest.accountOptional().isPresent()) {
+ groupKeyBuilder.setAccount(
+ AccountUtil.serialize(
+ removeFileGroupRequest.accountOptional().get()));
+ }
+ if (removeFileGroupRequest.variantIdOptional().isPresent()) {
+ groupKeyBuilder.setVariantId(
+ removeFileGroupRequest.variantIdOptional().get());
+ }
+
+ GroupKey groupKey = groupKeyBuilder.build();
+ return PropagatedFutures.transform(
+ mobileDataDownloadManager.removeFileGroup(
+ groupKey, removeFileGroupRequest.pendingOnly()),
+ voidArg -> true,
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
}
- return true;
- }
-
- /**
- * Helper function to create {@link DataDownloadFileGroupStats} object from {@link
- * GetFileGroupRequest} for getFileGroup() logging.
- *
- * <p>Used when the matching file group is not found or a failure occurred.
- * file_group_version_number and build_id are set to -1 by default.
- */
- private DataDownloadFileGroupStats createFileGroupStatsFromGetFileGroupRequest(
- GetFileGroupRequest getFileGroupRequest) {
- DataDownloadFileGroupStats.Builder fileGroupStatsBuilder =
- DataDownloadFileGroupStats.newBuilder();
- fileGroupStatsBuilder.setFileGroupName(getFileGroupRequest.groupName());
- if (getFileGroupRequest.variantIdOptional().isPresent()) {
- fileGroupStatsBuilder.setVariantId(getFileGroupRequest.variantIdOptional().get());
+ @Override
+ public ListenableFuture<RemoveFileGroupsByFilterResponse> removeFileGroupsByFilter(
+ RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest) {
+ return futureSerializer.submitAsync(
+ () ->
+ PropagatedFluentFuture.from(mobileDataDownloadManager.getAllFreshGroups())
+ .transformAsync(
+ allFreshGroupKeyAndGroups -> {
+ ImmutableSet.Builder<GroupKey>
+ groupKeysToRemoveBuilder =
+ ImmutableSet.builder();
+ for (GroupKeyAndGroup groupKeyAndGroup :
+ allFreshGroupKeyAndGroups) {
+ if (applyRemoveFileGroupsFilter(
+ removeFileGroupsByFilterRequest,
+ groupKeyAndGroup)) {
+ // Remove downloaded status so
+ // pending/downloaded versions of the same
+ // group are treated as one.
+ groupKeysToRemoveBuilder.add(
+ groupKeyAndGroup.groupKey().toBuilder().clearDownloaded().build());
+ }
+ }
+ ImmutableSet<GroupKey> groupKeysToRemove =
+ groupKeysToRemoveBuilder.build();
+ if (groupKeysToRemove.isEmpty()) {
+ return immediateFuture(
+ RemoveFileGroupsByFilterResponse.newBuilder()
+ .setRemovedFileGroupsCount(0)
+ .build());
+ }
+ return PropagatedFutures.transform(
+ mobileDataDownloadManager.removeFileGroups(
+ groupKeysToRemove.asList()),
+ unused ->
+ RemoveFileGroupsByFilterResponse.newBuilder()
+ .setRemovedFileGroupsCount(
+ groupKeysToRemove.size())
+ .build(),
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor),
+ sequentialControlExecutor);
}
- if (getFileGroupRequest.accountOptional().isPresent()) {
- fileGroupStatsBuilder.setHasAccount(true);
- } else {
- fileGroupStatsBuilder.setHasAccount(false);
+
+ // Perform filtering using options from RemoveFileGroupsByFilterRequest
+ private static boolean applyRemoveFileGroupsFilter(
+ RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest,
+ GroupKeyAndGroup groupKeyAndGroup) {
+ // If request filters by account, ensure account is present and is equal
+ Optional<Account> accountOptional = removeFileGroupsByFilterRequest.accountOptional();
+ if (!accountOptional.isPresent() && groupKeyAndGroup.groupKey().hasAccount()) {
+ // Account must explicitly be provided in order to remove account associated file
+ // groups.
+ return false;
+ }
+ if (accountOptional.isPresent()
+ && !AccountUtil.serialize(accountOptional.get())
+ .equals(groupKeyAndGroup.groupKey().getAccount())) {
+ return false;
+ }
+
+ return true;
}
- fileGroupStatsBuilder.setFileGroupVersionNumber(
- MddConstants.FILE_GROUP_NOT_FOUND_FILE_GROUP_VERSION_NUMBER);
- fileGroupStatsBuilder.setBuildId(MddConstants.FILE_GROUP_NOT_FOUND_BUILD_ID);
-
- return fileGroupStatsBuilder.build();
- }
-
- // TODO: Futures.immediateFuture(null) uses a different annotation for Nullable.
- @SuppressWarnings("nullness")
- @Override
- public ListenableFuture<ClientFileGroup> getFileGroup(GetFileGroupRequest getFileGroupRequest) {
- long startTimeNs = timeSource.elapsedRealtimeNanos();
-
- ListenableFuture<ClientFileGroup> resultFuture =
- futureSerializer.submitAsync(
- () -> {
- GroupKey groupKey =
- createGroupKey(
- getFileGroupRequest.groupName(),
- getFileGroupRequest.accountOptional(),
- getFileGroupRequest.variantIdOptional());
- return PropagatedFutures.transformAsync(
- mobileDataDownloadManager.getFileGroup(groupKey, /* downloaded= */ true),
- dataFileGroup ->
- createClientFileGroupAndLogQueryStats(
- groupKey,
- dataFileGroup,
- /* downloaded= */ true,
- getFileGroupRequest.preserveZipDirectories(),
- getFileGroupRequest.verifyIsolatedStructure()),
- sequentialControlExecutor);
- },
- sequentialControlExecutor);
-
- attachMddApiLogging(
- 0,
- resultFuture,
- startTimeNs,
- createFileGroupStatsFromGetFileGroupRequest(getFileGroupRequest),
- /* statsCreator= */ result -> createFileGroupDetails(result),
- /* resultCodeGetter= */ unused -> 0);
- return resultFuture;
- }
-
- @SuppressWarnings("nullness")
- @Override
- public ListenableFuture<DataFileGroup> readDataFileGroup(
- ReadDataFileGroupRequest readDataFileGroupRequest) {
- return futureSerializer.submitAsync(
- () -> {
- GroupKey groupKey =
- createGroupKey(
- readDataFileGroupRequest.groupName(),
- readDataFileGroupRequest.accountOptional(),
- readDataFileGroupRequest.variantIdOptional());
- return PropagatedFutures.transformAsync(
- mobileDataDownloadManager.getFileGroup(groupKey, /* downloaded= */ true),
- internalFileGroup -> immediateFuture(ProtoConversionUtil.reverse(internalFileGroup)),
- sequentialControlExecutor);
- },
- sequentialControlExecutor);
- }
-
- private GroupKey createGroupKey(
- String groupName, Optional<Account> accountOptional, Optional<String> variantOptional) {
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(context.getPackageName());
-
- if (accountOptional.isPresent()) {
- groupKeyBuilder.setAccount(AccountUtil.serialize(accountOptional.get()));
+ /**
+ * Helper function to create {@link DataDownloadFileGroupStats} object from {@link
+ * GetFileGroupRequest} for getFileGroup() logging.
+ *
+ * <p>Used when the matching file group is not found or a failure occurred.
+ * file_group_version_number and build_id are set to -1 by default.
+ */
+ private DataDownloadFileGroupStats createFileGroupStatsFromGetFileGroupRequest(
+ GetFileGroupRequest getFileGroupRequest) {
+ DataDownloadFileGroupStats.Builder fileGroupStatsBuilder =
+ DataDownloadFileGroupStats.newBuilder();
+ fileGroupStatsBuilder.setFileGroupName(getFileGroupRequest.groupName());
+ if (getFileGroupRequest.variantIdOptional().isPresent()) {
+ fileGroupStatsBuilder.setVariantId(getFileGroupRequest.variantIdOptional().get());
+ }
+ if (getFileGroupRequest.accountOptional().isPresent()) {
+ fileGroupStatsBuilder.setHasAccount(true);
+ } else {
+ fileGroupStatsBuilder.setHasAccount(false);
+ }
+
+ fileGroupStatsBuilder.setFileGroupVersionNumber(
+ MddConstants.FILE_GROUP_NOT_FOUND_FILE_GROUP_VERSION_NUMBER);
+ fileGroupStatsBuilder.setBuildId(MddConstants.FILE_GROUP_NOT_FOUND_BUILD_ID);
+
+ return fileGroupStatsBuilder.build();
}
- if (variantOptional.isPresent()) {
- groupKeyBuilder.setVariantId(variantOptional.get());
+ // TODO: Futures.immediateFuture(null) uses a different annotation for Nullable.
+ @SuppressWarnings("nullness")
+ @Override
+ public ListenableFuture<ClientFileGroup> getFileGroup(GetFileGroupRequest getFileGroupRequest) {
+ long startTimeNs = timeSource.elapsedRealtimeNanos();
+
+ ListenableFuture<ClientFileGroup> resultFuture =
+ futureSerializer.submitAsync(
+ () -> {
+ GroupKey groupKey =
+ createGroupKey(
+ getFileGroupRequest.groupName(),
+ getFileGroupRequest.accountOptional(),
+ getFileGroupRequest.variantIdOptional());
+ return PropagatedFutures.transformAsync(
+ mobileDataDownloadManager.getFileGroup(
+ groupKey, /* downloaded= */ true),
+ dataFileGroup ->
+ createClientFileGroupAndLogQueryStats(
+ groupKey,
+ dataFileGroup,
+ /* downloaded= */ true,
+ getFileGroupRequest.preserveZipDirectories(),
+ getFileGroupRequest.verifyIsolatedStructure()),
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
+
+ attachMddApiLogging(
+ MddLibApiName.Code.GET_FILE_GROUP,
+ resultFuture,
+ startTimeNs,
+ createFileGroupStatsFromGetFileGroupRequest(getFileGroupRequest),
+ /* statsCreator= */ result -> createFileGroupDetails(result),
+ /* resultCodeGetter= */ unused -> MddLibApiResult.Code.RESULT_SUCCESS);
+ return resultFuture;
}
- return groupKeyBuilder.build();
- }
-
- private ListenableFuture<ClientFileGroup> createClientFileGroupAndLogQueryStats(
- GroupKey groupKey,
- @Nullable DataFileGroupInternal dataFileGroup,
- boolean downloaded,
- boolean preserveZipDirectories,
- boolean verifyIsolatedStructure) {
- return PropagatedFutures.transform(
- createClientFileGroup(
- dataFileGroup,
- groupKey.hasAccount() ? groupKey.getAccount() : null,
- downloaded ? ClientFileGroup.Status.DOWNLOADED : ClientFileGroup.Status.PENDING,
- preserveZipDirectories,
- verifyIsolatedStructure,
- mobileDataDownloadManager,
- sequentialControlExecutor,
- fileStorage),
- clientFileGroup -> {
- if (clientFileGroup != null) {
- eventLogger.logMddQueryStats(createFileGroupDetails(clientFileGroup));
- }
- return clientFileGroup;
- },
- sequentialControlExecutor);
- }
-
- @SuppressWarnings("nullness")
- private static ListenableFuture<ClientFileGroup> createClientFileGroup(
- @Nullable DataFileGroupInternal dataFileGroup,
- @Nullable String account,
- ClientFileGroup.Status status,
- boolean preserveZipDirectories,
- boolean verifyIsolatedStructure,
- MobileDataDownloadManager manager,
- Executor executor,
- SynchronousFileStorage fileStorage) {
- if (dataFileGroup == null) {
- return immediateFuture(null);
+ @SuppressWarnings("nullness")
+ @Override
+ public ListenableFuture<DataFileGroup> readDataFileGroup(
+ ReadDataFileGroupRequest readDataFileGroupRequest) {
+ return futureSerializer.submitAsync(
+ () -> {
+ GroupKey groupKey =
+ createGroupKey(
+ readDataFileGroupRequest.groupName(),
+ readDataFileGroupRequest.accountOptional(),
+ readDataFileGroupRequest.variantIdOptional());
+ return PropagatedFutures.transformAsync(
+ mobileDataDownloadManager.getFileGroup(groupKey, /* downloaded= */
+ true),
+ internalFileGroup -> {
+ if (internalFileGroup == null) {
+ return immediateFailedFuture(
+ DownloadException.builder()
+ .setDownloadResultCode(
+ DownloadResultCode.GROUP_NOT_FOUND_ERROR)
+ .setMessage("Requested group not found.")
+ .build());
+ }
+ return immediateFuture(
+ ProtoConversionUtil.reverse(internalFileGroup));
+ },
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
}
- ClientFileGroup.Builder clientFileGroupBuilder =
- ClientFileGroup.newBuilder()
- .setGroupName(dataFileGroup.getGroupName())
- .setOwnerPackage(dataFileGroup.getOwnerPackage())
- .setVersionNumber(dataFileGroup.getFileGroupVersionNumber())
-// .setCustomProperty(dataFileGroup.getCustomProperty())
- .setBuildId(dataFileGroup.getBuildId())
- .setVariantId(dataFileGroup.getVariantId())
- .setStatus(status)
- .addAllLocale(dataFileGroup.getLocaleList());
-
- if (account != null) {
- clientFileGroupBuilder.setAccount(account);
+
+ @Override
+ public ListenableFuture<ImmutableList<DataFileGroup>> readDataFileGroupsByFilter(
+ ReadDataFileGroupsByFilterRequest request) {
+ return futureSerializer.submitAsync(
+ () ->
+ PropagatedFutures.transformAsync(
+ mobileDataDownloadManager.getAllFreshGroups(),
+ freshGroups -> {
+ ImmutableList<GroupKeyAndGroup> filteredGroups =
+ filterGroups(
+ request.includeAllGroups(),
+ request.groupNameOptional(),
+ request.groupWithNoAccountOnly(),
+ request.accountOptional(),
+ request.downloadedOptional(),
+ freshGroups);
+ ImmutableList.Builder<DataFileGroup> dataFileGroupsBuilder =
+ ImmutableList.<DataFileGroup>builder();
+ for (GroupKeyAndGroup keyAndGroup : filteredGroups) {
+ try {
+ dataFileGroupsBuilder.add(
+ ProtoConversionUtil.reverse(
+ keyAndGroup.dataFileGroup()));
+ } catch (InvalidProtocolBufferException e) {
+ return immediateFailedFuture(e);
+ }
+ }
+ return immediateFuture(dataFileGroupsBuilder.build());
+ },
+ sequentialControlExecutor),
+ sequentialControlExecutor);
}
- if (dataFileGroup.hasCustomMetadata()) {
- clientFileGroupBuilder.setCustomMetadata(dataFileGroup.getCustomMetadata());
+ private GroupKey createGroupKey(
+ String groupName, Optional<Account> accountOptional, Optional<String> variantOptional) {
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(
+ context.getPackageName());
+
+ if (accountOptional.isPresent()) {
+ groupKeyBuilder.setAccount(AccountUtil.serialize(accountOptional.get()));
+ }
+
+ if (variantOptional.isPresent()) {
+ groupKeyBuilder.setVariantId(variantOptional.get());
+ }
+
+ return groupKeyBuilder.build();
}
- List<DataFile> dataFiles = dataFileGroup.getFileList();
- ListenableFuture<Void> addOnDeviceUrisFuture = immediateVoidFuture();
- if (status == ClientFileGroup.Status.DOWNLOADED
- || status == ClientFileGroup.Status.PENDING_CUSTOM_VALIDATION) {
- addOnDeviceUrisFuture =
- PropagatedFluentFuture.from(
- manager.getDataFileUris(dataFileGroup, verifyIsolatedStructure))
- .transformAsync(
- dataFileUriMap -> {
- for (DataFile dataFile : dataFiles) {
- if (!dataFileUriMap.containsKey(dataFile)) {
- return immediateFailedFuture(
- DownloadException.builder()
- .setDownloadResultCode(
- DownloadResultCode.DOWNLOADED_FILE_NOT_FOUND_ERROR)
- .setMessage("getDataFileUris() resolved to null")
- .build());
- }
- Uri uri = dataFileUriMap.get(dataFile);
-
- try {
- if (!preserveZipDirectories && fileStorage.isDirectory(uri)) {
- String rootPath = uri.getPath();
- if (rootPath != null) {
- clientFileGroupBuilder.addAllFile(
- listAllClientFilesOfDirectory(fileStorage, uri, rootPath));
- }
- } else {
- clientFileGroupBuilder.addFile(
- createClientFile(
- dataFile.getFileId(),
- dataFile.getByteSize(),
- dataFile.getDownloadedFileByteSize(),
- uri.toString(),
- dataFile.hasCustomMetadata()
- ? dataFile.getCustomMetadata()
- : null));
- }
- } catch (IOException e) {
- LogUtil.e(e, "Failed to list files under directory:" + uri);
- }
+ private ListenableFuture<ClientFileGroup> createClientFileGroupAndLogQueryStats(
+ GroupKey groupKey,
+ @Nullable DataFileGroupInternal dataFileGroup,
+ boolean downloaded,
+ boolean preserveZipDirectories,
+ boolean verifyIsolatedStructure) {
+ return PropagatedFutures.transform(
+ createClientFileGroup(
+ dataFileGroup,
+ groupKey.hasAccount() ? groupKey.getAccount() : null,
+ downloaded ? ClientFileGroup.Status.DOWNLOADED
+ : ClientFileGroup.Status.PENDING,
+ preserveZipDirectories,
+ verifyIsolatedStructure,
+ mobileDataDownloadManager,
+ sequentialControlExecutor,
+ fileStorage),
+ clientFileGroup -> {
+ if (clientFileGroup != null) {
+ eventLogger.logMddQueryStats(createFileGroupDetails(clientFileGroup));
}
- return immediateVoidFuture();
- },
- executor);
- } else {
- for (DataFile dataFile : dataFiles) {
- clientFileGroupBuilder.addFile(
- createClientFile(
- dataFile.getFileId(),
- dataFile.getByteSize(),
- dataFile.getDownloadedFileByteSize(),
- /* uri= */ null,
- dataFile.hasCustomMetadata() ? dataFile.getCustomMetadata() : null));
- }
+ return clientFileGroup;
+ },
+ sequentialControlExecutor);
}
- return PropagatedFluentFuture.from(addOnDeviceUrisFuture)
- .transform(unused -> clientFileGroupBuilder.build(), executor)
- .catching(DownloadException.class, exn -> null, executor);
- }
-
- private static ClientFile createClientFile(
- String fileId,
- int byteSize,
- int downloadByteSize,
- @Nullable String uri,
- @Nullable Any customMetadata) {
- ClientFile.Builder clientFileBuilder =
- ClientFile.newBuilder().setFileId(fileId).setFullSizeInBytes(byteSize);
- if (downloadByteSize > 0) {
- // Files with downloaded transforms like compress and zip could have different downloaded
- // file size than the final file size on disk. Return the downloaded file size for client to
- // track and calculate the download progress.
- clientFileBuilder.setDownloadSizeInBytes(downloadByteSize);
- }
- if (uri != null) {
- clientFileBuilder.setFileUri(uri);
- }
- if (customMetadata != null) {
- clientFileBuilder.setCustomMetadata(customMetadata);
- }
- return clientFileBuilder.build();
- }
-
- private static List<ClientFile> listAllClientFilesOfDirectory(
- SynchronousFileStorage fileStorage, Uri dirUri, String rootDir) throws IOException {
- List<ClientFile> clientFileList = new ArrayList<>();
- for (Uri childUri : fileStorage.children(dirUri)) {
- if (fileStorage.isDirectory(childUri)) {
- clientFileList.addAll(listAllClientFilesOfDirectory(fileStorage, childUri, rootDir));
- } else {
- String childPath = childUri.getPath();
- if (childPath != null) {
- ClientFile clientFile =
- ClientFile.newBuilder()
- .setFileId(childPath.replaceFirst(rootDir, ""))
- .setFullSizeInBytes((int) fileStorage.fileSize(childUri))
- .setFileUri(childUri.toString())
- .build();
- clientFileList.add(clientFile);
+ @SuppressWarnings("nullness")
+ private static ListenableFuture<ClientFileGroup> createClientFileGroup(
+ @Nullable DataFileGroupInternal dataFileGroup,
+ @Nullable String account,
+ ClientFileGroup.Status status,
+ boolean preserveZipDirectories,
+ boolean verifyIsolatedStructure,
+ MobileDataDownloadManager manager,
+ Executor executor,
+ SynchronousFileStorage fileStorage) {
+ if (dataFileGroup == null) {
+ return immediateFuture(null);
}
- }
- }
- return clientFileList;
- }
-
- @Override
- public ListenableFuture<ImmutableList<ClientFileGroup>> getFileGroupsByFilter(
- GetFileGroupsByFilterRequest getFileGroupsByFilterRequest) {
- return futureSerializer.submitAsync(
- () ->
- PropagatedFutures.transformAsync(
- mobileDataDownloadManager.getAllFreshGroups(),
- allFreshGroupKeyAndGroups -> {
- ListenableFuture<ImmutableList.Builder<ClientFileGroup>>
- clientFileGroupsBuilderFuture =
- immediateFuture(ImmutableList.<ClientFileGroup>builder());
- for (GroupKeyAndGroup groupKeyAndGroup : allFreshGroupKeyAndGroups) {
- clientFileGroupsBuilderFuture =
- PropagatedFutures.transformAsync(
- clientFileGroupsBuilderFuture,
- clientFileGroupsBuilder -> {
- GroupKey groupKey = groupKeyAndGroup.groupKey();
- DataFileGroupInternal dataFileGroup =
- groupKeyAndGroup.dataFileGroup();
- if (applyFilter(
- getFileGroupsByFilterRequest, groupKey, dataFileGroup)) {
- return PropagatedFutures.transform(
- createClientFileGroupAndLogQueryStats(
- groupKey,
- dataFileGroup,
- groupKey.getDownloaded(),
- getFileGroupsByFilterRequest.preserveZipDirectories(),
- getFileGroupsByFilterRequest.verifyIsolatedStructure()),
- clientFileGroup -> {
- if (clientFileGroup != null) {
- clientFileGroupsBuilder.add(clientFileGroup);
- }
- return clientFileGroupsBuilder;
+ ClientFileGroup.Builder clientFileGroupBuilder =
+ ClientFileGroup.newBuilder()
+ .setGroupName(dataFileGroup.getGroupName())
+ .setOwnerPackage(dataFileGroup.getOwnerPackage())
+ .setVersionNumber(dataFileGroup.getFileGroupVersionNumber())
+ .setBuildId(dataFileGroup.getBuildId())
+ .setVariantId(dataFileGroup.getVariantId())
+ .setStatus(status)
+ .addAllLocale(dataFileGroup.getLocaleList());
+
+ if (account != null) {
+ clientFileGroupBuilder.setAccount(account);
+ }
+
+ if (dataFileGroup.hasCustomMetadata()) {
+ clientFileGroupBuilder.setCustomMetadata(dataFileGroup.getCustomMetadata());
+ }
+
+ List<DataFile> dataFiles = dataFileGroup.getFileList();
+ ListenableFuture<Void> addOnDeviceUrisFuture = immediateVoidFuture();
+ if (status == ClientFileGroup.Status.DOWNLOADED
+ || status == ClientFileGroup.Status.PENDING_CUSTOM_VALIDATION) {
+ addOnDeviceUrisFuture =
+ PropagatedFluentFuture.from(
+ manager.getDataFileUris(dataFileGroup, verifyIsolatedStructure))
+ .transformAsync(
+ dataFileUriMap -> {
+ for (DataFile dataFile : dataFiles) {
+ if (!dataFileUriMap.containsKey(dataFile)) {
+ return immediateFailedFuture(
+ DownloadException.builder()
+ .setDownloadResultCode(
+ DownloadResultCode.DOWNLOADED_FILE_NOT_FOUND_ERROR)
+ .setMessage(
+ "getDataFileUris() "
+ + "resolved to null")
+ .build());
+ }
+ Uri uri = dataFileUriMap.get(dataFile);
+
+ try {
+ if (!preserveZipDirectories
+ && fileStorage.isDirectory(uri)) {
+ String rootPath = uri.getPath();
+ if (rootPath != null) {
+ clientFileGroupBuilder.addAllFile(
+ listAllClientFilesOfDirectory(
+ fileStorage, uri,
+ rootPath));
+ }
+ } else {
+ clientFileGroupBuilder.addFile(
+ createClientFile(
+ dataFile.getFileId(),
+ dataFile.getByteSize(),
+ dataFile.getDownloadedFileByteSize(),
+ uri.toString(),
+ dataFile.hasCustomMetadata()
+ ?
+ dataFile.getCustomMetadata()
+ : null));
+ }
+ } catch (IOException e) {
+ LogUtil.e(e, "Failed to list files under directory:"
+ + uri);
+ }
+ }
+ return immediateVoidFuture();
},
- sequentialControlExecutor);
- }
- return immediateFuture(clientFileGroupsBuilder);
- },
- sequentialControlExecutor);
- }
+ executor);
+ } else {
+ for (DataFile dataFile : dataFiles) {
+ clientFileGroupBuilder.addFile(
+ createClientFile(
+ dataFile.getFileId(),
+ dataFile.getByteSize(),
+ dataFile.getDownloadedFileByteSize(),
+ /* uri= */ null,
+ dataFile.hasCustomMetadata() ? dataFile.getCustomMetadata()
+ : null));
+ }
+ }
- return PropagatedFutures.transform(
- clientFileGroupsBuilderFuture,
- ImmutableList.Builder::build,
- sequentialControlExecutor);
- },
- sequentialControlExecutor),
- sequentialControlExecutor);
- }
-
- // Perform filtering using options from GetFileGroupsByFilterRequest
- private static boolean applyFilter(
- GetFileGroupsByFilterRequest getFileGroupsByFilterRequest,
- GroupKey groupKey,
- DataFileGroupInternal fileGroup) {
- if (getFileGroupsByFilterRequest.includeAllGroups()) {
- return true;
+ return PropagatedFluentFuture.from(addOnDeviceUrisFuture)
+ .transform(unused -> clientFileGroupBuilder.build(), executor)
+ .catching(DownloadException.class, exn -> null, executor);
}
- // If request filters by group name, ensure name is equal
- Optional<String> groupNameOptional = getFileGroupsByFilterRequest.groupNameOptional();
- if (groupNameOptional.isPresent()
- && !TextUtils.equals(groupNameOptional.get(), groupKey.getGroupName())) {
- return false;
+ private static ClientFile createClientFile(
+ String fileId,
+ int byteSize,
+ int downloadByteSize,
+ @Nullable String uri,
+ @Nullable Any customMetadata) {
+ ClientFile.Builder clientFileBuilder =
+ ClientFile.newBuilder().setFileId(fileId).setFullSizeInBytes(byteSize);
+ if (downloadByteSize > 0) {
+ // Files with downloaded transforms like compress and zip could have different
+ // downloaded
+ // file size than the final file size on disk. Return the downloaded file size for
+ // client to
+ // track and calculate the download progress.
+ clientFileBuilder.setDownloadSizeInBytes(downloadByteSize);
+ }
+ if (uri != null) {
+ clientFileBuilder.setFileUri(uri);
+ }
+ if (customMetadata != null) {
+ clientFileBuilder.setCustomMetadata(customMetadata);
+ }
+ return clientFileBuilder.build();
}
- // When the caller requests account independent groups only.
- if (getFileGroupsByFilterRequest.groupWithNoAccountOnly()) {
- return !groupKey.hasAccount();
+ private static List<ClientFile> listAllClientFilesOfDirectory(
+ SynchronousFileStorage fileStorage, Uri dirUri, String rootDir) throws IOException {
+ List<ClientFile> clientFileList = new ArrayList<>();
+ for (Uri childUri : fileStorage.children(dirUri)) {
+ if (fileStorage.isDirectory(childUri)) {
+ clientFileList.addAll(
+ listAllClientFilesOfDirectory(fileStorage, childUri, rootDir));
+ } else {
+ String childPath = childUri.getPath();
+ if (childPath != null) {
+ ClientFile clientFile =
+ ClientFile.newBuilder()
+ .setFileId(childPath.replaceFirst(rootDir, ""))
+ .setFullSizeInBytes((int) fileStorage.fileSize(childUri))
+ .setFileUri(childUri.toString())
+ .build();
+ clientFileList.add(clientFile);
+ }
+ }
+ }
+ return clientFileList;
}
- // When the caller requests account dependent groups as well.
- if (getFileGroupsByFilterRequest.accountOptional().isPresent()
- && !AccountUtil.serialize(getFileGroupsByFilterRequest.accountOptional().get())
- .equals(groupKey.getAccount())) {
- return false;
- }
- return true;
- }
-
- /**
- * Creates {@link DataDownloadFileGroupStats} from {@link ClientFileGroup} for remote logging
- * purposes.
- */
- private static DataDownloadFileGroupStats createFileGroupDetails(
- ClientFileGroup clientFileGroup) {
- return DataDownloadFileGroupStats.newBuilder()
- .setFileGroupName(clientFileGroup.getGroupName())
- .setOwnerPackage(clientFileGroup.getOwnerPackage())
- .setFileGroupVersionNumber(clientFileGroup.getVersionNumber())
- .setFileCount(clientFileGroup.getFileCount())
- .setVariantId(clientFileGroup.getVariantId())
- .setBuildId(clientFileGroup.getBuildId())
- .build();
- }
-
- @Override
- public ListenableFuture<Void> importFiles(ImportFilesRequest importFilesRequest) {
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder()
- .setGroupName(importFilesRequest.groupName())
- .setOwnerPackage(context.getPackageName());
-
- if (importFilesRequest.accountOptional().isPresent()) {
- groupKeyBuilder.setAccount(AccountUtil.serialize(importFilesRequest.accountOptional().get()));
+ @Override
+ public ListenableFuture<ImmutableList<ClientFileGroup>> getFileGroupsByFilter(
+ GetFileGroupsByFilterRequest getFileGroupsByFilterRequest) {
+ return futureSerializer.submitAsync(
+ () ->
+ PropagatedFutures.transformAsync(
+ PropagatedFutures.transform(
+ mobileDataDownloadManager.getAllFreshGroups(),
+ allFreshGroups ->
+ filterGroups(
+ getFileGroupsByFilterRequest.includeAllGroups(),
+ getFileGroupsByFilterRequest.groupNameOptional(),
+ getFileGroupsByFilterRequest.groupWithNoAccountOnly(),
+ getFileGroupsByFilterRequest.accountOptional(),
+ Optional.absent(),
+ allFreshGroups),
+ sequentialControlExecutor),
+ filteredGroupKeyAndGroups -> {
+ ListenableFuture<ImmutableList.Builder<ClientFileGroup>>
+ clientFileGroupsBuilderFuture =
+ immediateFuture(
+ ImmutableList.<ClientFileGroup>builder());
+ for (GroupKeyAndGroup groupKeyAndGroup :
+ filteredGroupKeyAndGroups) {
+ clientFileGroupsBuilderFuture =
+ PropagatedFutures.transformAsync(
+ clientFileGroupsBuilderFuture,
+ clientFileGroupsBuilder -> {
+ GroupKey groupKey =
+ groupKeyAndGroup.groupKey();
+ DataFileGroupInternal dataFileGroup =
+ groupKeyAndGroup.dataFileGroup();
+ return PropagatedFutures.transform(
+ createClientFileGroupAndLogQueryStats(
+ groupKey,
+ dataFileGroup,
+ groupKey.getDownloaded(),
+ getFileGroupsByFilterRequest.preserveZipDirectories(),
+ getFileGroupsByFilterRequest.verifyIsolatedStructure()),
+ clientFileGroup -> {
+ if (clientFileGroup
+ != null) {
+ clientFileGroupsBuilder.add(
+ clientFileGroup);
+ }
+ return clientFileGroupsBuilder;
+ },
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
+ }
+ return PropagatedFutures.transform(
+ clientFileGroupsBuilderFuture,
+ ImmutableList.Builder::build,
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor),
+ sequentialControlExecutor);
}
- GroupKey groupKey = groupKeyBuilder.build();
+ private static ImmutableList<GroupKeyAndGroup> filterGroups(
+ boolean includeAllGroups,
+ Optional<String> groupNameOptional,
+ boolean groupWithNoAccountOnly,
+ Optional<Account> accountOptional,
+ Optional<Boolean> downloadedOptional,
+ List<GroupKeyAndGroup> allGroupKeyAndGroups) {
+ var builder = ImmutableList.<GroupKeyAndGroup>builder();
+ if (includeAllGroups) {
+ builder.addAll(allGroupKeyAndGroups);
+ return builder.build();
+ }
- ImmutableList.Builder<DataFile> updatedDataFileListBuilder =
- ImmutableList.builderWithExpectedSize(importFilesRequest.updatedDataFileList().size());
- for (DownloadConfigProto.DataFile dataFile : importFilesRequest.updatedDataFileList()) {
- updatedDataFileListBuilder.add(ProtoConversionUtil.convertDataFile(dataFile));
+ for (GroupKeyAndGroup groupKeyAndGroup : allGroupKeyAndGroups) {
+ GroupKey groupKey = groupKeyAndGroup.groupKey();
+ DataFileGroupInternal dataFileGroup = groupKeyAndGroup.dataFileGroup();
+ if (applyFilter(
+ groupNameOptional,
+ groupWithNoAccountOnly,
+ accountOptional,
+ downloadedOptional,
+ groupKey,
+ dataFileGroup)) {
+ builder.add(groupKeyAndGroup);
+ }
+ }
+ return builder.build();
}
- return futureSerializer.submitAsync(
- () ->
- mobileDataDownloadManager.importFiles(
- groupKey,
- importFilesRequest.buildId(),
- importFilesRequest.variantId(),
- updatedDataFileListBuilder.build(),
- importFilesRequest.inlineFileMap(),
- importFilesRequest.customPropertyOptional(),
- customFileGroupValidator),
- sequentialControlExecutor);
- }
-
- @Override
- public ListenableFuture<Void> downloadFile(SingleFileDownloadRequest singleFileDownloadRequest) {
- return singleFileDownloader.download(
- MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest));
- }
-
- @Override
- public ListenableFuture<ClientFileGroup> downloadFileGroup(
- DownloadFileGroupRequest downloadFileGroupRequest) {
- // Submit the call to sequentialControlExecutor, but don't use futureSerializer. This will
- // ensure that multiple calls are enqueued to the executor in a FIFO order, but these calls
- // won't block each other when the download is in progress.
- return PropagatedFutures.submitAsync(
- () ->
- PropagatedFutures.transformAsync(
- // Check if requested file group has already been downloaded
- getDownloadGroupState(downloadFileGroupRequest),
- downloadGroupState -> {
- switch (downloadGroupState.getKind()) {
- case IN_PROGRESS_FUTURE:
- // If the file group download is in progress, return that future immediately
- return downloadGroupState.inProgressFuture();
- case DOWNLOADED_GROUP:
- // If the file group is already downloaded, return that immediately.
- return immediateFuture(downloadGroupState.downloadedGroup());
- case PENDING_GROUP:
- return downloadPendingFileGroup(downloadFileGroupRequest);
- }
- throw new AssertionError(
- String.format(
- "received unsupported DownloadGroupState kind %s",
- downloadGroupState.getKind()));
- },
- sequentialControlExecutor),
- sequentialControlExecutor);
- }
-
- /** Helper method to download a group after it's determined to be pending. */
- private ListenableFuture<ClientFileGroup> downloadPendingFileGroup(
- DownloadFileGroupRequest downloadFileGroupRequest) {
- String groupName = downloadFileGroupRequest.groupName();
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(context.getPackageName());
-
- if (downloadFileGroupRequest.accountOptional().isPresent()) {
- groupKeyBuilder.setAccount(
- AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
+ /** Check if given data matches with {@code groupKey} and {@code fileGroup}. */
+ private static boolean applyFilter(
+ Optional<String> groupNameOptional,
+ boolean groupWithNoAccountOnly,
+ Optional<Account> accountOptional,
+ Optional<Boolean> downloadedOptional,
+ GroupKey groupKey,
+ DataFileGroupInternal fileGroup) {
+ // If request filters by group name, ensure name is equal
+ if (groupNameOptional.isPresent()
+ && !TextUtils.equals(groupNameOptional.get(), groupKey.getGroupName())) {
+ return false;
+ }
+
+ // When the caller requests account independent groups only.
+ if (groupWithNoAccountOnly) {
+ return !groupKey.hasAccount();
+ }
+
+ // When the caller requests account dependent groups as well.
+ if (accountOptional.isPresent()
+ && !AccountUtil.serialize(accountOptional.get()).equals(groupKey.getAccount())) {
+ return false;
+ }
+
+ if (downloadedOptional.isPresent()
+ && !downloadedOptional.get().equals(groupKey.getDownloaded())) {
+ return false;
+ }
+
+ return true;
}
- if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
- groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
+
+ /**
+ * Creates {@link DataDownloadFileGroupStats} from {@link ClientFileGroup} for remote logging
+ * purposes.
+ */
+ private static DataDownloadFileGroupStats createFileGroupDetails(
+ ClientFileGroup clientFileGroup) {
+ return DataDownloadFileGroupStats.newBuilder()
+ .setFileGroupName(clientFileGroup.getGroupName())
+ .setOwnerPackage(clientFileGroup.getOwnerPackage())
+ .setFileGroupVersionNumber(clientFileGroup.getVersionNumber())
+ .setFileCount(clientFileGroup.getFileCount())
+ .setVariantId(clientFileGroup.getVariantId())
+ .setBuildId(clientFileGroup.getBuildId())
+ .build();
}
- GroupKey groupKey = groupKeyBuilder.build();
+ @Override
+ public ListenableFuture<Void> importFiles(ImportFilesRequest importFilesRequest) {
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder()
+ .setGroupName(importFilesRequest.groupName())
+ .setOwnerPackage(context.getPackageName());
- if (downloadFileGroupRequest.listenerOptional().isPresent()) {
- if (downloadMonitorOptional.isPresent()) {
- downloadMonitorOptional
- .get()
- .addDownloadListener(groupName, downloadFileGroupRequest.listenerOptional().get());
- } else {
- return immediateFailedFuture(
- DownloadException.builder()
- .setDownloadResultCode(DownloadResultCode.DOWNLOAD_MONITOR_NOT_PROVIDED_ERROR)
- .setMessage(
- "downloadFileGroup: DownloadListener is present but Download Monitor"
- + " is not provided!")
- .build());
- }
+ if (importFilesRequest.accountOptional().isPresent()) {
+ groupKeyBuilder.setAccount(
+ AccountUtil.serialize(importFilesRequest.accountOptional().get()));
+ }
+
+ GroupKey groupKey = groupKeyBuilder.build();
+
+ ImmutableList.Builder<DataFile> updatedDataFileListBuilder =
+ ImmutableList.builderWithExpectedSize(
+ importFilesRequest.updatedDataFileList().size());
+ for (DownloadConfigProto.DataFile dataFile : importFilesRequest.updatedDataFileList()) {
+ updatedDataFileListBuilder.add(ProtoConversionUtil.convertDataFile(dataFile));
+ }
+
+ return futureSerializer.submitAsync(
+ () ->
+ mobileDataDownloadManager.importFiles(
+ groupKey,
+ importFilesRequest.buildId(),
+ importFilesRequest.variantId(),
+ updatedDataFileListBuilder.build(),
+ importFilesRequest.inlineFileMap(),
+ importFilesRequest.customPropertyOptional(),
+ customFileGroupValidator),
+ sequentialControlExecutor);
}
- Optional<DownloadConditions> downloadConditions;
- try {
- downloadConditions =
- downloadFileGroupRequest.downloadConditionsOptional().isPresent()
- ? Optional.of(
- ProtoConversionUtil.convert(
- downloadFileGroupRequest.downloadConditionsOptional().get()))
- : Optional.absent();
- } catch (InvalidProtocolBufferException e) {
- return immediateFailedFuture(e);
+ @Override
+ public ListenableFuture<Void> downloadFile(
+ SingleFileDownloadRequest singleFileDownloadRequest) {
+ return singleFileDownloader.download(
+ MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest));
}
- // Get the key used for the download future map
- ForegroundDownloadKey downloadKey =
- ForegroundDownloadKey.ofFileGroup(
- downloadFileGroupRequest.groupName(),
- downloadFileGroupRequest.accountOptional(),
- downloadFileGroupRequest.variantIdOptional());
-
- // Create a ListenableFutureTask to delay starting the downloadFuture until we can add the
- // future to our map.
- ListenableFutureTask<Void> startTask = ListenableFutureTask.create(() -> null);
- ListenableFuture<ClientFileGroup> downloadFuture =
- PropagatedFluentFuture.from(startTask)
- .transformAsync(
- unused ->
- mobileDataDownloadManager.downloadFileGroup(
- groupKey, downloadConditions, customFileGroupValidator),
- sequentialControlExecutor)
- .transformAsync(
- dataFileGroup ->
- createClientFileGroup(
- dataFileGroup,
- downloadFileGroupRequest.accountOptional().isPresent()
- ? AccountUtil.serialize(
- downloadFileGroupRequest.accountOptional().get())
- : null,
- ClientFileGroup.Status.DOWNLOADED,
- downloadFileGroupRequest.preserveZipDirectories(),
- downloadFileGroupRequest.verifyIsolatedStructure(),
- mobileDataDownloadManager,
- sequentialControlExecutor,
- fileStorage),
- sequentialControlExecutor)
- .transform(Preconditions::checkNotNull, sequentialControlExecutor);
-
- // Get a handle on the download task so we can get the CFG during transforms
- PropagatedFluentFuture<ClientFileGroup> downloadTaskFuture =
- PropagatedFluentFuture.from(downloadFutureMap.add(downloadKey.toString(), downloadFuture))
- .transformAsync(
- unused -> {
- // Now that the download future is added, start the task and return the future
- startTask.run();
- return downloadFuture;
- },
+ @Override
+ public ListenableFuture<ClientFileGroup> downloadFileGroup(
+ DownloadFileGroupRequest downloadFileGroupRequest) {
+ // Submit the call to sequentialControlExecutor, but don't use futureSerializer. This will
+ // ensure that multiple calls are enqueued to the executor in a FIFO order, but these calls
+ // won't block each other when the download is in progress.
+ return PropagatedFutures.submitAsync(
+ () ->
+ PropagatedFutures.transformAsync(
+ // Check if requested file group has already been downloaded
+ getDownloadGroupState(downloadFileGroupRequest),
+ downloadGroupState -> {
+ switch (downloadGroupState.getKind()) {
+ case IN_PROGRESS_FUTURE:
+ // If the file group download is in progress, return
+ // that future immediately
+ return downloadGroupState.inProgressFuture();
+ case DOWNLOADED_GROUP:
+ // If the file group is already downloaded, return
+ // that immediately.
+ return immediateFuture(
+ downloadGroupState.downloadedGroup());
+ case PENDING_GROUP:
+ return downloadPendingFileGroup(
+ downloadFileGroupRequest);
+ }
+ throw new AssertionError(
+ String.format(
+ "received unsupported DownloadGroupState kind"
+ + " %s",
+ downloadGroupState.getKind()));
+ },
+ sequentialControlExecutor),
sequentialControlExecutor);
+ }
+
+ /** Helper method to download a group after it's determined to be pending. */
+ private ListenableFuture<ClientFileGroup> downloadPendingFileGroup(
+ DownloadFileGroupRequest downloadFileGroupRequest) {
+ String groupName = downloadFileGroupRequest.groupName();
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(
+ context.getPackageName());
+
+ if (downloadFileGroupRequest.accountOptional().isPresent()) {
+ groupKeyBuilder.setAccount(
+ AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
+ }
+ if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
+ groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
+ }
- ListenableFuture<ClientFileGroup> transformFuture =
- downloadTaskFuture
- .transformAsync(
- unused -> downloadFutureMap.remove(downloadKey.toString()),
- sequentialControlExecutor)
- .transformAsync(
- unused -> {
- ClientFileGroup clientFileGroup = getDone(downloadTaskFuture);
-
- if (downloadFileGroupRequest.listenerOptional().isPresent()) {
- try {
- downloadFileGroupRequest.listenerOptional().get().onComplete(clientFileGroup);
- } catch (Exception e) {
- LogUtil.w(
- e,
- "%s: Listener onComplete failed for group %s",
- TAG,
- clientFileGroup.getGroupName());
+ GroupKey groupKey = groupKeyBuilder.build();
+
+ if (downloadFileGroupRequest.listenerOptional().isPresent()) {
+ if (downloadMonitorOptional.isPresent()) {
+ downloadMonitorOptional
+ .get()
+ .addDownloadListener(groupName,
+ downloadFileGroupRequest.listenerOptional().get());
+ } else {
+ return immediateFailedFuture(
+ DownloadException.builder()
+ .setDownloadResultCode(
+ DownloadResultCode.DOWNLOAD_MONITOR_NOT_PROVIDED_ERROR)
+ .setMessage(
+ "downloadFileGroup: DownloadListener is present but "
+ + "Download Monitor"
+ + " is not provided!")
+ .build());
+ }
+ }
+
+ Optional<DownloadConditions> downloadConditions;
+ try {
+ downloadConditions =
+ downloadFileGroupRequest.downloadConditionsOptional().isPresent()
+ ? Optional.of(
+ ProtoConversionUtil.convert(
+ downloadFileGroupRequest.downloadConditionsOptional().get()))
+ : Optional.absent();
+ } catch (InvalidProtocolBufferException e) {
+ return immediateFailedFuture(e);
+ }
+
+ // Get the key used for the download future map
+ ForegroundDownloadKey downloadKey =
+ ForegroundDownloadKey.ofFileGroup(
+ downloadFileGroupRequest.groupName(),
+ downloadFileGroupRequest.accountOptional(),
+ downloadFileGroupRequest.variantIdOptional());
+
+ // Create a ListenableFutureTask to delay starting the downloadFuture until we can add the
+ // future to our map.
+ ListenableFutureTask<Void> startTask = ListenableFutureTask.create(() -> null);
+ ListenableFuture<ClientFileGroup> downloadFuture =
+ PropagatedFluentFuture.from(startTask)
+ .transformAsync(
+ unused ->
+ mobileDataDownloadManager.downloadFileGroup(
+ groupKey, downloadConditions,
+ customFileGroupValidator),
+ sequentialControlExecutor)
+ .transformAsync(
+ dataFileGroup ->
+ createClientFileGroup(
+ dataFileGroup,
+ downloadFileGroupRequest.accountOptional().isPresent()
+ ? AccountUtil.serialize(
+ downloadFileGroupRequest.accountOptional().get())
+ : null,
+ ClientFileGroup.Status.DOWNLOADED,
+ downloadFileGroupRequest.preserveZipDirectories(),
+ downloadFileGroupRequest.verifyIsolatedStructure(),
+ mobileDataDownloadManager,
+ sequentialControlExecutor,
+ fileStorage),
+ sequentialControlExecutor)
+ .transform(Preconditions::checkNotNull, sequentialControlExecutor);
+
+ // Get a handle on the download task so we can get the CFG during transforms
+ PropagatedFluentFuture<ClientFileGroup> downloadTaskFuture =
+ PropagatedFluentFuture.from(
+ downloadFutureMap.add(downloadKey.toString(), downloadFuture))
+ .transformAsync(
+ unused -> {
+ // Now that the download future is added, start the task and
+ // return the future
+ startTask.run();
+ return downloadFuture;
+ },
+ sequentialControlExecutor);
+
+ ListenableFuture<ClientFileGroup> transformFuture =
+ downloadTaskFuture
+ .transformAsync(
+ unused -> downloadFutureMap.remove(downloadKey.toString()),
+ sequentialControlExecutor)
+ .transformAsync(
+ unused -> {
+ ClientFileGroup clientFileGroup = getDone(downloadTaskFuture);
+
+ if (downloadFileGroupRequest.listenerOptional().isPresent()) {
+ try {
+ downloadFileGroupRequest.listenerOptional().get().onComplete(
+ clientFileGroup);
+ } catch (Exception e) {
+ LogUtil.w(
+ e,
+ "%s: Listener onComplete failed for group %s",
+ TAG,
+ clientFileGroup.getGroupName());
+ }
+ if (downloadMonitorOptional.isPresent()) {
+ downloadMonitorOptional.get().removeDownloadListener(
+ groupName);
+ }
+ }
+ return immediateFuture(clientFileGroup);
+ },
+ sequentialControlExecutor);
+
+ PropagatedFutures.addCallback(
+ transformFuture,
+ new FutureCallback<ClientFileGroup>() {
+ @Override
+ public void onSuccess(ClientFileGroup result) {
}
- if (downloadMonitorOptional.isPresent()) {
- downloadMonitorOptional.get().removeDownloadListener(groupName);
+
+ @Override
+ public void onFailure(Throwable t) {
+ if (downloadFileGroupRequest.listenerOptional().isPresent()) {
+ downloadFileGroupRequest.listenerOptional().get().onFailure(t);
+
+ if (downloadMonitorOptional.isPresent()) {
+ downloadMonitorOptional.get().removeDownloadListener(groupName);
+ }
+ }
+
+ // Remove future from map
+ ListenableFuture<Void> unused = downloadFutureMap.remove(
+ downloadKey.toString());
}
- }
- return immediateFuture(clientFileGroup);
},
sequentialControlExecutor);
- PropagatedFutures.addCallback(
- transformFuture,
- new FutureCallback<ClientFileGroup>() {
- @Override
- public void onSuccess(ClientFileGroup result) {}
+ return transformFuture;
+ }
- @Override
- public void onFailure(Throwable t) {
- if (downloadFileGroupRequest.listenerOptional().isPresent()) {
- downloadFileGroupRequest.listenerOptional().get().onFailure(t);
+ @Override
+ public ListenableFuture<Void> downloadFileWithForegroundService(
+ SingleFileDownloadRequest singleFileDownloadRequest) {
+ return singleFileDownloader.downloadWithForegroundService(
+ MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest));
+ }
- if (downloadMonitorOptional.isPresent()) {
- downloadMonitorOptional.get().removeDownloadListener(groupName);
- }
- }
+ @Override
+ public ListenableFuture<ClientFileGroup> downloadFileGroupWithForegroundService(
+ DownloadFileGroupRequest downloadFileGroupRequest) {
+ LogUtil.d("%s: downloadFileGroupWithForegroundService start.", TAG);
+ if (!foregroundDownloadServiceClassOptional.isPresent()) {
+ return immediateFailedFuture(
+ new IllegalArgumentException(
+ "downloadFileGroupWithForegroundService: ForegroundDownloadService is"
+ + " not"
+ + " provided!"));
+ }
- // Remove future from map
- ListenableFuture<Void> unused = downloadFutureMap.remove(downloadKey.toString());
- }
- },
- sequentialControlExecutor);
-
- return transformFuture;
- }
-
- @Override
- public ListenableFuture<Void> downloadFileWithForegroundService(
- SingleFileDownloadRequest singleFileDownloadRequest) {
- return singleFileDownloader.downloadWithForegroundService(
- MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest));
- }
-
- @Override
- public ListenableFuture<ClientFileGroup> downloadFileGroupWithForegroundService(
- DownloadFileGroupRequest downloadFileGroupRequest) {
- LogUtil.d("%s: downloadFileGroupWithForegroundService start.", TAG);
- if (!foregroundDownloadServiceClassOptional.isPresent()) {
- return immediateFailedFuture(
- new IllegalArgumentException(
- "downloadFileGroupWithForegroundService: ForegroundDownloadService is not"
- + " provided!"));
- }
+ if (!downloadMonitorOptional.isPresent()) {
+ return immediateFailedFuture(
+ DownloadException.builder()
+ .setDownloadResultCode(
+ DownloadResultCode.DOWNLOAD_MONITOR_NOT_PROVIDED_ERROR)
+ .setMessage(
+ "downloadFileGroupWithForegroundService: Download Monitor is "
+ + "not provided!")
+ .build());
+ }
- if (!downloadMonitorOptional.isPresent()) {
- return immediateFailedFuture(
- DownloadException.builder()
- .setDownloadResultCode(DownloadResultCode.DOWNLOAD_MONITOR_NOT_PROVIDED_ERROR)
- .setMessage(
- "downloadFileGroupWithForegroundService: Download Monitor is not provided!")
- .build());
+ // Submit the call to sequentialControlExecutor, but don't use futureSerializer. This will
+ // ensure that multiple calls are enqueued to the executor in a FIFO order, but these calls
+ // won't block each other when the download is in progress.
+ return PropagatedFutures.submitAsync(
+ () ->
+ PropagatedFutures.transformAsync(
+ // Check if requested file group has already been downloaded
+ getDownloadGroupState(downloadFileGroupRequest),
+ downloadGroupState -> {
+ switch (downloadGroupState.getKind()) {
+ case IN_PROGRESS_FUTURE:
+ // If the file group download is in progress, return
+ // that future immediately
+ return downloadGroupState.inProgressFuture();
+ case DOWNLOADED_GROUP:
+ // If the file group is already downloaded, return
+ // that immediately
+ return immediateFuture(
+ downloadGroupState.downloadedGroup());
+ case PENDING_GROUP:
+ return downloadPendingFileGroupWithForegroundService(
+ downloadFileGroupRequest,
+ downloadGroupState.pendingGroup());
+ }
+ throw new AssertionError(
+ String.format(
+ "received unsupported DownloadGroupState kind"
+ + " %s",
+ downloadGroupState.getKind()));
+ },
+ sequentialControlExecutor),
+ sequentialControlExecutor);
}
- // Submit the call to sequentialControlExecutor, but don't use futureSerializer. This will
- // ensure that multiple calls are enqueued to the executor in a FIFO order, but these calls
- // won't block each other when the download is in progress.
- return PropagatedFutures.submitAsync(
- () ->
- PropagatedFutures.transformAsync(
- // Check if requested file group has already been downloaded
- getDownloadGroupState(downloadFileGroupRequest),
- downloadGroupState -> {
- switch (downloadGroupState.getKind()) {
- case IN_PROGRESS_FUTURE:
- // If the file group download is in progress, return that future immediately
- return downloadGroupState.inProgressFuture();
- case DOWNLOADED_GROUP:
- // If the file group is already downloaded, return that immediately
- return immediateFuture(downloadGroupState.downloadedGroup());
- case PENDING_GROUP:
- return downloadPendingFileGroupWithForegroundService(
- downloadFileGroupRequest, downloadGroupState.pendingGroup());
- }
- throw new AssertionError(
- String.format(
- "received unsupported DownloadGroupState kind %s",
- downloadGroupState.getKind()));
+ /**
+ * Helper method to download a file group in the foreground after it has been confirmed to be
+ * pending.
+ */
+ private ListenableFuture<ClientFileGroup> downloadPendingFileGroupWithForegroundService(
+ DownloadFileGroupRequest downloadFileGroupRequest, DataFileGroupInternal pendingGroup) {
+ // It's OK to recreate the NotificationChannel since it can also be used to restore a
+ // deleted channel and to update an existing channel's name, description, group, and/or
+ // importance.
+ NotificationUtil.createNotificationChannel(context);
+
+ String groupName = downloadFileGroupRequest.groupName();
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(
+ context.getPackageName());
+
+ if (downloadFileGroupRequest.accountOptional().isPresent()) {
+ groupKeyBuilder.setAccount(
+ AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
+ }
+ if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
+ groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
+ }
+
+ GroupKey groupKey = groupKeyBuilder.build();
+ ForegroundDownloadKey foregroundDownloadKey =
+ ForegroundDownloadKey.ofFileGroup(
+ groupName,
+ downloadFileGroupRequest.accountOptional(),
+ downloadFileGroupRequest.variantIdOptional());
+
+ DownloadListener downloadListenerWithNotification =
+ createDownloadListenerWithNotification(downloadFileGroupRequest, pendingGroup);
+ // The downloadMonitor will trigger the DownloadListener.
+ downloadMonitorOptional
+ .get()
+ .addDownloadListener(
+ downloadFileGroupRequest.groupName(), downloadListenerWithNotification);
+
+ Optional<DownloadConditions> downloadConditions;
+ try {
+ downloadConditions =
+ downloadFileGroupRequest.downloadConditionsOptional().isPresent()
+ ? Optional.of(
+ ProtoConversionUtil.convert(
+ downloadFileGroupRequest.downloadConditionsOptional().get()))
+ : Optional.absent();
+ } catch (InvalidProtocolBufferException e) {
+ return immediateFailedFuture(e);
+ }
+
+ // Create a ListenableFutureTask to delay starting the downloadFuture until we can add the
+ // future to our map.
+ ListenableFutureTask<Void> startTask = ListenableFutureTask.create(() -> null);
+ PropagatedFluentFuture<ClientFileGroup> downloadFileGroupFuture =
+ PropagatedFluentFuture.from(startTask)
+ .transformAsync(
+ unused ->
+ mobileDataDownloadManager.downloadFileGroup(
+ groupKey, downloadConditions,
+ customFileGroupValidator),
+ sequentialControlExecutor)
+ .transformAsync(
+ dataFileGroup ->
+ createClientFileGroup(
+ dataFileGroup,
+ downloadFileGroupRequest.accountOptional().isPresent()
+ ? AccountUtil.serialize(
+ downloadFileGroupRequest.accountOptional().get())
+ : null,
+ ClientFileGroup.Status.DOWNLOADED,
+ downloadFileGroupRequest.preserveZipDirectories(),
+ downloadFileGroupRequest.verifyIsolatedStructure(),
+ mobileDataDownloadManager,
+ sequentialControlExecutor,
+ fileStorage),
+ sequentialControlExecutor)
+ .transform(Preconditions::checkNotNull, sequentialControlExecutor);
+
+ ListenableFuture<ClientFileGroup> transformFuture =
+ PropagatedFutures.transformAsync(
+ foregroundDownloadFutureMap.add(
+ foregroundDownloadKey.toString(), downloadFileGroupFuture),
+ unused -> {
+ // Now that the download future is added, start the task and return
+ // the future
+ startTask.run();
+ return downloadFileGroupFuture;
+ },
+ sequentialControlExecutor);
+
+ PropagatedFutures.addCallback(
+ transformFuture,
+ new FutureCallback<ClientFileGroup>() {
+ @Override
+ public void onSuccess(ClientFileGroup clientFileGroup) {
+ // Currently the MobStore monitor does not support onSuccess so we have
+ // to add
+ // callback to the download future here.
+ try {
+ downloadListenerWithNotification.onComplete(clientFileGroup);
+ } catch (Exception e) {
+ LogUtil.w(
+ e,
+ "%s: Listener onComplete failed for group %s",
+ TAG,
+ clientFileGroup.getGroupName());
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ // Currently the MobStore monitor does not support onFailure so we have
+ // to add
+ // callback to the download future here.
+ downloadListenerWithNotification.onFailure(t);
+ }
},
- sequentialControlExecutor),
- sequentialControlExecutor);
- }
-
- /**
- * Helper method to download a file group in the foreground after it has been confirmed to be
- * pending.
- */
- private ListenableFuture<ClientFileGroup> downloadPendingFileGroupWithForegroundService(
- DownloadFileGroupRequest downloadFileGroupRequest, DataFileGroupInternal pendingGroup) {
- // It's OK to recreate the NotificationChannel since it can also be used to restore a
- // deleted channel and to update an existing channel's name, description, group, and/or
- // importance.
- NotificationUtil.createNotificationChannel(context);
-
- String groupName = downloadFileGroupRequest.groupName();
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(context.getPackageName());
-
- if (downloadFileGroupRequest.accountOptional().isPresent()) {
- groupKeyBuilder.setAccount(
- AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
- }
- if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
- groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
- }
+ sequentialControlExecutor);
- GroupKey groupKey = groupKeyBuilder.build();
- ForegroundDownloadKey foregroundDownloadKey =
- ForegroundDownloadKey.ofFileGroup(
- groupName,
- downloadFileGroupRequest.accountOptional(),
- downloadFileGroupRequest.variantIdOptional());
-
- DownloadListener downloadListenerWithNotification =
- createDownloadListenerWithNotification(downloadFileGroupRequest, pendingGroup);
- // The downloadMonitor will trigger the DownloadListener.
- downloadMonitorOptional
- .get()
- .addDownloadListener(
- downloadFileGroupRequest.groupName(), downloadListenerWithNotification);
-
- Optional<DownloadConditions> downloadConditions;
- try {
- downloadConditions =
- downloadFileGroupRequest.downloadConditionsOptional().isPresent()
- ? Optional.of(
- ProtoConversionUtil.convert(
- downloadFileGroupRequest.downloadConditionsOptional().get()))
- : Optional.absent();
- } catch (InvalidProtocolBufferException e) {
- return immediateFailedFuture(e);
+ return transformFuture;
}
- // Create a ListenableFutureTask to delay starting the downloadFuture until we can add the
- // future to our map.
- ListenableFutureTask<Void> startTask = ListenableFutureTask.create(() -> null);
- PropagatedFluentFuture<ClientFileGroup> downloadFileGroupFuture =
- PropagatedFluentFuture.from(startTask)
- .transformAsync(
- unused ->
- mobileDataDownloadManager.downloadFileGroup(
- groupKey, downloadConditions, customFileGroupValidator),
- sequentialControlExecutor)
- .transformAsync(
- dataFileGroup ->
- createClientFileGroup(
- dataFileGroup,
- downloadFileGroupRequest.accountOptional().isPresent()
- ? AccountUtil.serialize(
- downloadFileGroupRequest.accountOptional().get())
- : null,
- ClientFileGroup.Status.DOWNLOADED,
- downloadFileGroupRequest.preserveZipDirectories(),
- downloadFileGroupRequest.verifyIsolatedStructure(),
- mobileDataDownloadManager,
- sequentialControlExecutor,
- fileStorage),
- sequentialControlExecutor)
- .transform(Preconditions::checkNotNull, sequentialControlExecutor);
-
- ListenableFuture<ClientFileGroup> transformFuture =
- PropagatedFutures.transformAsync(
- foregroundDownloadFutureMap.add(
- foregroundDownloadKey.toString(), downloadFileGroupFuture),
- unused -> {
- // Now that the download future is added, start the task and return the future
- startTask.run();
- return downloadFileGroupFuture;
- },
- sequentialControlExecutor);
-
- PropagatedFutures.addCallback(
- transformFuture,
- new FutureCallback<ClientFileGroup>() {
- @Override
- public void onSuccess(ClientFileGroup clientFileGroup) {
- // Currently the MobStore monitor does not support onSuccess so we have to add
- // callback to the download future here.
- try {
- downloadListenerWithNotification.onComplete(clientFileGroup);
- } catch (Exception e) {
- LogUtil.w(
- e,
- "%s: Listener onComplete failed for group %s",
- TAG,
- clientFileGroup.getGroupName());
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Currently the MobStore monitor does not support onFailure so we have to add
- // callback to the download future here.
- downloadListenerWithNotification.onFailure(t);
- }
- },
- sequentialControlExecutor);
-
- return transformFuture;
- }
-
- /** Helper method to return a {@link DownloadGroupState} for the given request. */
- private ListenableFuture<DownloadGroupState> getDownloadGroupState(
- DownloadFileGroupRequest downloadFileGroupRequest) {
- ForegroundDownloadKey foregroundDownloadKey =
- ForegroundDownloadKey.ofFileGroup(
- downloadFileGroupRequest.groupName(),
- downloadFileGroupRequest.accountOptional(),
- downloadFileGroupRequest.variantIdOptional());
-
- String groupName = downloadFileGroupRequest.groupName();
- GroupKey.Builder groupKeyBuilder =
- GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(context.getPackageName());
-
- if (downloadFileGroupRequest.accountOptional().isPresent()) {
- groupKeyBuilder.setAccount(
- AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
- }
+ /** Helper method to return a {@link DownloadGroupState} for the given request. */
+ private ListenableFuture<DownloadGroupState> getDownloadGroupState(
+ DownloadFileGroupRequest downloadFileGroupRequest) {
+ ForegroundDownloadKey foregroundDownloadKey =
+ ForegroundDownloadKey.ofFileGroup(
+ downloadFileGroupRequest.groupName(),
+ downloadFileGroupRequest.accountOptional(),
+ downloadFileGroupRequest.variantIdOptional());
+
+ String groupName = downloadFileGroupRequest.groupName();
+ GroupKey.Builder groupKeyBuilder =
+ GroupKey.newBuilder().setGroupName(groupName).setOwnerPackage(
+ context.getPackageName());
+
+ if (downloadFileGroupRequest.accountOptional().isPresent()) {
+ groupKeyBuilder.setAccount(
+ AccountUtil.serialize(downloadFileGroupRequest.accountOptional().get()));
+ }
+
+ if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
+ groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
+ }
- if (downloadFileGroupRequest.variantIdOptional().isPresent()) {
- groupKeyBuilder.setVariantId(downloadFileGroupRequest.variantIdOptional().get());
+ boolean isDownloadListenerPresent = downloadFileGroupRequest.listenerOptional().isPresent();
+ GroupKey groupKey = groupKeyBuilder.build();
+
+ return futureSerializer.submitAsync(
+ () -> {
+ ListenableFuture<Optional<ListenableFuture<ClientFileGroup>>>
+ foregroundDownloadFutureOptional =
+ foregroundDownloadFutureMap.get(foregroundDownloadKey.toString());
+ ListenableFuture<Optional<ListenableFuture<ClientFileGroup>>>
+ backgroundDownloadFutureOptional =
+ downloadFutureMap.get(foregroundDownloadKey.toString());
+
+ return PropagatedFutures.whenAllSucceed(
+ foregroundDownloadFutureOptional,
+ backgroundDownloadFutureOptional)
+ .callAsync(
+ () -> {
+ if (getDone(foregroundDownloadFutureOptional).isPresent()) {
+ return immediateFuture(
+ DownloadGroupState.ofInProgressFuture(
+ getDone(foregroundDownloadFutureOptional).get()));
+ } else if (getDone(
+ backgroundDownloadFutureOptional).isPresent()) {
+ return immediateFuture(
+ DownloadGroupState.ofInProgressFuture(
+ getDone(backgroundDownloadFutureOptional).get()));
+ }
+
+ // Get pending and downloaded versions to tell if we
+ // should return downloaded
+ // version early
+ ListenableFuture<GroupPair> fileGroupVersionsFuture =
+ PropagatedFutures.transformAsync(
+ mobileDataDownloadManager.getFileGroup(
+ groupKey, /* downloaded= */ false),
+ pendingDataFileGroup ->
+ PropagatedFutures.transform(
+ mobileDataDownloadManager.getFileGroup(
+ groupKey, /*
+ downloaded= */
+ true),
+ downloadedDataFileGroup ->
+ GroupPair.create(
+ pendingDataFileGroup,
+ downloadedDataFileGroup),
+ sequentialControlExecutor),
+ sequentialControlExecutor);
+
+ return PropagatedFutures.transformAsync(
+ fileGroupVersionsFuture,
+ fileGroupVersionsPair -> {
+ // if pending version is not null, return
+ // pending version
+ if (fileGroupVersionsPair.pendingGroup()
+ != null) {
+ return immediateFuture(
+ DownloadGroupState.ofPendingGroup(
+ checkNotNull(
+ fileGroupVersionsPair.pendingGroup())));
+ }
+ // If both groups are null, return group not
+ // found failure
+ if (fileGroupVersionsPair.downloadedGroup()
+ == null) {
+ // TODO(b/174808410): Add Logging
+ // file group is not pending nor
+ // downloaded -- return failure.
+ DownloadException failure =
+ DownloadException.builder()
+ .setDownloadResultCode(
+ DownloadResultCode.GROUP_NOT_FOUND_ERROR)
+ .setMessage(
+ "Nothing to "
+ + "download for "
+ + "file group: "
+ + groupKey.getGroupName())
+ .build();
+ if (isDownloadListenerPresent) {
+ downloadFileGroupRequest.listenerOptional().get().onFailure(
+ failure);
+ }
+ return immediateFailedFuture(failure);
+ }
+
+ DataFileGroupInternal downloadedDataFileGroup =
+ checkNotNull(
+ fileGroupVersionsPair.downloadedGroup());
+
+ // Notify download listener (if present) that
+ // file group has been
+ // downloaded.
+ if (isDownloadListenerPresent) {
+ downloadMonitorOptional
+ .get()
+ .addDownloadListener(
+ downloadFileGroupRequest.groupName(),
+ downloadFileGroupRequest.listenerOptional().get());
+ }
+ PropagatedFluentFuture<ClientFileGroup>
+ transformFuture =
+ PropagatedFluentFuture.from(
+ createClientFileGroup(
+ downloadedDataFileGroup,
+ downloadFileGroupRequest.accountOptional().isPresent()
+ ?
+ AccountUtil.serialize(
+ downloadFileGroupRequest.accountOptional().get())
+ : null,
+ ClientFileGroup.Status.DOWNLOADED,
+ downloadFileGroupRequest.preserveZipDirectories(),
+ downloadFileGroupRequest.verifyIsolatedStructure(),
+ mobileDataDownloadManager,
+ sequentialControlExecutor,
+ fileStorage))
+ .transform(
+ Preconditions::checkNotNull,
+ sequentialControlExecutor)
+ .transform(
+ clientFileGroup -> {
+ if (isDownloadListenerPresent) {
+ try {
+ downloadFileGroupRequest
+ .listenerOptional()
+ .get()
+ .onComplete(
+ clientFileGroup);
+ } catch (
+ Exception e) {
+ LogUtil.w(
+ e,
+ "%s: Listener onComplete failed for group %s",
+ TAG,
+ clientFileGroup.getGroupName());
+ }
+ downloadMonitorOptional
+ .get()
+ .removeDownloadListener(
+ groupName);
+ }
+ return clientFileGroup;
+ },
+ sequentialControlExecutor);
+ transformFuture.addCallback(
+ new FutureCallback<ClientFileGroup>() {
+ @Override
+ public void onSuccess(
+ ClientFileGroup result) {
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ if (isDownloadListenerPresent) {
+ downloadMonitorOptional.get().removeDownloadListener(
+ groupName);
+ }
+ }
+ },
+ sequentialControlExecutor);
+
+ // Use directExecutor here since we are performing a trivial operation.
+ return transformFuture.transform(
+ DownloadGroupState::ofDownloadedGroup,
+ directExecutor());
+ },
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
+ },
+ sequentialControlExecutor);
}
- boolean isDownloadListenerPresent = downloadFileGroupRequest.listenerOptional().isPresent();
- GroupKey groupKey = groupKeyBuilder.build();
-
- return futureSerializer.submitAsync(
- () -> {
- ListenableFuture<Optional<ListenableFuture<ClientFileGroup>>>
- foregroundDownloadFutureOptional =
- foregroundDownloadFutureMap.get(foregroundDownloadKey.toString());
- ListenableFuture<Optional<ListenableFuture<ClientFileGroup>>>
- backgroundDownloadFutureOptional =
- downloadFutureMap.get(foregroundDownloadKey.toString());
-
- return PropagatedFutures.whenAllSucceed(
- foregroundDownloadFutureOptional, backgroundDownloadFutureOptional)
- .callAsync(
- () -> {
- if (getDone(foregroundDownloadFutureOptional).isPresent()) {
- return immediateFuture(
- DownloadGroupState.ofInProgressFuture(
- getDone(foregroundDownloadFutureOptional).get()));
- } else if (getDone(backgroundDownloadFutureOptional).isPresent()) {
- return immediateFuture(
- DownloadGroupState.ofInProgressFuture(
- getDone(backgroundDownloadFutureOptional).get()));
- }
+ private DownloadListener createDownloadListenerWithNotification(
+ DownloadFileGroupRequest downloadRequest, DataFileGroupInternal fileGroup) {
+
+ String networkPausedMessage = getNetworkPausedMessage(downloadRequest, fileGroup);
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ ForegroundDownloadKey foregroundDownloadKey =
+ ForegroundDownloadKey.ofFileGroup(
+ downloadRequest.groupName(),
+ downloadRequest.accountOptional(),
+ downloadRequest.variantIdOptional());
+
+ NotificationCompat.Builder notification =
+ NotificationUtil.createNotificationBuilder(
+ context,
+ downloadRequest.groupSizeBytes(),
+ downloadRequest.contentTitleOptional().or(downloadRequest.groupName()),
+ downloadRequest.contentTextOptional().or(downloadRequest.groupName()));
+ int notificationKey = NotificationUtil.notificationKeyForKey(downloadRequest.groupName());
+
+ if (downloadRequest.showNotifications() == DownloadFileGroupRequest.ShowNotifications.ALL) {
+ NotificationUtil.createCancelAction(
+ context,
+ foregroundDownloadServiceClassOptional.get(),
+ foregroundDownloadKey.toString(),
+ notification,
+ notificationKey);
+
+ notificationManager.notify(notificationKey, notification.build());
+ }
- // Get pending and downloaded versions to tell if we should return downloaded
- // version early
- ListenableFuture<GroupPair> fileGroupVersionsFuture =
+ return new DownloadListener() {
+ @Override
+ public void onProgress(long currentSize) {
+ // TODO(b/229123693): return this future once DownloadListener has an async api.
+ // There can be a race condition, where onProgress can be called
+ // after onComplete or onFailure which removes the future and the notification.
+ // Check foregroundDownloadFutureMap first before updating notification.
+ ListenableFuture<?> unused =
PropagatedFutures.transformAsync(
- mobileDataDownloadManager.getFileGroup(
- groupKey, /* downloaded= */ false),
- pendingDataFileGroup ->
- PropagatedFutures.transform(
- mobileDataDownloadManager.getFileGroup(
- groupKey, /* downloaded= */ true),
- downloadedDataFileGroup ->
- GroupPair.create(
- pendingDataFileGroup, downloadedDataFileGroup),
- sequentialControlExecutor),
- sequentialControlExecutor);
+ foregroundDownloadFutureMap.containsKey(
+ foregroundDownloadKey.toString()),
+ futureInProgress -> {
+ if (futureInProgress
+ && downloadRequest.showNotifications()
+ == DownloadFileGroupRequest.ShowNotifications.ALL) {
+ notification
+ .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+ .setSmallIcon(android.R.drawable.stat_sys_download)
+ .setProgress(
+ downloadRequest.groupSizeBytes(),
+ (int) currentSize,
+ /* indeterminate= */
+ downloadRequest.groupSizeBytes() <= 0);
+ notificationManager.notify(notificationKey,
+ notification.build());
+ }
+ if (downloadRequest.listenerOptional().isPresent()) {
+ downloadRequest.listenerOptional().get().onProgress(
+ currentSize);
+ }
+ return immediateVoidFuture();
+ },
+ sequentialControlExecutor);
+ }
- return PropagatedFutures.transformAsync(
- fileGroupVersionsFuture,
- fileGroupVersionsPair -> {
- // if pending version is not null, return pending version
- if (fileGroupVersionsPair.pendingGroup() != null) {
- return immediateFuture(
- DownloadGroupState.ofPendingGroup(
- checkNotNull(fileGroupVersionsPair.pendingGroup())));
- }
- // If both groups are null, return group not found failure
- if (fileGroupVersionsPair.downloadedGroup() == null) {
- // TODO(b/174808410): Add Logging
- // file group is not pending nor downloaded -- return failure.
- DownloadException failure =
- DownloadException.builder()
- .setDownloadResultCode(DownloadResultCode.GROUP_NOT_FOUND_ERROR)
- .setMessage(
- "Nothing to download for file group: "
- + groupKey.getGroupName())
- .build();
- if (isDownloadListenerPresent) {
- downloadFileGroupRequest.listenerOptional().get().onFailure(failure);
- }
- return immediateFailedFuture(failure);
- }
-
- DataFileGroupInternal downloadedDataFileGroup =
- checkNotNull(fileGroupVersionsPair.downloadedGroup());
-
- // Notify download listener (if present) that file group has been
- // downloaded.
- if (isDownloadListenerPresent) {
- downloadMonitorOptional
- .get()
- .addDownloadListener(
- downloadFileGroupRequest.groupName(),
- downloadFileGroupRequest.listenerOptional().get());
- }
- PropagatedFluentFuture<ClientFileGroup> transformFuture =
- PropagatedFluentFuture.from(
- createClientFileGroup(
- downloadedDataFileGroup,
- downloadFileGroupRequest.accountOptional().isPresent()
- ? AccountUtil.serialize(
- downloadFileGroupRequest.accountOptional().get())
- : null,
- ClientFileGroup.Status.DOWNLOADED,
- downloadFileGroupRequest.preserveZipDirectories(),
- downloadFileGroupRequest.verifyIsolatedStructure(),
- mobileDataDownloadManager,
- sequentialControlExecutor,
- fileStorage))
- .transform(Preconditions::checkNotNull, sequentialControlExecutor)
- .transform(
- clientFileGroup -> {
- if (isDownloadListenerPresent) {
- try {
- downloadFileGroupRequest
- .listenerOptional()
- .get()
- .onComplete(clientFileGroup);
- } catch (Exception e) {
+ @Override
+ public void pausedForConnectivity() {
+ // TODO(b/229123693): return this future once DownloadListener has an async api.
+ // There can be a race condition, where pausedForConnectivity can be called
+ // after onComplete or onFailure which removes the future and the notification.
+ // Check foregroundDownloadFutureMap first before updating notification.
+ ListenableFuture<?> unused =
+ PropagatedFutures.transformAsync(
+ foregroundDownloadFutureMap.containsKey(
+ foregroundDownloadKey.toString()),
+ futureInProgress -> {
+ if (futureInProgress
+ && downloadRequest.showNotifications()
+ == DownloadFileGroupRequest.ShowNotifications.ALL) {
+ notification
+ .setCategory(NotificationCompat.CATEGORY_STATUS)
+ .setContentText(networkPausedMessage)
+ .setSmallIcon(android.R.drawable.stat_sys_download)
+ .setOngoing(true)
+ // hide progress bar.
+ .setProgress(0, 0, false);
+ notificationManager.notify(notificationKey,
+ notification.build());
+ }
+ if (downloadRequest.listenerOptional().isPresent()) {
+ downloadRequest.listenerOptional().get().pausedForConnectivity();
+ }
+ return immediateVoidFuture();
+ },
+ sequentialControlExecutor);
+ }
+
+ @Override
+ public void onComplete(ClientFileGroup clientFileGroup) {
+ // TODO(b/229123693): return this future once DownloadListener has an async api.
+ ListenableFuture<?> unused =
+ PropagatedFutures.submitAsync(
+ () -> {
+ boolean onCompleteFailed = false;
+ if (downloadRequest.listenerOptional().isPresent()) {
+ try {
+ downloadRequest.listenerOptional().get().onComplete(
+ clientFileGroup);
+ } catch (Exception e) {
LogUtil.w(
- e,
- "%s: Listener onComplete failed for group %s",
- TAG,
- clientFileGroup.getGroupName());
- }
- downloadMonitorOptional
- .get()
- .removeDownloadListener(groupName);
+ e,
+ "%s: Delegate onComplete failed for group %s, showing failure"
+ + " notification.",
+ TAG,
+ clientFileGroup.getGroupName());
+ onCompleteFailed = true;
}
- return clientFileGroup;
- },
- sequentialControlExecutor);
- transformFuture.addCallback(
- new FutureCallback<ClientFileGroup>() {
- @Override
- public void onSuccess(ClientFileGroup result) {}
-
- @Override
- public void onFailure(Throwable t) {
- if (isDownloadListenerPresent) {
- downloadMonitorOptional.get().removeDownloadListener(groupName);
- }
- }
- },
- sequentialControlExecutor);
+ }
+
+ // Clear the notification action.
+ if (downloadRequest.showNotifications()
+ == DownloadFileGroupRequest.ShowNotifications.ALL) {
+ notification.mActions.clear();
+
+ if (onCompleteFailed) {
+ // Show download failed in notification.
+ notification
+ .setCategory(NotificationCompat.CATEGORY_STATUS)
+ .setContentText(
+ NotificationUtil.getDownloadFailedMessage(
+ context))
+ .setOngoing(false)
+ .setSmallIcon(
+ android.R.drawable.stat_sys_warning)
+ // hide progress bar.
+ .setProgress(0, 0, false);
+
+ notificationManager.notify(notificationKey,
+ notification.build());
+ } else {
+ NotificationUtil.cancelNotificationForKey(
+ context, downloadRequest.groupName());
+ }
+ }
+
+ downloadMonitorOptional.get().removeDownloadListener(
+ downloadRequest.groupName());
+
+ return foregroundDownloadFutureMap.remove(
+ foregroundDownloadKey.toString());
+ },
+ sequentialControlExecutor);
+ }
- // Use directExecutor here since we are performing a trivial operation.
- return transformFuture.transform(
- DownloadGroupState::ofDownloadedGroup, directExecutor());
+ @Override
+ public void onFailure(Throwable t) {
+ // TODO(b/229123693): return this future once DownloadListener has an async api.
+ ListenableFuture<?> unused =
+ PropagatedFutures.submitAsync(
+ () -> {
+ if (downloadRequest.showNotifications()
+ == DownloadFileGroupRequest.ShowNotifications.ALL) {
+ // Clear the notification action.
+ notification.mActions.clear();
+
+ // Show download failed in notification.
+ notification
+ .setCategory(NotificationCompat.CATEGORY_STATUS)
+ .setContentText(
+ NotificationUtil.getDownloadFailedMessage(
+ context))
+ .setOngoing(false)
+ .setSmallIcon(android.R.drawable.stat_sys_warning)
+ // hide progress bar.
+ .setProgress(0, 0, false);
+
+ notificationManager.notify(notificationKey,
+ notification.build());
+ }
+
+ if (downloadRequest.listenerOptional().isPresent()) {
+ downloadRequest.listenerOptional().get().onFailure(t);
+ }
+ downloadMonitorOptional.get().removeDownloadListener(
+ downloadRequest.groupName());
+
+ return foregroundDownloadFutureMap.remove(
+ foregroundDownloadKey.toString());
+ },
+ sequentialControlExecutor);
+ }
+ };
+ }
+
+ // Helper method to get the correct network paused message
+ private String getNetworkPausedMessage(
+ DownloadFileGroupRequest downloadRequest, DataFileGroupInternal fileGroup) {
+ DeviceNetworkPolicy networkPolicyForDownload =
+ fileGroup.getDownloadConditions().getDeviceNetworkPolicy();
+ if (downloadRequest.downloadConditionsOptional().isPresent()) {
+ try {
+ networkPolicyForDownload =
+ ProtoConversionUtil.convert(
+ downloadRequest.downloadConditionsOptional().get())
+ .getDeviceNetworkPolicy();
+ } catch (InvalidProtocolBufferException unused) {
+ // Do nothing -- we will rely on the file group's network policy.
+ }
+ }
+
+ switch (networkPolicyForDownload) {
+ case DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK: // fallthrough
+ case DOWNLOAD_ONLY_ON_WIFI:
+ return NotificationUtil.getDownloadPausedWifiMessage(context);
+ default:
+ return NotificationUtil.getDownloadPausedMessage(context);
+ }
+ }
+
+ @Override
+ public void cancelForegroundDownload(String downloadKey) {
+ LogUtil.d("%s: CancelForegroundDownload for key = %s", TAG, downloadKey);
+ ListenableFuture<?> unused =
+ PropagatedFutures.transformAsync(
+ foregroundDownloadFutureMap.get(downloadKey),
+ downloadFuture -> {
+ if (downloadFuture.isPresent()) {
+ LogUtil.v(
+ "%s: CancelForegroundDownload future found for key = %s, cancelling...",
+ TAG, downloadKey);
+ downloadFuture.get().cancel(false);
+ }
+ return immediateVoidFuture();
},
sequentialControlExecutor);
- },
- sequentialControlExecutor);
- },
- sequentialControlExecutor);
- }
-
- private DownloadListener createDownloadListenerWithNotification(
- DownloadFileGroupRequest downloadRequest, DataFileGroupInternal fileGroup) {
-
- String networkPausedMessage = getNetworkPausedMessage(downloadRequest, fileGroup);
-
- NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
- ForegroundDownloadKey foregroundDownloadKey =
- ForegroundDownloadKey.ofFileGroup(
- downloadRequest.groupName(),
- downloadRequest.accountOptional(),
- downloadRequest.variantIdOptional());
-
- NotificationCompat.Builder notification =
- NotificationUtil.createNotificationBuilder(
- context,
- downloadRequest.groupSizeBytes(),
- downloadRequest.contentTitleOptional().or(downloadRequest.groupName()),
- downloadRequest.contentTextOptional().or(downloadRequest.groupName()));
- int notificationKey = NotificationUtil.notificationKeyForKey(downloadRequest.groupName());
-
- if (downloadRequest.showNotifications() == DownloadFileGroupRequest.ShowNotifications.ALL) {
- NotificationUtil.createCancelAction(
- context,
- foregroundDownloadServiceClassOptional.get(),
- foregroundDownloadKey.toString(),
- notification,
- notificationKey);
-
- notificationManager.notify(notificationKey, notification.build());
+ // Attempt cancel with internal MDD Lite instance in case it's a single file uri (cancel call is
+ // a noop if internal MDD Lite doesn't know about it).
+ singleFileDownloader.cancelForegroundDownload(downloadKey);
}
- return new DownloadListener() {
- @Override
- public void onProgress(long currentSize) {
- // TODO(b/229123693): return this future once DownloadListener has an async api.
- // There can be a race condition, where onProgress can be called
- // after onComplete or onFailure which removes the future and the notification.
- // Check foregroundDownloadFutureMap first before updating notification.
- ListenableFuture<?> unused =
- PropagatedFutures.transformAsync(
- foregroundDownloadFutureMap.containsKey(foregroundDownloadKey.toString()),
- futureInProgress -> {
- if (futureInProgress
- && downloadRequest.showNotifications()
- == DownloadFileGroupRequest.ShowNotifications.ALL) {
- notification
- .setCategory(NotificationCompat.CATEGORY_PROGRESS)
- .setSmallIcon(android.R.drawable.stat_sys_download)
- .setProgress(
- downloadRequest.groupSizeBytes(),
- (int) currentSize,
- /* indeterminate= */ downloadRequest.groupSizeBytes() <= 0);
- notificationManager.notify(notificationKey, notification.build());
- }
- if (downloadRequest.listenerOptional().isPresent()) {
- downloadRequest.listenerOptional().get().onProgress(currentSize);
- }
- return immediateVoidFuture();
+ @Override
+ public void schedulePeriodicTasks() {
+ schedulePeriodicTasksInternal(Optional.absent());
+ }
+
+ @Override
+ public ListenableFuture<Void> schedulePeriodicBackgroundTasks() {
+ return futureSerializer.submit(
+ () -> {
+ schedulePeriodicTasksInternal(/* constraintOverridesMap= */ Optional.absent());
+ return null;
},
sequentialControlExecutor);
- }
-
- @Override
- public void pausedForConnectivity() {
- // TODO(b/229123693): return this future once DownloadListener has an async api.
- // There can be a race condition, where pausedForConnectivity can be called
- // after onComplete or onFailure which removes the future and the notification.
- // Check foregroundDownloadFutureMap first before updating notification.
- ListenableFuture<?> unused =
- PropagatedFutures.transformAsync(
- foregroundDownloadFutureMap.containsKey(foregroundDownloadKey.toString()),
- futureInProgress -> {
- if (futureInProgress
- && downloadRequest.showNotifications()
- == DownloadFileGroupRequest.ShowNotifications.ALL) {
- notification
- .setCategory(NotificationCompat.CATEGORY_STATUS)
- .setContentText(networkPausedMessage)
- .setSmallIcon(android.R.drawable.stat_sys_download)
- .setOngoing(true)
- // hide progress bar.
- .setProgress(0, 0, false);
- notificationManager.notify(notificationKey, notification.build());
- }
- if (downloadRequest.listenerOptional().isPresent()) {
- downloadRequest.listenerOptional().get().pausedForConnectivity();
- }
- return immediateVoidFuture();
+ }
+
+ @Override
+ public ListenableFuture<Void> schedulePeriodicBackgroundTasks(
+ Optional<Map<String, ConstraintOverrides>> constraintOverridesMap) {
+ return futureSerializer.submit(
+ () -> {
+ schedulePeriodicTasksInternal(constraintOverridesMap);
+ return null;
},
sequentialControlExecutor);
- }
+ }
- @Override
- public void onComplete(ClientFileGroup clientFileGroup) {
- // TODO(b/229123693): return this future once DownloadListener has an async api.
- ListenableFuture<?> unused =
- PropagatedFutures.submitAsync(
- () -> {
- boolean onCompleteFailed = false;
- if (downloadRequest.listenerOptional().isPresent()) {
- try {
- downloadRequest.listenerOptional().get().onComplete(clientFileGroup);
- } catch (Exception e) {
- LogUtil.w(
- e,
- "%s: Delegate onComplete failed for group %s, showing failure"
- + " notification.",
- TAG,
- clientFileGroup.getGroupName());
- onCompleteFailed = true;
- }
- }
-
- // Clear the notification action.
- if (downloadRequest.showNotifications()
- == DownloadFileGroupRequest.ShowNotifications.ALL) {
- notification.mActions.clear();
-
- if (onCompleteFailed) {
- // Show download failed in notification.
- notification
- .setCategory(NotificationCompat.CATEGORY_STATUS)
- .setContentText(NotificationUtil.getDownloadFailedMessage(context))
- .setOngoing(false)
- .setSmallIcon(android.R.drawable.stat_sys_warning)
- // hide progress bar.
- .setProgress(0, 0, false);
-
- notificationManager.notify(notificationKey, notification.build());
- } else {
- NotificationUtil.cancelNotificationForKey(
- context, downloadRequest.groupName());
- }
- }
+ private void schedulePeriodicTasksInternal(
+ Optional<Map<String, ConstraintOverrides>> constraintOverridesMap) {
+ if (!taskSchedulerOptional.isPresent()) {
+ LogUtil.e(
+ "%s: Called schedulePeriodicTasksInternal when taskScheduler is not provided.",
+ TAG);
+ return;
+ }
- downloadMonitorOptional.get().removeDownloadListener(downloadRequest.groupName());
+ TaskScheduler taskScheduler = taskSchedulerOptional.get();
+
+ // Schedule task that runs on charging without any network, every 6 hours.
+ taskScheduler.schedulePeriodicTask(
+ TaskScheduler.CHARGING_PERIODIC_TASK,
+ flags.chargingGcmTaskPeriod(),
+ NetworkState.NETWORK_STATE_ANY,
+ getConstraintOverrides(constraintOverridesMap,
+ TaskScheduler.CHARGING_PERIODIC_TASK));
+
+ // Schedule maintenance task that runs on charging, once every day.
+ // This task should run even if mdd is disabled, to handle cleanup.
+ taskScheduler.schedulePeriodicTask(
+ TaskScheduler.MAINTENANCE_PERIODIC_TASK,
+ flags.maintenanceGcmTaskPeriod(),
+ NetworkState.NETWORK_STATE_ANY,
+ getConstraintOverrides(constraintOverridesMap,
+ TaskScheduler.MAINTENANCE_PERIODIC_TASK));
+
+ // Schedule task that runs on cellular+charging, every 6 hours.
+ taskScheduler.schedulePeriodicTask(
+ TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK,
+ flags.cellularChargingGcmTaskPeriod(),
+ NetworkState.NETWORK_STATE_CONNECTED,
+ getConstraintOverrides(
+ constraintOverridesMap, TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK));
+
+ // Schedule task that runs on wifi+charging, every 6 hours.
+ taskScheduler.schedulePeriodicTask(
+ TaskScheduler.WIFI_CHARGING_PERIODIC_TASK,
+ flags.wifiChargingGcmTaskPeriod(),
+ NetworkState.NETWORK_STATE_UNMETERED,
+ getConstraintOverrides(constraintOverridesMap,
+ TaskScheduler.WIFI_CHARGING_PERIODIC_TASK));
+ }
- return foregroundDownloadFutureMap.remove(foregroundDownloadKey.toString());
- },
- sequentialControlExecutor);
- }
+ private static Optional<ConstraintOverrides> getConstraintOverrides(
+ Optional<Map<String, ConstraintOverrides>> constraintOverridesMap,
+ String maintenancePeriodicTask) {
+ return constraintOverridesMap.isPresent()
+ ? Optional.fromNullable(constraintOverridesMap.get().get(maintenancePeriodicTask))
+ : Optional.absent();
+ }
- @Override
- public void onFailure(Throwable t) {
- // TODO(b/229123693): return this future once DownloadListener has an async api.
- ListenableFuture<?> unused =
- PropagatedFutures.submitAsync(
+ @Override
+ public ListenableFuture<Void> cancelPeriodicBackgroundTasks() {
+ return futureSerializer.submit(
() -> {
- if (downloadRequest.showNotifications()
- == DownloadFileGroupRequest.ShowNotifications.ALL) {
- // Clear the notification action.
- notification.mActions.clear();
-
- // Show download failed in notification.
- notification
- .setCategory(NotificationCompat.CATEGORY_STATUS)
- .setContentText(NotificationUtil.getDownloadFailedMessage(context))
- .setOngoing(false)
- .setSmallIcon(android.R.drawable.stat_sys_warning)
- // hide progress bar.
- .setProgress(0, 0, false);
-
- notificationManager.notify(notificationKey, notification.build());
- }
-
- if (downloadRequest.listenerOptional().isPresent()) {
- downloadRequest.listenerOptional().get().onFailure(t);
- }
- downloadMonitorOptional.get().removeDownloadListener(downloadRequest.groupName());
-
- return foregroundDownloadFutureMap.remove(foregroundDownloadKey.toString());
+ cancelPeriodicTasksInternal();
+ return null;
},
sequentialControlExecutor);
- }
- };
- }
-
- // Helper method to get the correct network paused message
- private String getNetworkPausedMessage(
- DownloadFileGroupRequest downloadRequest, DataFileGroupInternal fileGroup) {
- DeviceNetworkPolicy networkPolicyForDownload =
- fileGroup.getDownloadConditions().getDeviceNetworkPolicy();
- if (downloadRequest.downloadConditionsOptional().isPresent()) {
- try {
- networkPolicyForDownload =
- ProtoConversionUtil.convert(downloadRequest.downloadConditionsOptional().get())
- .getDeviceNetworkPolicy();
- } catch (InvalidProtocolBufferException unused) {
- // Do nothing -- we will rely on the file group's network policy.
- }
}
- switch (networkPolicyForDownload) {
- case DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK: // fallthrough
- case DOWNLOAD_ONLY_ON_WIFI:
- return NotificationUtil.getDownloadPausedWifiMessage(context);
- default:
- return NotificationUtil.getDownloadPausedMessage(context);
+ private void cancelPeriodicTasksInternal() {
+ if (!taskSchedulerOptional.isPresent()) {
+ LogUtil.w("%s: Called cancelPeriodicTasksInternal when taskScheduler is not provided.",
+ TAG);
+ return;
+ }
+
+ TaskScheduler taskScheduler = taskSchedulerOptional.get();
+
+ taskScheduler.cancelPeriodicTask(TaskScheduler.CHARGING_PERIODIC_TASK);
+ taskScheduler.cancelPeriodicTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK);
+ taskScheduler.cancelPeriodicTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK);
+ taskScheduler.cancelPeriodicTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK);
}
- }
-
- @Override
- public void cancelForegroundDownload(String downloadKey) {
- LogUtil.d("%s: CancelForegroundDownload for key = %s", TAG, downloadKey);
- ListenableFuture<?> unused =
- PropagatedFutures.transformAsync(
- foregroundDownloadFutureMap.get(downloadKey),
- downloadFuture -> {
- if (downloadFuture.isPresent()) {
- LogUtil.v(
- "%s: CancelForegroundDownload future found for key = %s, cancelling...",
- TAG, downloadKey);
- downloadFuture.get().cancel(false);
- }
- return immediateVoidFuture();
- },
- sequentialControlExecutor);
- // Attempt cancel with internal MDD Lite instance in case it's a single file uri (cancel call is
- // a noop if internal MDD Lite doesn't know about it).
- singleFileDownloader.cancelForegroundDownload(downloadKey);
- }
-
- @Override
- public void schedulePeriodicTasks() {
- schedulePeriodicTasksInternal(Optional.absent());
- }
-
- @Override
- public ListenableFuture<Void> schedulePeriodicBackgroundTasks() {
- return futureSerializer.submit(
- () -> {
- schedulePeriodicTasksInternal(/* constraintOverridesMap= */ Optional.absent());
- return null;
- },
- sequentialControlExecutor);
- }
-
- @Override
- public ListenableFuture<Void> schedulePeriodicBackgroundTasks(
- Optional<Map<String, ConstraintOverrides>> constraintOverridesMap) {
- return futureSerializer.submit(
- () -> {
- schedulePeriodicTasksInternal(constraintOverridesMap);
- return null;
- },
- sequentialControlExecutor);
- }
-
- private void schedulePeriodicTasksInternal(
- Optional<Map<String, ConstraintOverrides>> constraintOverridesMap) {
- if (!taskSchedulerOptional.isPresent()) {
- LogUtil.e(
- "%s: Called schedulePeriodicTasksInternal when taskScheduler is not provided.", TAG);
- return;
+
+ @Override
+ public ListenableFuture<Void> handleTask(String tag) {
+ // All work done here that touches metadata (MobileDataDownloadManager) should be serialized
+ // through sequentialControlExecutor.
+ switch (tag) {
+ case TaskScheduler.MAINTENANCE_PERIODIC_TASK:
+ return futureSerializer.submitAsync(
+ mobileDataDownloadManager::maintenance, sequentialControlExecutor);
+
+ case TaskScheduler.CHARGING_PERIODIC_TASK:
+ ListenableFuture<Void> refreshFileGroupsFuture = refreshFileGroups();
+ return PropagatedFutures.transformAsync(
+ refreshFileGroupsFuture,
+ propagateAsyncFunction(
+ v -> mobileDataDownloadManager.verifyAllPendingGroups(
+ customFileGroupValidator)),
+ sequentialControlExecutor);
+
+ case TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK:
+ return refreshAndDownload(false /*onWifi*/);
+
+ case TaskScheduler.WIFI_CHARGING_PERIODIC_TASK:
+ return refreshAndDownload(true /*onWifi*/);
+
+ default:
+ LogUtil.d("%s: gcm task doesn't belong to MDD", TAG);
+ return immediateFailedFuture(
+ new IllegalArgumentException(
+ "Unknown task tag sent to MDD.handleTask() " + tag));
+ }
+ }
+
+ private ListenableFuture<Void> refreshAndDownload(boolean onWifi) {
+ // We will do 2 passes to support 2-step downloads. In each step, we will refresh and then
+ // download.
+ return PropagatedFluentFuture.from(refreshFileGroups())
+ .transformAsync(
+ v ->
+ mobileDataDownloadManager.downloadAllPendingGroups(
+ onWifi, customFileGroupValidator),
+ sequentialControlExecutor)
+ .transformAsync(v -> refreshFileGroups(), sequentialControlExecutor)
+ .transformAsync(
+ v ->
+ mobileDataDownloadManager.downloadAllPendingGroups(
+ onWifi, customFileGroupValidator),
+ sequentialControlExecutor);
}
- TaskScheduler taskScheduler = taskSchedulerOptional.get();
-
- // Schedule task that runs on charging without any network, every 6 hours.
- taskScheduler.schedulePeriodicTask(
- TaskScheduler.CHARGING_PERIODIC_TASK,
- flags.chargingGcmTaskPeriod(),
- NetworkState.NETWORK_STATE_ANY,
- getConstraintOverrides(constraintOverridesMap, TaskScheduler.CHARGING_PERIODIC_TASK));
-
- // Schedule maintenance task that runs on charging, once every day.
- // This task should run even if mdd is disabled, to handle cleanup.
- taskScheduler.schedulePeriodicTask(
- TaskScheduler.MAINTENANCE_PERIODIC_TASK,
- flags.maintenanceGcmTaskPeriod(),
- NetworkState.NETWORK_STATE_ANY,
- getConstraintOverrides(constraintOverridesMap, TaskScheduler.MAINTENANCE_PERIODIC_TASK));
-
- // Schedule task that runs on cellular+charging, every 6 hours.
- taskScheduler.schedulePeriodicTask(
- TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK,
- flags.cellularChargingGcmTaskPeriod(),
- NetworkState.NETWORK_STATE_CONNECTED,
- getConstraintOverrides(
- constraintOverridesMap, TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK));
-
- // Schedule task that runs on wifi+charging, every 6 hours.
- taskScheduler.schedulePeriodicTask(
- TaskScheduler.WIFI_CHARGING_PERIODIC_TASK,
- flags.wifiChargingGcmTaskPeriod(),
- NetworkState.NETWORK_STATE_UNMETERED,
- getConstraintOverrides(constraintOverridesMap, TaskScheduler.WIFI_CHARGING_PERIODIC_TASK));
- }
-
- private static Optional<ConstraintOverrides> getConstraintOverrides(
- Optional<Map<String, ConstraintOverrides>> constraintOverridesMap,
- String maintenancePeriodicTask) {
- return constraintOverridesMap.isPresent()
- ? Optional.fromNullable(constraintOverridesMap.get().get(maintenancePeriodicTask))
- : Optional.absent();
- }
-
- @Override
- public ListenableFuture<Void> cancelPeriodicBackgroundTasks() {
- return futureSerializer.submit(
- () -> {
- cancelPeriodicTasksInternal();
- return null;
- },
- sequentialControlExecutor);
- }
-
- private void cancelPeriodicTasksInternal() {
- if (!taskSchedulerOptional.isPresent()) {
- LogUtil.w("%s: Called cancelPeriodicTasksInternal when taskScheduler is not provided.", TAG);
- return;
+ private ListenableFuture<Void> refreshFileGroups() {
+ List<ListenableFuture<Void>> refreshFutures = new ArrayList<>();
+ for (FileGroupPopulator fileGroupPopulator : fileGroupPopulatorList) {
+ refreshFutures.add(fileGroupPopulator.refreshFileGroups(this));
+ }
+
+ return PropagatedFutures.whenAllComplete(refreshFutures)
+ .call(() -> null, sequentialControlExecutor);
}
- TaskScheduler taskScheduler = taskSchedulerOptional.get();
+ @Override
+ public ListenableFuture<Void> maintenance() {
+ return handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK);
+ }
- taskScheduler.cancelPeriodicTask(TaskScheduler.CHARGING_PERIODIC_TASK);
- taskScheduler.cancelPeriodicTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK);
- taskScheduler.cancelPeriodicTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK);
- taskScheduler.cancelPeriodicTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK);
- }
+ @Override
+ public ListenableFuture<Void> collectGarbage() {
+ return futureSerializer.submitAsync(
+ mobileDataDownloadManager::removeExpiredGroupsAndFiles, sequentialControlExecutor);
+ }
- @Override
- public ListenableFuture<Void> handleTask(String tag) {
- // All work done here that touches metadata (MobileDataDownloadManager) should be serialized
- // through sequentialControlExecutor.
- switch (tag) {
- case TaskScheduler.MAINTENANCE_PERIODIC_TASK:
+ @Override
+ public ListenableFuture<Void> clear() {
return futureSerializer.submitAsync(
- mobileDataDownloadManager::maintenance, sequentialControlExecutor);
-
- case TaskScheduler.CHARGING_PERIODIC_TASK:
- ListenableFuture<Void> refreshFileGroupsFuture = refreshFileGroups();
- return PropagatedFutures.transformAsync(
- refreshFileGroupsFuture,
- propagateAsyncFunction(
- v -> mobileDataDownloadManager.verifyAllPendingGroups(customFileGroupValidator)),
- sequentialControlExecutor);
-
- case TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK:
- return refreshAndDownload(false /*onWifi*/);
-
- case TaskScheduler.WIFI_CHARGING_PERIODIC_TASK:
- return refreshAndDownload(true /*onWifi*/);
-
- default:
- LogUtil.d("%s: gcm task doesn't belong to MDD", TAG);
- return immediateFailedFuture(
- new IllegalArgumentException("Unknown task tag sent to MDD.handleTask() " + tag));
+ mobileDataDownloadManager::clear, sequentialControlExecutor);
}
- }
-
- private ListenableFuture<Void> refreshAndDownload(boolean onWifi) {
- // We will do 2 passes to support 2-step downloads. In each step, we will refresh and then
- // download.
- return PropagatedFluentFuture.from(refreshFileGroups())
- .transformAsync(
- v ->
- mobileDataDownloadManager.downloadAllPendingGroups(
- onWifi, customFileGroupValidator),
- sequentialControlExecutor)
- .transformAsync(v -> refreshFileGroups(), sequentialControlExecutor)
- .transformAsync(
- v ->
- mobileDataDownloadManager.downloadAllPendingGroups(
- onWifi, customFileGroupValidator),
- sequentialControlExecutor);
- }
-
- private ListenableFuture<Void> refreshFileGroups() {
- List<ListenableFuture<Void>> refreshFutures = new ArrayList<>();
- for (FileGroupPopulator fileGroupPopulator : fileGroupPopulatorList) {
- refreshFutures.add(fileGroupPopulator.refreshFileGroups(this));
+
+ // incompatible argument for parameter msg of e.
+ // incompatible types in return.
+ @Override
+ public String getDebugInfoAsString() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PrintWriter writer = new PrintWriter(out);
+ try {
+ // Okay to block here because this method is for debugging only.
+ mobileDataDownloadManager.dump(writer).get(DUMP_DEBUG_INFO_TIMEOUT, TimeUnit.SECONDS);
+ writer.println("==== MOBSTORE_DEBUG_INFO ====");
+ writer.print(fileStorage.getDebugInfo());
+ } catch (ExecutionException | TimeoutException e) {
+ String errString = String.format("%s: Couldn't get debug info: %s", TAG, e);
+ LogUtil.e(errString);
+ return errString;
+ } catch (InterruptedException e) {
+ // see <internal>
+ Thread.currentThread().interrupt();
+ String errString = String.format("%s: Couldn't get debug info: %s", TAG, e);
+ LogUtil.e(errString);
+ return errString;
+ }
+ writer.flush();
+ return out.toString();
}
- return PropagatedFutures.whenAllComplete(refreshFutures)
- .call(() -> null, sequentialControlExecutor);
- }
-
- @Override
- public ListenableFuture<Void> maintenance() {
- return handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK);
- }
-
- @Override
- public ListenableFuture<Void> collectGarbage() {
- return futureSerializer.submitAsync(
- mobileDataDownloadManager::removeExpiredGroupsAndFiles, sequentialControlExecutor);
- }
-
- @Override
- public ListenableFuture<Void> clear() {
- return futureSerializer.submitAsync(
- mobileDataDownloadManager::clear, sequentialControlExecutor);
- }
-
- // incompatible argument for parameter msg of e.
- // incompatible types in return.
- @Override
- public String getDebugInfoAsString() {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- PrintWriter writer = new PrintWriter(out);
- try {
- // Okay to block here because this method is for debugging only.
- mobileDataDownloadManager.dump(writer).get(DUMP_DEBUG_INFO_TIMEOUT, TimeUnit.SECONDS);
- writer.println("==== MOBSTORE_DEBUG_INFO ====");
- writer.print(fileStorage.getDebugInfo());
- } catch (ExecutionException | TimeoutException e) {
- String errString = String.format("%s: Couldn't get debug info: %s", TAG, e);
- LogUtil.e(errString);
- return errString;
- } catch (InterruptedException e) {
- // see <internal>
- Thread.currentThread().interrupt();
- String errString = String.format("%s: Couldn't get debug info: %s", TAG, e);
- LogUtil.e(errString);
- return errString;
+ @Override
+ public ListenableFuture<Void> reportUsage(UsageEvent usageEvent) {
+ eventLogger.logMddUsageEvent(createFileGroupDetails(usageEvent.clientFileGroup()), null);
+
+ return immediateVoidFuture();
+ }
+
+ private static DownloadFutureMap.StateChangeCallbacks createCallbacksForForegroundService(
+ Context context, Optional<Class<?>> foregroundDownloadServiceClassOptional) {
+ return new DownloadFutureMap.StateChangeCallbacks() {
+ @Override
+ public void onAdd(String key, int newSize) {
+ // Only start foreground service if this is the first future we are adding.
+ if (newSize == 1 && foregroundDownloadServiceClassOptional.isPresent()) {
+ NotificationUtil.startForegroundDownloadService(
+ context, foregroundDownloadServiceClassOptional.get(), key);
+ }
+ }
+
+ @Override
+ public void onRemove(String key, int newSize) {
+ // Only stop foreground service if there are no more futures remaining.
+ if (newSize == 0 && foregroundDownloadServiceClassOptional.isPresent()) {
+ NotificationUtil.stopForegroundDownloadService(
+ context, foregroundDownloadServiceClassOptional.get(), key);
+ }
+ }
+ };
}
- writer.flush();
- return out.toString();
- }
-
- @Override
- public ListenableFuture<Void> reportUsage(UsageEvent usageEvent) {
- eventLogger.logMddUsageEvent(createFileGroupDetails(usageEvent.clientFileGroup()), null);
-
- return immediateVoidFuture();
- }
-
- private static DownloadFutureMap.StateChangeCallbacks createCallbacksForForegroundService(
- Context context, Optional<Class<?>> foregroundDownloadServiceClassOptional) {
- return new DownloadFutureMap.StateChangeCallbacks() {
- @Override
- public void onAdd(String key, int newSize) {
- // Only start foreground service if this is the first future we are adding.
- if (newSize == 1 && foregroundDownloadServiceClassOptional.isPresent()) {
- NotificationUtil.startForegroundDownloadService(
- context, foregroundDownloadServiceClassOptional.get(), key);
- }
- }
-
- @Override
- public void onRemove(String key, int newSize) {
- // Only stop foreground service if there are no more futures remaining.
- if (newSize == 0 && foregroundDownloadServiceClassOptional.isPresent()) {
- NotificationUtil.stopForegroundDownloadService(
- context, foregroundDownloadServiceClassOptional.get(), key);
- }
- }
- };
- }
}