From 383a6204bd58851d00d57f77ff7d9c3cd5396230 Mon Sep 17 00:00:00 2001 From: Alex Buynytskyy Date: Mon, 17 Apr 2023 17:20:24 +0000 Subject: Fix for the errorprone build. Bug: 275409981 Test: m javac-check dist RUN_ERROR_PRONE=true Change-Id: I7bce7c6965db91a32c3e46456be2ad4210935a63 --- javatests/Android.bp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/javatests/Android.bp b/javatests/Android.bp index 49e4e97..dcd23c2 100644 --- a/javatests/Android.bp +++ b/javatests/Android.bp @@ -24,6 +24,9 @@ android_app { name: "MobileDataDownloadPlaceHolderApp", manifest: "com/google/android/libraries/mobiledatadownload/internal/AndroidManifest.xml", platform_apis: true, + libs: [ + "android.test.runner", + ] } android_robolectric_test { -- cgit v1.2.3 From 446e53dafdbbdf3041b396a1e989f1e988610020 Mon Sep 17 00:00:00 2001 From: Yichun Li Date: Mon, 24 Jul 2023 19:29:26 +0000 Subject: Update uiautomator references in external/mobile-data-download * Remove unused dependencies. * Replace "ub-uiautomator" (v2.2.0) with "androidx.test.uiautomator_uiautomator" (v2.3.0). Bug: 235842600 Test: presubmit Change-Id: Idd8baaf31aaba6a466fedba64ca3e50b30932e5e --- Android.bp | 2 +- .../mobiledatadownload/file/backends/BlobStoreBackendTest.java | 2 +- .../android/libraries/mobiledatadownload/internal/MddTestUtil.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Android.bp b/Android.bp index d8a8555..baa6ff7 100644 --- a/Android.bp +++ b/Android.bp @@ -62,7 +62,7 @@ android_library { ], libs: [ - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "androidx.test.ext.truth", "androidx.test.rules", "androidx.annotation_annotation", diff --git a/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java b/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java index c172205..7de85c2 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java @@ -24,10 +24,10 @@ import android.app.blob.BlobStoreManager; import android.content.Context; import android.net.Uri; import android.os.ParcelFileDescriptor; -import android.support.test.uiautomator.UiDevice; import android.util.Log; import android.util.Pair; import androidx.test.core.app.ApplicationProvider; +import androidx.test.uiautomator.UiDevice; import com.google.android.libraries.mobiledatadownload.file.common.LimitExceededException; import com.google.common.io.BaseEncoding; import com.google.common.io.ByteStreams; diff --git a/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java b/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java index e3976c7..433a7aa 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java @@ -23,8 +23,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; import android.os.Build.VERSION; -import android.support.test.uiautomator.UiDevice; import android.util.Log; +import androidx.test.uiautomator.UiDevice; import com.google.mobiledatadownload.internal.MetadataProto.BaseFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; -- cgit v1.2.3 From dae4e9d43309e08670dcbca39374d84fd5d2ec9b Mon Sep 17 00:00:00 2001 From: Yichun Li Date: Mon, 24 Jul 2023 19:29:26 +0000 Subject: Update uiautomator references in external/mobile-data-download * Remove unused dependencies. * Replace "ub-uiautomator" (v2.2.0) with "androidx.test.uiautomator_uiautomator" (v2.3.0). Bug: 235842600 Test: presubmit (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:446e53dafdbbdf3041b396a1e989f1e988610020) Merged-In: Idd8baaf31aaba6a466fedba64ca3e50b30932e5e Change-Id: Idd8baaf31aaba6a466fedba64ca3e50b30932e5e --- Android.bp | 2 +- .../mobiledatadownload/file/backends/BlobStoreBackendTest.java | 2 +- .../android/libraries/mobiledatadownload/internal/MddTestUtil.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Android.bp b/Android.bp index d52d055..a49ddf9 100644 --- a/Android.bp +++ b/Android.bp @@ -62,7 +62,7 @@ android_library { ], libs: [ - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "androidx.test.ext.truth", "androidx.test.rules", "androidx.annotation_annotation", diff --git a/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java b/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java index c172205..7de85c2 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/file/backends/BlobStoreBackendTest.java @@ -24,10 +24,10 @@ import android.app.blob.BlobStoreManager; import android.content.Context; import android.net.Uri; import android.os.ParcelFileDescriptor; -import android.support.test.uiautomator.UiDevice; import android.util.Log; import android.util.Pair; import androidx.test.core.app.ApplicationProvider; +import androidx.test.uiautomator.UiDevice; import com.google.android.libraries.mobiledatadownload.file.common.LimitExceededException; import com.google.common.io.BaseEncoding; import com.google.common.io.ByteStreams; diff --git a/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java b/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java index e3976c7..433a7aa 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/internal/MddTestUtil.java @@ -23,8 +23,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; import android.os.Build.VERSION; -import android.support.test.uiautomator.UiDevice; import android.util.Log; +import androidx.test.uiautomator.UiDevice; import com.google.mobiledatadownload.internal.MetadataProto.BaseFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; -- cgit v1.2.3 From bc7ec3ff9db70e8a11310d548541d75c75acbce7 Mon Sep 17 00:00:00 2001 From: Hao Liu Date: Tue, 3 Oct 2023 16:15:34 +0000 Subject: Code dump with latest updates. The updates include more MDD logging feature. Test: atest MddJobServiceTest & AdServicesMddIntegrationTests | MobileDataDownloadRoboTests Tested manually with MDD download job Bug: 302700021 Change-Id: I887b59953b617e5e9436d119a8353f39db64769b --- BUILD | 34 + .../android/libraries/mobiledatadownload/BUILD | 1 + .../mobiledatadownload/MobileDataDownload.java | 19 + .../mobiledatadownload/MobileDataDownloadImpl.java | 3431 +++++++++++--------- .../ReadDataFileGroupsByFilterRequest.java | 112 + .../downloader/MultiSchemeFileDownloader.java | 14 + .../internal/DataFileGroupValidator.java | 2 +- .../internal/ExceptionToMddResultMapper.java | 65 +- .../internal/ExpirationHandler.java | 2 +- .../internal/FileGroupManager.java | 17 +- .../internal/MobileDataDownloadManager.java | 10 +- .../internal/SharedFileManager.java | 4 +- .../DeltaFileDownloaderCallbackImpl.java | 2 +- .../internal/logging/DownloadStateLogger.java | 12 +- .../internal/logging/EventLogger.java | 205 +- .../internal/logging/LogUtil.java | 28 - .../internal/logging/MddEventLogger.java | 668 ++-- .../internal/logging/NetworkLogger.java | 32 +- .../internal/logging/NoOpEventLogger.java | 156 +- .../logging/SharedPreferencesLoggingState.java | 5 +- .../internal/logging/testing/FakeEventLogger.java | 278 +- .../mobiledatadownload/internal/proto/BUILD | 4 +- .../internal/proto/metadata.proto | 10 +- .../internal/util/FileGroupsMetadataUtil.java | 2 +- .../libraries/mobiledatadownload/lite/BUILD | 1 + .../mobiledatadownload/lite/DownloaderImpl.java | 2 +- .../mobiledatadownload/populator/proto/BUILD | 3 +- .../internal/logging/MddEventLoggerTest.java | 445 +-- proto/BUILD | 14 +- proto/client_config.proto | 3 +- proto/download_config.proto | 3 - proto/log_enums.proto | 109 +- proto/logs.proto | 84 +- proto/metadata.proto | 12 +- 34 files changed, 3233 insertions(+), 2556 deletions(-) create mode 100644 BUILD create mode 100644 java/com/google/android/libraries/mobiledatadownload/ReadDataFileGroupsByFilterRequest.java diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..9d0ae86 --- /dev/null +++ b/BUILD @@ -0,0 +1,34 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Description: +# Standalone Android library for downloading and managing files on device. + +load("//tools/build_defs/license:license.bzl", "license") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//visibility:public", + ], + licenses = ["notice"], +) + +license( + name = "license", + package_name = "mobiledatadownload", +) + +licenses(["notice"]) + +exports_files(["LICENSE"]) diff --git a/java/com/google/android/libraries/mobiledatadownload/BUILD b/java/com/google/android/libraries/mobiledatadownload/BUILD index ca39a4e..a462e75 100644 --- a/java/com/google/android/libraries/mobiledatadownload/BUILD +++ b/java/com/google/android/libraries/mobiledatadownload/BUILD @@ -36,6 +36,7 @@ android_library( "MobileDataDownload.java", "MobileDataDownloadImpl.java", "ReadDataFileGroupRequest.java", + "ReadDataFileGroupsByFilterRequest.java", "RemoveFileGroupRequest.java", "RemoveFileGroupsByFilterRequest.java", "RemoveFileGroupsByFilterResponse.java", diff --git a/java/com/google/android/libraries/mobiledatadownload/MobileDataDownload.java b/java/com/google/android/libraries/mobiledatadownload/MobileDataDownload.java index 1894e86..0db3be8 100644 --- a/java/com/google/android/libraries/mobiledatadownload/MobileDataDownload.java +++ b/java/com/google/android/libraries/mobiledatadownload/MobileDataDownload.java @@ -93,6 +93,25 @@ public interface MobileDataDownload { throw new UnsupportedOperationException(); } + /** + * Gets DataFileGroup definitions that were added to MDD by filter. This API cannot be used to + * access files. + * + *

Only present fields in {@link ReadDataFileGroupsByFilterRequest} will be used to perform the + * filtering. For example, if no account is specified in the filter, file groups won't be filtered + * based on account. + * + * @param readDataFileGroupsByFilterRequest The request to get multiple data file groups after + * filtering. + * @return The ListenableFuture that will resolve to a list of the requested data file groups. + * This ListenableFuture will resolve to all data file groups when {@code + * readDataFileGroupsByFilterRequest.includeAllGroups} is true. + */ + default ListenableFuture> readDataFileGroupsByFilter( + ReadDataFileGroupsByFilterRequest readDataFileGroupsByFilterRequest) { + throw new UnsupportedOperationException(); + } + /** * Returns the latest downloaded data that we have for the given group name. * 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 fileGroupPopulatorList; - private final Optional 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 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 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 (). 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 downloadMonitorOptional; - private final Optional> foregroundDownloadServiceClassOptional; - private final AsyncFunction customFileGroupValidator; - private final TimeSource timeSource; - - MobileDataDownloadImpl( - Context context, - EventLogger eventLogger, - MobileDataDownloadManager mobileDataDownloadManager, - Executor sequentialControlExecutor, - List fileGroupPopulatorList, - Optional taskSchedulerOptional, - SynchronousFileStorage fileStorage, - Optional downloadMonitorOptional, - Optional> foregroundDownloadServiceClassOptional, - Flags flags, - Downloader singleFileDownloader, - Optional 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 createCustomFileGroupValidator( - Optional 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 fileGroupPopulatorList; + private final Optional 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 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 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 (). 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 downloadMonitorOptional; + private final Optional> foregroundDownloadServiceClassOptional; + private final AsyncFunction customFileGroupValidator; + private final TimeSource timeSource; + + MobileDataDownloadImpl( + Context context, + EventLogger eventLogger, + MobileDataDownloadManager mobileDataDownloadManager, + Executor sequentialControlExecutor, + List fileGroupPopulatorList, + Optional taskSchedulerOptional, + SynchronousFileStorage fileStorage, + Optional downloadMonitorOptional, + Optional> foregroundDownloadServiceClassOptional, + Flags flags, + Downloader singleFileDownloader, + Optional 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 { - 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. - * - *

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. - * - *

TODO(b/143572409): Remove once addGroupForDownload is updated to return void. - * - * @see attachMddApiLogging - */ - private interface ResultCodeFromApiResultGetter { - 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 void attachMddApiLogging( - int apiName, - ListenableFuture resultFuture, - long startTimeNs, - DataDownloadFileGroupStats defaultFileGroupStats, - StatsFromApiResultCreator statsCreator, - ResultCodeFromApiResultGetter 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 createCustomFileGroupValidator( + Optional 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 addFileGroup(AddFileGroupRequest addFileGroupRequest) { - long startTimeNs = timeSource.elapsedRealtimeNanos(); - - ListenableFuture 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 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 { + 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. + * + *

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. + * + *

TODO(b/143572409): Remove once addGroupForDownload is updated to return void. + * + * @see attachMddApiLogging + */ + private interface ResultCodeFromApiResultGetter { + 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 void attachMddApiLogging( + MddLibApiName.Code apiName, + ListenableFuture resultFuture, + long startTimeNs, + DataDownloadFileGroupStats defaultFileGroupStats, + StatsFromApiResultCreator statsCreator, + ResultCodeFromApiResultGetter 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 addFileGroup(AddFileGroupRequest addFileGroupRequest) { + long startTimeNs = timeSource.elapsedRealtimeNanos(); + + ListenableFuture 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. - @Override - public ListenableFuture removeFileGroup(RemoveFileGroupRequest removeFileGroupRequest) { - return futureSerializer.submitAsync( - () -> { - GroupKey.Builder groupKeyBuilder = - GroupKey.newBuilder() - .setGroupName(removeFileGroupRequest.groupName()) - .setOwnerPackage(context.getPackageName()); - if (removeFileGroupRequest.accountOptional().isPresent()) { + + private ListenableFuture 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 removeFileGroupsByFilter( - RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest) { - return futureSerializer.submitAsync( - () -> - PropagatedFluentFuture.from(mobileDataDownloadManager.getAllFreshGroups()) - .transformAsync( - allFreshGroupKeyAndGroups -> { - ImmutableSet.Builder 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 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 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. + @Override + public ListenableFuture 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. - * - *

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 removeFileGroupsByFilter( + RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest) { + return futureSerializer.submitAsync( + () -> + PropagatedFluentFuture.from(mobileDataDownloadManager.getAllFreshGroups()) + .transformAsync( + allFreshGroupKeyAndGroups -> { + ImmutableSet.Builder + 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 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 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 getFileGroup(GetFileGroupRequest getFileGroupRequest) { - long startTimeNs = timeSource.elapsedRealtimeNanos(); - - ListenableFuture 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 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 accountOptional, Optional 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. + * + *

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 getFileGroup(GetFileGroupRequest getFileGroupRequest) { + long startTimeNs = timeSource.elapsedRealtimeNanos(); + + ListenableFuture 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 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 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 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> readDataFileGroupsByFilter( + ReadDataFileGroupsByFilterRequest request) { + return futureSerializer.submitAsync( + () -> + PropagatedFutures.transformAsync( + mobileDataDownloadManager.getAllFreshGroups(), + freshGroups -> { + ImmutableList filteredGroups = + filterGroups( + request.includeAllGroups(), + request.groupNameOptional(), + request.groupWithNoAccountOnly(), + request.accountOptional(), + request.downloadedOptional(), + freshGroups); + ImmutableList.Builder dataFileGroupsBuilder = + ImmutableList.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 accountOptional, Optional 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 dataFiles = dataFileGroup.getFileList(); - ListenableFuture 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 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 listAllClientFilesOfDirectory( - SynchronousFileStorage fileStorage, Uri dirUri, String rootDir) throws IOException { - List 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 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> getFileGroupsByFilter( - GetFileGroupsByFilterRequest getFileGroupsByFilterRequest) { - return futureSerializer.submitAsync( - () -> - PropagatedFutures.transformAsync( - mobileDataDownloadManager.getAllFreshGroups(), - allFreshGroupKeyAndGroups -> { - ListenableFuture> - clientFileGroupsBuilderFuture = - immediateFuture(ImmutableList.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 dataFiles = dataFileGroup.getFileList(); + ListenableFuture 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 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 listAllClientFilesOfDirectory( + SynchronousFileStorage fileStorage, Uri dirUri, String rootDir) throws IOException { + List 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 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> 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> + clientFileGroupsBuilderFuture = + immediateFuture( + ImmutableList.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 filterGroups( + boolean includeAllGroups, + Optional groupNameOptional, + boolean groupWithNoAccountOnly, + Optional accountOptional, + Optional downloadedOptional, + List allGroupKeyAndGroups) { + var builder = ImmutableList.builder(); + if (includeAllGroups) { + builder.addAll(allGroupKeyAndGroups); + return builder.build(); + } - ImmutableList.Builder 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 downloadFile(SingleFileDownloadRequest singleFileDownloadRequest) { - return singleFileDownloader.download( - MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest)); - } - - @Override - public ListenableFuture 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 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 groupNameOptional, + boolean groupWithNoAccountOnly, + Optional accountOptional, + Optional 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 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 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; - try { - downloadConditions = - downloadFileGroupRequest.downloadConditionsOptional().isPresent() - ? Optional.of( - ProtoConversionUtil.convert( - downloadFileGroupRequest.downloadConditionsOptional().get())) - : Optional.absent(); - } catch (InvalidProtocolBufferException e) { - return immediateFailedFuture(e); + @Override + public ListenableFuture 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 startTask = ListenableFutureTask.create(() -> null); - ListenableFuture 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 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 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 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 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; + 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 startTask = ListenableFutureTask.create(() -> null); + ListenableFuture 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 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 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() { + @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 unused = downloadFutureMap.remove( + downloadKey.toString()); } - } - return immediateFuture(clientFileGroup); }, sequentialControlExecutor); - PropagatedFutures.addCallback( - transformFuture, - new FutureCallback() { - @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 downloadFileWithForegroundService( + SingleFileDownloadRequest singleFileDownloadRequest) { + return singleFileDownloader.downloadWithForegroundService( + MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest)); + } - if (downloadMonitorOptional.isPresent()) { - downloadMonitorOptional.get().removeDownloadListener(groupName); - } - } + @Override + public ListenableFuture 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 unused = downloadFutureMap.remove(downloadKey.toString()); - } - }, - sequentialControlExecutor); - - return transformFuture; - } - - @Override - public ListenableFuture downloadFileWithForegroundService( - SingleFileDownloadRequest singleFileDownloadRequest) { - return singleFileDownloader.downloadWithForegroundService( - MddLiteConversionUtil.convertToDownloadRequest(singleFileDownloadRequest)); - } - - @Override - public ListenableFuture 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 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; + 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 startTask = ListenableFutureTask.create(() -> null); + PropagatedFluentFuture 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 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() { + @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 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; - 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 startTask = ListenableFutureTask.create(() -> null); - PropagatedFluentFuture 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 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() { - @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 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 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>> + foregroundDownloadFutureOptional = + foregroundDownloadFutureMap.get(foregroundDownloadKey.toString()); + ListenableFuture>> + 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 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 + 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() { + @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>> - foregroundDownloadFutureOptional = - foregroundDownloadFutureMap.get(foregroundDownloadKey.toString()); - ListenableFuture>> - 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 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 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() { - @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 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 schedulePeriodicBackgroundTasks( + Optional> 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> 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 getConstraintOverrides( + Optional> 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 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 schedulePeriodicBackgroundTasks() { - return futureSerializer.submit( - () -> { - schedulePeriodicTasksInternal(/* constraintOverridesMap= */ Optional.absent()); - return null; - }, - sequentialControlExecutor); - } - - @Override - public ListenableFuture schedulePeriodicBackgroundTasks( - Optional> constraintOverridesMap) { - return futureSerializer.submit( - () -> { - schedulePeriodicTasksInternal(constraintOverridesMap); - return null; - }, - sequentialControlExecutor); - } - - private void schedulePeriodicTasksInternal( - Optional> constraintOverridesMap) { - if (!taskSchedulerOptional.isPresent()) { - LogUtil.e( - "%s: Called schedulePeriodicTasksInternal when taskScheduler is not provided.", TAG); - return; + + @Override + public ListenableFuture 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 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 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 getConstraintOverrides( - Optional> constraintOverridesMap, - String maintenancePeriodicTask) { - return constraintOverridesMap.isPresent() - ? Optional.fromNullable(constraintOverridesMap.get().get(maintenancePeriodicTask)) - : Optional.absent(); - } - - @Override - public ListenableFuture 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 refreshFileGroups() { + List> 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 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 collectGarbage() { + return futureSerializer.submitAsync( + mobileDataDownloadManager::removeExpiredGroupsAndFiles, sequentialControlExecutor); + } - @Override - public ListenableFuture 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 clear() { return futureSerializer.submitAsync( - mobileDataDownloadManager::maintenance, sequentialControlExecutor); - - case TaskScheduler.CHARGING_PERIODIC_TASK: - ListenableFuture 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 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 refreshFileGroups() { - List> 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 + 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 maintenance() { - return handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK); - } - - @Override - public ListenableFuture collectGarbage() { - return futureSerializer.submitAsync( - mobileDataDownloadManager::removeExpiredGroupsAndFiles, sequentialControlExecutor); - } - - @Override - public ListenableFuture 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 - Thread.currentThread().interrupt(); - String errString = String.format("%s: Couldn't get debug info: %s", TAG, e); - LogUtil.e(errString); - return errString; + @Override + public ListenableFuture reportUsage(UsageEvent usageEvent) { + eventLogger.logMddUsageEvent(createFileGroupDetails(usageEvent.clientFileGroup()), null); + + return immediateVoidFuture(); + } + + private static DownloadFutureMap.StateChangeCallbacks createCallbacksForForegroundService( + Context context, Optional> 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 reportUsage(UsageEvent usageEvent) { - eventLogger.logMddUsageEvent(createFileGroupDetails(usageEvent.clientFileGroup()), null); - - return immediateVoidFuture(); - } - - private static DownloadFutureMap.StateChangeCallbacks createCallbacksForForegroundService( - Context context, Optional> 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); - } - } - }; - } } diff --git a/java/com/google/android/libraries/mobiledatadownload/ReadDataFileGroupsByFilterRequest.java b/java/com/google/android/libraries/mobiledatadownload/ReadDataFileGroupsByFilterRequest.java new file mode 100644 index 0000000..c73be16 --- /dev/null +++ b/java/com/google/android/libraries/mobiledatadownload/ReadDataFileGroupsByFilterRequest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.libraries.mobiledatadownload; + +import static com.google.common.base.Preconditions.checkArgument; + +import android.accounts.Account; +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; +import com.google.errorprone.annotations.Immutable; + +/** Request to get multiple data file groups after filtering. */ +@AutoValue +@AutoValue.CopyAnnotations +@Immutable +@SuppressWarnings("Immutable") +public abstract class ReadDataFileGroupsByFilterRequest { + ReadDataFileGroupsByFilterRequest() {} + + // If this value is set to true, groupName should not be set. + public abstract boolean includeAllGroups(); + + // If this value is set to true, only groups without account will be returned, and accountOptional + // should be absent. The default value is false. + public abstract boolean groupWithNoAccountOnly(); + + public abstract Optional groupNameOptional(); + + public abstract Optional accountOptional(); + + public abstract Optional downloadedOptional(); + + public static Builder newBuilder() { + return new AutoValue_ReadDataFileGroupsByFilterRequest.Builder() + .setIncludeAllGroups(false) + .setGroupWithNoAccountOnly(false); + } + + /** Builder for {@link ReadDataFileGroupsByFilterRequest}. */ + @AutoValue.Builder + public abstract static class Builder { + Builder() {} + + /** Sets the flag whether all groups are included. */ + public abstract Builder setIncludeAllGroups(boolean includeAllGroups); + + /** Sets the flag whether to only return account independent groups. */ + public abstract Builder setGroupWithNoAccountOnly(boolean groupWithNoAccountOnly); + + /** + * Sets the name of the file group, which is optional. When groupNameOptional is absent, caller + * must set the includeAllGroups. + */ + public abstract Builder setGroupNameOptional(Optional groupNameOptional); + + /** Sets the account that is associated with the file group, which is optional. */ + public abstract Builder setAccountOptional(Optional accountOptional); + + /** + * Use setDownloaded instead. + * + * @see setDownloaded. + */ + abstract Builder setDownloadedOptional(Optional downloadedOptional); + + /** + * Sets whether to include only downloaded or pending file groups. + * + * @param downloaded if set to true, only downloaded file groups returned. If set to false, only + * pending groups are included. If not set, all groups are returned. + */ + public Builder setDownloaded(Boolean downloaded) { + return setDownloadedOptional(Optional.of(downloaded)); + } + + abstract ReadDataFileGroupsByFilterRequest autoBuild(); + + public final ReadDataFileGroupsByFilterRequest build() { + ReadDataFileGroupsByFilterRequest readDataFileGroupsByFilterRequest = autoBuild(); + + if (readDataFileGroupsByFilterRequest.includeAllGroups()) { + checkArgument(!readDataFileGroupsByFilterRequest.groupNameOptional().isPresent()); + checkArgument(!readDataFileGroupsByFilterRequest.accountOptional().isPresent()); + checkArgument(!readDataFileGroupsByFilterRequest.groupWithNoAccountOnly()); + checkArgument(!readDataFileGroupsByFilterRequest.downloadedOptional().isPresent()); + } else { + checkArgument( + readDataFileGroupsByFilterRequest.groupNameOptional().isPresent(), + "Request must provide a group name or source to filter by"); + } + + if (readDataFileGroupsByFilterRequest.groupWithNoAccountOnly()) { + checkArgument(!readDataFileGroupsByFilterRequest.accountOptional().isPresent()); + } + + return readDataFileGroupsByFilterRequest; + } + } +} diff --git a/java/com/google/android/libraries/mobiledatadownload/downloader/MultiSchemeFileDownloader.java b/java/com/google/android/libraries/mobiledatadownload/downloader/MultiSchemeFileDownloader.java index 7dfc5b4..cd769df 100644 --- a/java/com/google/android/libraries/mobiledatadownload/downloader/MultiSchemeFileDownloader.java +++ b/java/com/google/android/libraries/mobiledatadownload/downloader/MultiSchemeFileDownloader.java @@ -61,6 +61,20 @@ public final class MultiSchemeFileDownloader implements FileDownloader { return new Builder(); } + /** Returns a Builder containing all registered FileDownloaders. */ + public Builder toBuilder() { + final Builder builder = new Builder(); + for (Map.Entry entry : schemeToDownloader.entrySet()) { + builder.addScheme(entry.getKey(), entry.getValue()); + } + return builder; + } + + /** Returns true if a FileDownloader is registered for the given scheme. */ + public boolean supportsScheme(String scheme) { + return schemeToDownloader.containsKey(scheme); + } + private MultiSchemeFileDownloader(Builder builder) { this.schemeToDownloader = ImmutableMap.copyOf(builder.schemeToDownloader); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/DataFileGroupValidator.java b/java/com/google/android/libraries/mobiledatadownload/internal/DataFileGroupValidator.java index f05e831..3d49157 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/DataFileGroupValidator.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/DataFileGroupValidator.java @@ -20,6 +20,7 @@ import com.google.android.libraries.mobiledatadownload.Flags; import com.google.android.libraries.mobiledatadownload.file.transforms.TransformProtos; import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil; +import com.google.mobiledatadownload.TransformProto.Transforms; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; @@ -27,7 +28,6 @@ import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInterna import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile; import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy; -import com.google.mobiledatadownload.TransformProto.Transforms; /** DataFileGroupValidator - validates the passed in DataFileGroup */ public class DataFileGroupValidator { diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/ExceptionToMddResultMapper.java b/java/com/google/android/libraries/mobiledatadownload/internal/ExceptionToMddResultMapper.java index f5fa536..44db96c 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/ExceptionToMddResultMapper.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/ExceptionToMddResultMapper.java @@ -16,6 +16,8 @@ package com.google.android.libraries.mobiledatadownload.internal; import com.google.android.libraries.mobiledatadownload.DownloadException; +import com.google.mobiledatadownload.LogEnumsProto.MddLibApiResult; + import java.io.IOException; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -27,39 +29,40 @@ import java.util.concurrent.ExecutionException; */ public final class ExceptionToMddResultMapper { - private ExceptionToMddResultMapper() {} + private ExceptionToMddResultMapper() { + } - /** - * Maps Exception to appropriate int for logging. - * - *

If t is an ExecutionException, then the cause (t.getCause()) is mapped. - */ - public static int map(Throwable t) { + /** + * Maps Exception to appropriate MddLibApiResult.Code for logging. + * + *

If t is an ExecutionException, then the cause (t.getCause()) is mapped. + */ + public static MddLibApiResult.Code map(Throwable t) { - Throwable cause; - if (t instanceof ExecutionException) { - cause = t.getCause(); - } else { - cause = t; - } + Throwable cause; + if (t instanceof ExecutionException) { + cause = t.getCause(); + } else { + cause = t; + } - if (cause instanceof CancellationException) { - return 0; - } else if (cause instanceof InterruptedException) { - return 0; - } else if (cause instanceof IOException) { - return 0; - } else if (cause instanceof IllegalStateException) { - return 0; - } else if (cause instanceof IllegalArgumentException) { - return 0; - } else if (cause instanceof UnsupportedOperationException) { - return 0; - } else if (cause instanceof DownloadException) { - return 0; - } + if (cause instanceof CancellationException) { + return MddLibApiResult.Code.RESULT_CANCELLED; + } else if (cause instanceof InterruptedException) { + return MddLibApiResult.Code.RESULT_INTERRUPTED; + } else if (cause instanceof IOException) { + return MddLibApiResult.Code.RESULT_IO_ERROR; + } else if (cause instanceof IllegalStateException) { + return MddLibApiResult.Code.RESULT_ILLEGAL_STATE; + } else if (cause instanceof IllegalArgumentException) { + return MddLibApiResult.Code.RESULT_ILLEGAL_ARGUMENT; + } else if (cause instanceof UnsupportedOperationException) { + return MddLibApiResult.Code.RESULT_UNSUPPORTED_OPERATION; + } else if (cause instanceof DownloadException) { + return MddLibApiResult.Code.RESULT_DOWNLOAD_ERROR; + } - // Capturing all other errors occurred during execution as unknown errors. - return 0; - } + // Capturing all other errors occurred during execution as unknown errors. + return MddLibApiResult.Code.RESULT_FAILURE; + } } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/ExpirationHandler.java b/java/com/google/android/libraries/mobiledatadownload/internal/ExpirationHandler.java index 7c0f698..adf0997 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/ExpirationHandler.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/ExpirationHandler.java @@ -35,11 +35,11 @@ import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUt import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; +import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; -import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/FileGroupManager.java b/java/com/google/android/libraries/mobiledatadownload/internal/FileGroupManager.java index 7cf3eb6..068523d 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/FileGroupManager.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/FileGroupManager.java @@ -73,6 +73,9 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; +import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; +import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import com.google.mobiledatadownload.internal.MetadataProto; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile.AndroidSharingType; @@ -86,9 +89,6 @@ import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; import com.google.mobiledatadownload.internal.MetadataProto.GroupKeyProperties; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; import com.google.mobiledatadownload.internal.MetadataProto.SharedFile; -import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; -import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; -import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import com.google.protobuf.Any; import java.io.IOException; import java.io.PrintWriter; @@ -2344,7 +2344,7 @@ public class FileGroupManager { } } ImmutableMap nonSideloadedKeyMap = - nonSideloadedKeyMapBuilder.build(); + nonSideloadedKeyMapBuilder.buildKeepingLast(); return PropagatedFluentFuture.from( sharedFileManager.getOnDeviceUris(ImmutableSet.copyOf(nonSideloadedKeyMap.values()))) @@ -2358,7 +2358,7 @@ public class FileGroupManager { onDeviceUriMap.put(keyMapEntry.getKey(), nonSideloadedUriMap.get(newFileKey)); } } - return onDeviceUriMap.build(); + return onDeviceUriMap.buildKeepingLast(); }, sequentialControlExecutor); } @@ -2378,7 +2378,7 @@ public class FileGroupManager { isolatedFileUrisBuilder.put( dataFile, FileGroupUtil.appendIsolatedFileUri(isolatedRootUri, dataFile)); } - return isolatedFileUrisBuilder.build(); + return isolatedFileUrisBuilder.buildKeepingLast(); } /** @@ -2426,7 +2426,7 @@ public class FileGroupManager { TAG, isolatedUri, onDeviceUri); } } - return verifiedUriMapBuilder.build(); + return verifiedUriMapBuilder.buildKeepingLast(); } /** @@ -2845,9 +2845,6 @@ public class FileGroupManager { if (!prevGroup.getAllowedReadersEnum().equals(newGroup.getAllowedReadersEnum())) { return Optional.of(0); } -// if (!prevGroup.getExperimentInfo().equals(newGroup.getExperimentInfo())) { -// return Optional.of(0); -// } return Optional.absent(); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/MobileDataDownloadManager.java b/java/com/google/android/libraries/mobiledatadownload/internal/MobileDataDownloadManager.java index b9496e2..002b896 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/MobileDataDownloadManager.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/MobileDataDownloadManager.java @@ -53,13 +53,13 @@ import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; +import com.google.mobiledatadownload.TransformProto.Transforms; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; -import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; -import com.google.mobiledatadownload.TransformProto.Transforms; import com.google.protobuf.Any; import java.io.IOException; import java.io.PrintWriter; @@ -337,7 +337,7 @@ public class MobileDataDownloadManager { // downloaded, pendingGroup must be non-null. DataFileGroupInternal group = checkNotNull(getDone(pendingGroupFuture)); eventLogger.logEventSampled( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, + MddClientEvent.Code.DATA_DOWNLOAD_COMPLETE_IMMEDIATE, group.getGroupName(), group.getFileGroupVersionNumber(), group.getBuildId(), @@ -439,7 +439,7 @@ public class MobileDataDownloadManager { if (useIsolatedStructure) { isolatedUriMapBuilder.putAll(fileGroupManager.getIsolatedFileUris(dataFileGroup)); } - ImmutableMap isolatedUriMap = isolatedUriMapBuilder.build(); + ImmutableMap isolatedUriMap = isolatedUriMapBuilder.buildKeepingLast(); return PropagatedFluentFuture.from(init()) .transformAsync( @@ -491,7 +491,7 @@ public class MobileDataDownloadManager { finalUriMapBuilder.put(entry); } } - return finalUriMapBuilder.build(); + return finalUriMapBuilder.buildKeepingLast(); }, sequentialControlExecutor); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java b/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java index 2c1775d..6ba466f 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/SharedFileManager.java @@ -57,6 +57,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders; @@ -68,7 +69,6 @@ import com.google.mobiledatadownload.internal.MetadataProto.FileStatus; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; import com.google.mobiledatadownload.internal.MetadataProto.SharedFile; -import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; @@ -963,7 +963,7 @@ public class SharedFileManager { uriMapBuilder.put(newFileKey, onDeviceUri); } } - return immediateFuture(uriMapBuilder.build()); + return immediateFuture(uriMapBuilder.buildKeepingLast()); }, sequentialControlExecutor); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/downloader/DeltaFileDownloaderCallbackImpl.java b/java/com/google/android/libraries/mobiledatadownload/internal/downloader/DeltaFileDownloaderCallbackImpl.java index 345616d..2583905 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/downloader/DeltaFileDownloaderCallbackImpl.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/downloader/DeltaFileDownloaderCallbackImpl.java @@ -38,13 +38,13 @@ import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures import com.google.common.base.Ascii; import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; +import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders; import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile; import com.google.mobiledatadownload.internal.MetadataProto.FileStatus; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; -import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import java.io.IOException; import java.util.concurrent.Executor; diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/DownloadStateLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/DownloadStateLogger.java index 85b13a9..b10692b 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/DownloadStateLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/DownloadStateLogger.java @@ -18,6 +18,7 @@ package com.google.android.libraries.mobiledatadownload.internal.logging; import com.google.errorprone.annotations.CheckReturnValue; import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; +import com.google.mobiledatadownload.LogProto.MddDownloadLatency; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupBookkeeping; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; @@ -56,7 +57,7 @@ public final class DownloadStateLogger { public void logStarted(DataFileGroupInternal fileGroup) { switch (operation) { case DOWNLOAD: - logEventWithDataFileGroup(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, fileGroup); + logEventWithDataFileGroup(MddClientEvent.Code.DATA_DOWNLOAD_STARTED, fileGroup); break; case IMPORT: logEventWithDataFileGroup(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, fileGroup); @@ -89,7 +90,7 @@ public final class DownloadStateLogger { public void logComplete(DataFileGroupInternal fileGroup) { switch (operation) { case DOWNLOAD: - logEventWithDataFileGroup(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, fileGroup); + logEventWithDataFileGroup(MddClientEvent.Code.DATA_DOWNLOAD_COMPLETE, fileGroup); logDownloadLatency(fileGroup); break; case IMPORT: @@ -120,7 +121,12 @@ public final class DownloadStateLogger { long downloadStartedTimestamp = bookkeeping.getGroupDownloadStartedTimestampInMillis(); long downloadCompleteTimestamp = bookkeeping.getGroupDownloadedTimestampInMillis(); - Void downloadLatency = null; + MddDownloadLatency downloadLatency = + MddDownloadLatency.newBuilder() + .setDownloadAttemptCount(bookkeeping.getDownloadStartedCount()) + .setDownloadLatencyMs(downloadCompleteTimestamp - downloadStartedTimestamp) + .setTotalLatencyMs(downloadCompleteTimestamp - newFilesReceivedTimestamp) + .build(); eventLogger.logMddDownloadLatency(fileGroupDetails, downloadLatency); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/EventLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/EventLogger.java index 83e8311..03d42d3 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/EventLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/EventLogger.java @@ -21,110 +21,119 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; +import com.google.mobiledatadownload.LogProto.MddDownloadLatency; import com.google.mobiledatadownload.LogProto.MddFileGroupStatus; +import com.google.mobiledatadownload.LogProto.MddLibApiResultLog; +import com.google.mobiledatadownload.LogProto.MddNetworkStats; import com.google.mobiledatadownload.LogProto.MddStorageStats; + import java.util.List; /** Interface for remote logging. */ public interface EventLogger { - /** Log an mdd event */ - void logEventSampled(MddClientEvent.Code eventCode); - - /** Log an mdd event with an associated file group. */ - void logEventSampled( - MddClientEvent.Code eventCode, - String fileGroupName, - int fileGroupVersionNumber, - long buildId, - String variantId); - - /** - * Log an mdd event. This not sampled. Caller should make sure this method is called after - * sampling at the passed in value of sample interval. - */ - void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval); - - /** - * Log mdd file group stats. The buildFileGroupStats callable is only called if the event is going - * to be logged. - * - * @param buildFileGroupStats callable which builds a List of FileGroupStatusWithDetails. Each - * file group status will be logged individually. - * @return a future that completes when the logging work is done. The future will complete with a - * failure if the callable fails or if there is an error when logging. - */ - ListenableFuture logMddFileGroupStats( - AsyncCallable> buildFileGroupStats); - - /** Simple wrapper class for MDD file group stats and details. */ - @AutoValue - abstract class FileGroupStatusWithDetails { - abstract MddFileGroupStatus fileGroupStatus(); - - abstract DataDownloadFileGroupStats fileGroupDetails(); - - static FileGroupStatusWithDetails create( - MddFileGroupStatus fileGroupStatus, DataDownloadFileGroupStats fileGroupDetails) { - return new AutoValue_EventLogger_FileGroupStatusWithDetails( - fileGroupStatus, fileGroupDetails); + /** Log an mdd event */ + void logEventSampled(MddClientEvent.Code eventCode); + + /** Log an mdd event with an associated file group. */ + void logEventSampled( + MddClientEvent.Code eventCode, + String fileGroupName, + int fileGroupVersionNumber, + long buildId, + String variantId); + + /** + * Log an mdd event. This not sampled. Caller should make sure this method is called after + * sampling at the passed in value of sample interval. + */ + void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval); + + /** + * Log mdd file group stats. The buildFileGroupStats callable is only called if the event is + * going + * to be logged. + * + * @param buildFileGroupStats callable which builds a List of FileGroupStatusWithDetails. Each + * file group status will be logged individually. + * @return a future that completes when the logging work is done. The future will complete + * with a + * failure if the callable fails or if there is an error when logging. + */ + ListenableFuture logMddFileGroupStats( + AsyncCallable> buildFileGroupStats); + + /** Simple wrapper class for MDD file group stats and details. */ + @AutoValue + abstract class FileGroupStatusWithDetails { + abstract MddFileGroupStatus fileGroupStatus(); + + abstract DataDownloadFileGroupStats fileGroupDetails(); + + static FileGroupStatusWithDetails create( + MddFileGroupStatus fileGroupStatus, DataDownloadFileGroupStats fileGroupDetails) { + return new AutoValue_EventLogger_FileGroupStatusWithDetails( + fileGroupStatus, fileGroupDetails); + } } - } - - /** Log mdd api call stats. */ - void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats); - - void logMddLibApiResultLog(Void mddLibApiResultLog); - - /** - * Log mdd storage stats. The buildMddStorageStats callable is only called if the event is going - * to be logged. - * - * @param buildMddStorageStats callable which builds the MddStorageStats to log. - * @return a future that completes when the logging work is done. The future will complete with a - * failure if the callable fails or if there is an error when logging. - */ - ListenableFuture logMddStorageStats(AsyncCallable buildMddStorageStats); - - /** - * Log mdd network stats. The buildMddNetworkStats callable is only called if the event is going - * to be logged. - * - * @param buildMddNetworkStats callable which builds the Void to log. - * @return a future that completes when the logging work is done. The future will complete with a - * failure if the callable fails or if there is an error when logging. - */ - ListenableFuture logMddNetworkStats(AsyncCallable buildMddNetworkStats); - - /** Log the number of unaccounted files/metadata deleted during maintenance */ - void logMddDataDownloadFileExpirationEvent(int eventCode, int count); - - /** Log the network savings of MDD download features */ - void logMddNetworkSavings( - DataDownloadFileGroupStats fileGroupDetails, - int code, - long fullFileSize, - long downloadedFileSize, - String fileId, - int deltaIndex); - - /** Log mdd download result events. */ - void logMddDownloadResult( - MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails); - - /** Log stats of mdd {@code getFileGroup} and {@code getFileGroupByFilter} calls. */ - void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails); - - /** Log mdd stats on android sharing events. */ - void logMddAndroidSharingLog(Void event); - - /** Log mdd download latency. */ - void logMddDownloadLatency(DataDownloadFileGroupStats fileGroupStats, Void downloadLatency); - - /** Log mdd usage event. */ - void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog); - - /** Log new config received event. */ - void logNewConfigReceived( - DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo); + + /** Log mdd api call stats. */ + void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats); + + void logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog); + + /** + * Log mdd storage stats. The buildMddStorageStats callable is only called if the event is going + * to be logged. + * + * @param buildMddStorageStats callable which builds the MddStorageStats to log. + * @return a future that completes when the logging work is done. The future will complete + * with a + * failure if the callable fails or if there is an error when logging. + */ + ListenableFuture logMddStorageStats(AsyncCallable buildMddStorageStats); + + /** + * Log mdd network stats. The buildMddNetworkStats callable is only called if the event is going + * to be logged. + * + * @param buildMddNetworkStats callable which builds the MddNetworkStats to log. + * @return a future that completes when the logging work is done. The future will complete + * with a + * failure if the callable fails or if there is an error when logging. + */ + ListenableFuture logMddNetworkStats(AsyncCallable buildMddNetworkStats); + + /** Log the number of unaccounted files/metadata deleted during maintenance */ + void logMddDataDownloadFileExpirationEvent(int eventCode, int count); + + /** Log the network savings of MDD download features */ + void logMddNetworkSavings( + DataDownloadFileGroupStats fileGroupDetails, + int code, + long fullFileSize, + long downloadedFileSize, + String fileId, + int deltaIndex); + + /** Log mdd download result events. */ + void logMddDownloadResult( + MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails); + + /** Log stats of mdd {@code getFileGroup} and {@code getFileGroupByFilter} calls. */ + void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails); + + /** Log mdd stats on android sharing events. */ + void logMddAndroidSharingLog(Void event); + + /** Log mdd download latency. */ + void logMddDownloadLatency( + DataDownloadFileGroupStats fileGroupStats, MddDownloadLatency downloadLatency); + + /** Log mdd usage event. */ + void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog); + + /** Log new config received event. */ + void logNewConfigReceived( + DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo); } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/LogUtil.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/LogUtil.java index bc4375e..149d7f9 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/LogUtil.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/LogUtil.java @@ -17,7 +17,6 @@ package com.google.android.libraries.mobiledatadownload.internal.logging; import android.annotation.SuppressLint; import android.util.Log; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.FormatMethod; import com.google.errorprone.annotations.FormatString; import java.util.Locale; @@ -30,7 +29,6 @@ public class LogUtil { private static final Random random = new Random(); - @CanIgnoreReturnValue // pushed down from class to method; see public static int getLogPriority() { int level = Log.ASSERT; while (level > Log.VERBOSE) { @@ -42,7 +40,6 @@ public class LogUtil { return level; } - @CanIgnoreReturnValue // pushed down from class to method; see public static int v(String msg) { if (Log.isLoggable(TAG, Log.VERBOSE)) { return Log.v(TAG, msg); @@ -50,7 +47,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int v(@FormatString String format, Object obj0) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -60,7 +56,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int v(@FormatString String format, Object obj0, Object obj1) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -70,7 +65,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int v(@FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -80,7 +74,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see public static int d(String msg) { if (Log.isLoggable(TAG, Log.DEBUG)) { return Log.d(TAG, msg); @@ -88,7 +81,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int d(@FormatString String format, Object obj0) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -98,7 +90,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int d(@FormatString String format, Object obj0, Object obj1) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -108,7 +99,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int d(@FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -118,7 +108,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int d(@Nullable Throwable tr, @FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -128,7 +117,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see public static int i(String msg) { if (Log.isLoggable(TAG, Log.INFO)) { return Log.i(TAG, msg); @@ -136,7 +124,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int i(@FormatString String format, Object obj0) { if (Log.isLoggable(TAG, Log.INFO)) { @@ -146,7 +133,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int i(@FormatString String format, Object obj0, Object obj1) { if (Log.isLoggable(TAG, Log.INFO)) { @@ -156,7 +142,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int i(@FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.INFO)) { @@ -166,7 +151,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see public static int e(String msg) { if (Log.isLoggable(TAG, Log.ERROR)) { return Log.e(TAG, msg); @@ -174,7 +158,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int e(@FormatString String format, Object obj0) { if (Log.isLoggable(TAG, Log.ERROR)) { @@ -184,7 +167,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int e(@FormatString String format, Object obj0, Object obj1) { if (Log.isLoggable(TAG, Log.ERROR)) { @@ -194,7 +176,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int e(@FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.ERROR)) { @@ -204,7 +185,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @SuppressLint("LogTagMismatch") public static int e(@Nullable Throwable tr, String msg) { if (Log.isLoggable(TAG, Log.ERROR)) { @@ -219,13 +199,11 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int e(@Nullable Throwable tr, @FormatString String format, Object... params) { return Log.isLoggable(TAG, Log.ERROR) ? e(tr, format(format, params)) : 0; } - @CanIgnoreReturnValue // pushed down from class to method; see public static int w(String msg) { if (Log.isLoggable(TAG, Log.WARN)) { return Log.w(TAG, msg); @@ -233,7 +211,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int w(@FormatString String format, Object obj0) { if (Log.isLoggable(TAG, Log.WARN)) { @@ -243,7 +220,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int w(@FormatString String format, Object obj0, Object obj1) { if (Log.isLoggable(TAG, Log.WARN)) { @@ -253,7 +229,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod public static int w(@FormatString String format, Object... params) { if (Log.isLoggable(TAG, Log.WARN)) { @@ -263,7 +238,6 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @SuppressLint("LogTagMismatch") @FormatMethod public static int w(@Nullable Throwable tr, @FormatString String format, Object... params) { @@ -280,13 +254,11 @@ public class LogUtil { return 0; } - @CanIgnoreReturnValue // pushed down from class to method; see @FormatMethod private static String format(@FormatString String format, Object... args) { return String.format(Locale.US, format, args); } - @CanIgnoreReturnValue // pushed down from class to method; see public static boolean shouldSampleInterval(long sampleInterval) { if (sampleInterval <= 0L) { if (sampleInterval < 0L) { diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLogger.java index 620421c..9be820d 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLogger.java @@ -21,6 +21,7 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; + import com.google.android.libraries.mobiledatadownload.Flags; import com.google.android.libraries.mobiledatadownload.Logger; import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFluentFuture; @@ -34,10 +35,14 @@ import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; import com.google.mobiledatadownload.LogProto.AndroidClientInfo; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import com.google.mobiledatadownload.LogProto.MddDeviceInfo; +import com.google.mobiledatadownload.LogProto.MddDownloadLatency; import com.google.mobiledatadownload.LogProto.MddDownloadResultLog; +import com.google.mobiledatadownload.LogProto.MddLibApiResultLog; import com.google.mobiledatadownload.LogProto.MddLogData; +import com.google.mobiledatadownload.LogProto.MddNetworkStats; import com.google.mobiledatadownload.LogProto.MddStorageStats; import com.google.mobiledatadownload.LogProto.StableSamplingInfo; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -45,327 +50,354 @@ import java.util.List; /** Assembles data and logs them with underlying {@link Logger}. */ public final class MddEventLogger implements EventLogger { - private static final String TAG = "MddEventLogger"; - - private final Context context; - private final Logger logger; - // A process that has mdi download module loaded will get restarted if a new module version is - // installed. - private final int moduleVersion; - private final String hostPackageName; - private final Flags flags; - private final LogSampler logSampler; - - private Optional loggingStateStore = Optional.absent(); - - public MddEventLogger( - Context context, Logger logger, int moduleVersion, LogSampler logSampler, Flags flags) { - this.context = context; - this.logger = logger; - this.moduleVersion = moduleVersion; - this.hostPackageName = context.getPackageName(); - this.logSampler = logSampler; - this.flags = flags; - } - - /** - * This should be called before MddEventLogger is used. If it is not called before MddEventLogger - * is used, stable sampling will not be used. - * - *

Note(rohitsat): this is required because LoggingStateStore is constructed with a PDS in the - * MainMddLibModule. MddEventLogger is required to construct the MainMddLibModule. - * - * @param loggingStateStore the LoggingStateStore that contains the persisted random number for - * stable sampling. - */ - public void setLoggingStateStore(LoggingStateStore loggingStateStore) { - this.loggingStateStore = Optional.of(loggingStateStore); - } - - @Override - public void logEventSampled(MddClientEvent.Code eventCode) { - sampleAndSendLogEvent(eventCode, MddLogData.newBuilder(), flags.mddDefaultSampleInterval()); - } - - @Override - public void logEventSampled( - MddClientEvent.Code eventCode, - String fileGroupName, - int fileGroupVersionNumber, - long buildId, - String variantId) { - - DataDownloadFileGroupStats dataDownloadFileGroupStats = - DataDownloadFileGroupStats.newBuilder() - .setFileGroupName(fileGroupName) - .setFileGroupVersionNumber(fileGroupVersionNumber) - .setBuildId(buildId) - .setVariantId(variantId) - .build(); - - sampleAndSendLogEvent( - eventCode, - MddLogData.newBuilder().setDataDownloadFileGroupStats(dataDownloadFileGroupStats), - flags.mddDefaultSampleInterval()); - } - - @Override - public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) { - // TODO(b/138392640): delete this method once the pds migration is complete. If it's necessary - // for other use cases, we can establish a pattern where this class is still responsible for - // sampling. - MddLogData.Builder logData = MddLogData.newBuilder(); - processAndSendEventWithoutStableSampling(eventCode, logData, sampleInterval); - } - - @Override - public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) { - // TODO(b/144684763): update this to use stable sampling. Leaving it as is for now since it is - // fairly high volume. - long sampleInterval = flags.apiLoggingSampleInterval(); - if (!LogUtil.shouldSampleInterval(sampleInterval)) { - return; + private static final String TAG = "MddEventLogger"; + + private final Context context; + private final Logger logger; + // A process that has mdi download module loaded will get restarted if a new module version is + // installed. + private final int moduleVersion; + private final String hostPackageName; + private final Flags flags; + private final LogSampler logSampler; + + private Optional loggingStateStore = Optional.absent(); + + public MddEventLogger( + Context context, Logger logger, int moduleVersion, LogSampler logSampler, Flags flags) { + this.context = context; + this.logger = logger; + this.moduleVersion = moduleVersion; + this.hostPackageName = context.getPackageName(); + this.logSampler = logSampler; + this.flags = flags; + } + + /** + * This should be called before MddEventLogger is used. If it is not called before + * MddEventLogger + * is used, stable sampling will not be used. + * + *

Note(rohitsat): this is required because LoggingStateStore is constructed with a PDS in + * the + * MainMddLibModule. MddEventLogger is required to construct the MainMddLibModule. + * + * @param loggingStateStore the LoggingStateStore that contains the persisted random number for + * stable sampling. + */ + public void setLoggingStateStore(LoggingStateStore loggingStateStore) { + this.loggingStateStore = Optional.of(loggingStateStore); + } + + @Override + public void logEventSampled(MddClientEvent.Code eventCode) { + sampleAndSendLogEvent(eventCode, MddLogData.newBuilder(), flags.mddDefaultSampleInterval()); + } + + @Override + public void logEventSampled( + MddClientEvent.Code eventCode, + String fileGroupName, + int fileGroupVersionNumber, + long buildId, + String variantId) { + + DataDownloadFileGroupStats dataDownloadFileGroupStats = + DataDownloadFileGroupStats.newBuilder() + .setFileGroupName(fileGroupName) + .setFileGroupVersionNumber(fileGroupVersionNumber) + .setBuildId(buildId) + .setVariantId(variantId) + .build(); + + sampleAndSendLogEvent( + eventCode, + MddLogData.newBuilder().setDataDownloadFileGroupStats(dataDownloadFileGroupStats), + flags.mddDefaultSampleInterval()); + } + + @Override + public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) { + // TODO(b/138392640): delete this method once the pds migration is complete. If it's + // necessary + // for other use cases, we can establish a pattern where this class is still responsible for + // sampling. + MddLogData.Builder logData = MddLogData.newBuilder(); + processAndSendEventWithoutStableSampling(eventCode, logData, sampleInterval); + } + + @Override + public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) { + // TODO(b/144684763): update this to use stable sampling. Leaving it as is for now since + // it is + // fairly high volume. + long sampleInterval = flags.apiLoggingSampleInterval(); + if (!LogUtil.shouldSampleInterval(sampleInterval)) { + return; + } + MddLogData.Builder logData = + MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); + processAndSendEventWithoutStableSampling( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval); + } + + @Override + public void logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog) { + MddLogData.Builder logData = MddLogData.newBuilder().setMddLibApiResultLog( + mddLibApiResultLog); + + sampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_LIB_API_RESULT, + logData, + flags.apiLoggingSampleInterval()); + } + + @Override + public ListenableFuture logMddFileGroupStats( + AsyncCallable> buildFileGroupStats) { + return lazySampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_FILE_GROUP_STATUS, + () -> + PropagatedFutures.transform( + buildFileGroupStats.call(), + fileGroupStatusAndDetailsList -> { + List allMddLogData = new ArrayList<>(); + + for (FileGroupStatusWithDetails fileGroupStatusAndDetails : + fileGroupStatusAndDetailsList) { + allMddLogData.add( + MddLogData.newBuilder() + .setMddFileGroupStatus( + fileGroupStatusAndDetails.fileGroupStatus()) + .setDataDownloadFileGroupStats( + fileGroupStatusAndDetails.fileGroupDetails()) + .build()); + } + return allMddLogData; + }, + directExecutor()), + flags.groupStatsLoggingSampleInterval()); + } + + @Override + public ListenableFuture logMddStorageStats( + AsyncCallable buildStorageStats) { + return lazySampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_STORAGE_STATS, + () -> + PropagatedFutures.transform( + buildStorageStats.call(), + storageStats -> + Arrays.asList(MddLogData.newBuilder().setMddStorageStats( + storageStats).build()), + directExecutor()), + flags.storageStatsLoggingSampleInterval()); + } + + @Override + public ListenableFuture logMddNetworkStats( + AsyncCallable buildNetworkStats) { + return lazySampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_NETWORK_STATS, + () -> + PropagatedFutures.transform( + buildNetworkStats.call(), + networkStats -> + Arrays.asList(MddLogData.newBuilder().setMddNetworkStats( + networkStats).build()), + directExecutor()), + flags.networkStatsLoggingSampleInterval()); + } + + @Override + public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) { + MddLogData.Builder logData = MddLogData.newBuilder(); + sampleAndSendLogEvent( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logMddNetworkSavings( + DataDownloadFileGroupStats fileGroupDetails, + int code, + long fullFileSize, + long downloadedFileSize, + String fileId, + int deltaIndex) { + MddLogData.Builder logData = MddLogData.newBuilder(); + + sampleAndSendLogEvent( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) { + MddLogData.Builder logData = MddLogData.newBuilder(); + + sampleAndSendLogEvent( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logMddDownloadLatency( + DataDownloadFileGroupStats fileGroupDetails, MddDownloadLatency downloadLatency) { + MddLogData.Builder logData = + MddLogData.newBuilder() + .setMddDownloadLatency(downloadLatency) + .setDataDownloadFileGroupStats(fileGroupDetails); + + sampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_LATENCY_LOG, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logMddDownloadResult( + MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) { + MddLogData.Builder logData = + MddLogData.newBuilder() + .setMddDownloadResultLog( + MddDownloadResultLog.newBuilder() + .setResult(code) + .setDataDownloadFileGroupStats(fileGroupDetails)); + + sampleAndSendLogEvent( + MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logMddAndroidSharingLog(Void event) { + // TODO(b/144684763): consider moving this to stable sampling depending on frequency of + // events. + long sampleInterval = flags.mddAndroidSharingSampleInterval(); + if (!LogUtil.shouldSampleInterval(sampleInterval)) { + return; + } + MddLogData.Builder logData = MddLogData.newBuilder(); + processAndSendEventWithoutStableSampling( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval); + } + + @Override + public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) { + MddLogData.Builder logData = + MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); + + sampleAndSendLogEvent( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, + flags.mddDefaultSampleInterval()); + } + + @Override + public void logNewConfigReceived( + DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) { + MddLogData.Builder logData = + MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); + sampleAndSendLogEvent( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, + flags.mddDefaultSampleInterval()); } - MddLogData.Builder logData = - MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); - processAndSendEventWithoutStableSampling( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval); - } - - @Override - public void logMddLibApiResultLog(Void mddLibApiResultLog) { - MddLogData.Builder logData = MddLogData.newBuilder(); - - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.apiLoggingSampleInterval()); - } - - @Override - public ListenableFuture logMddFileGroupStats( - AsyncCallable> buildFileGroupStats) { - return lazySampleAndSendLogEvent( - MddClientEvent.Code.DATA_DOWNLOAD_FILE_GROUP_STATUS, - () -> - PropagatedFutures.transform( - buildFileGroupStats.call(), - fileGroupStatusAndDetailsList -> { - List allMddLogData = new ArrayList<>(); - - for (FileGroupStatusWithDetails fileGroupStatusAndDetails : - fileGroupStatusAndDetailsList) { - allMddLogData.add( - MddLogData.newBuilder() - .setMddFileGroupStatus(fileGroupStatusAndDetails.fileGroupStatus()) - .setDataDownloadFileGroupStats( - fileGroupStatusAndDetails.fileGroupDetails()) - .build()); - } - return allMddLogData; + + /** + * Determines whether the log event will be a part of the sample, and if so calls {@code + * buildStats} to construct the log event. This is like {@link sampleAndSendLogEvent} but + * constructs the log event lazy. This is useful if constructing the log event is expensive. + */ + private ListenableFuture lazySampleAndSendLogEvent( + MddClientEvent.Code eventCode, + AsyncCallable> buildStats, + int sampleInterval) { + return PropagatedFutures.transformAsync( + logSampler.shouldLog(sampleInterval, loggingStateStore), + samplingInfoOptional -> { + if (!samplingInfoOptional.isPresent()) { + return immediateVoidFuture(); + } + + return PropagatedFluentFuture.from(buildStats.call()) + .transform( + icingLogDataList -> { + if (icingLogDataList != null) { + for (MddLogData icingLogData : icingLogDataList) { + processAndSendEvent( + eventCode, + icingLogData.toBuilder(), + sampleInterval, + samplingInfoOptional.get()); + } + } + return null; + }, + directExecutor()); }, - directExecutor()), - flags.groupStatsLoggingSampleInterval()); - } - - @Override - public ListenableFuture logMddStorageStats( - AsyncCallable buildStorageStats) { - return lazySampleAndSendLogEvent( - MddClientEvent.Code.DATA_DOWNLOAD_STORAGE_STATS, - () -> - PropagatedFutures.transform( - buildStorageStats.call(), - storageStats -> - Arrays.asList(MddLogData.newBuilder().setMddStorageStats(storageStats).build()), - directExecutor()), - flags.storageStatsLoggingSampleInterval()); - } - - @Override - public ListenableFuture logMddNetworkStats(AsyncCallable buildNetworkStats) { - return lazySampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, - () -> - PropagatedFutures.transform( - buildNetworkStats.call(), networkStats -> Arrays.asList(), directExecutor()), - flags.networkStatsLoggingSampleInterval()); - } - - @Override - public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) { - MddLogData.Builder logData = MddLogData.newBuilder(); - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logMddNetworkSavings( - DataDownloadFileGroupStats fileGroupDetails, - int code, - long fullFileSize, - long downloadedFileSize, - String fileId, - int deltaIndex) { - MddLogData.Builder logData = MddLogData.newBuilder(); - - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) { - MddLogData.Builder logData = MddLogData.newBuilder(); - - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logMddDownloadLatency( - DataDownloadFileGroupStats fileGroupDetails, Void downloadLatency) { - MddLogData.Builder logData = - MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); - - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logMddDownloadResult( - MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) { - MddLogData.Builder logData = - MddLogData.newBuilder() - .setMddDownloadResultLog( - MddDownloadResultLog.newBuilder() - .setResult(code) - .setDataDownloadFileGroupStats(fileGroupDetails)); - - sampleAndSendLogEvent( - MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logMddAndroidSharingLog(Void event) { - // TODO(b/144684763): consider moving this to stable sampling depending on frequency of events. - long sampleInterval = flags.mddAndroidSharingSampleInterval(); - if (!LogUtil.shouldSampleInterval(sampleInterval)) { - return; + directExecutor()); } - MddLogData.Builder logData = MddLogData.newBuilder(); - processAndSendEventWithoutStableSampling( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval); - } - - @Override - public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) { - MddLogData.Builder logData = - MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); - - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - @Override - public void logNewConfigReceived( - DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) { - MddLogData.Builder logData = - MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails); - sampleAndSendLogEvent( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, flags.mddDefaultSampleInterval()); - } - - /** - * Determines whether the log event will be a part of the sample, and if so calls {@code - * buildStats} to construct the log event. This is like {@link sampleAndSendLogEvent} but - * constructs the log event lazy. This is useful if constructing the log event is expensive. - */ - private ListenableFuture lazySampleAndSendLogEvent( - MddClientEvent.Code eventCode, - AsyncCallable> buildStats, - int sampleInterval) { - return PropagatedFutures.transformAsync( - logSampler.shouldLog(sampleInterval, loggingStateStore), - samplingInfoOptional -> { - if (!samplingInfoOptional.isPresent()) { - return immediateVoidFuture(); - } - - return PropagatedFluentFuture.from(buildStats.call()) - .transform( - icingLogDataList -> { - if (icingLogDataList != null) { - for (MddLogData icingLogData : icingLogDataList) { - processAndSendEvent( - eventCode, - icingLogData.toBuilder(), - sampleInterval, - samplingInfoOptional.get()); - } + + private void sampleAndSendLogEvent( + MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) { + // NOTE: When using a single-threaded executor, logging may be delayed since other + // work will come before the log sampler check. + PropagatedFutures.addCallback( + logSampler.shouldLog(sampleInterval, loggingStateStore), + new FutureCallback>() { + @Override + public void onSuccess(Optional stableSamplingInfo) { + if (stableSamplingInfo.isPresent()) { + processAndSendEvent(eventCode, logData, sampleInterval, + stableSamplingInfo.get()); + } } - return null; - }, - directExecutor()); - }, - directExecutor()); - } - - private void sampleAndSendLogEvent( - MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) { - // NOTE: When using a single-threaded executor, logging may be delayed since other - // work will come before the log sampler check. - PropagatedFutures.addCallback( - logSampler.shouldLog(sampleInterval, loggingStateStore), - new FutureCallback>() { - @Override - public void onSuccess(Optional stableSamplingInfo) { - if (stableSamplingInfo.isPresent()) { - processAndSendEvent(eventCode, logData, sampleInterval, stableSamplingInfo.get()); - } - } - - @Override - public void onFailure(Throwable t) { - LogUtil.e(t, "%s: failure when sampling log!", TAG); - } - }, - directExecutor()); - } - - /** Adds all transforms common to all logs and sends the event to Logger. */ - private void processAndSendEventWithoutStableSampling( - MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) { - processAndSendEvent( - eventCode, - logData, - sampleInterval, - StableSamplingInfo.newBuilder().setStableSamplingUsed(false).build()); - } - - /** Adds all transforms common to all logs and sends the event to Logger. */ - private void processAndSendEvent( - MddClientEvent.Code eventCode, - MddLogData.Builder logData, - long sampleInterval, - StableSamplingInfo stableSamplingInfo) { - if (eventCode.equals(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED)) { - LogUtil.e("%s: unspecified code used, skipping event log", TAG); - // return early for unspecified codes. - return; + + @Override + public void onFailure(Throwable t) { + LogUtil.e(t, "%s: failure when sampling log!", TAG); + } + }, + directExecutor()); + } + + /** Adds all transforms common to all logs and sends the event to Logger. */ + private void processAndSendEventWithoutStableSampling( + MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) { + processAndSendEvent( + eventCode, + logData, + sampleInterval, + StableSamplingInfo.newBuilder().setStableSamplingUsed(false).build()); + } + + /** Adds all transforms common to all logs and sends the event to Logger. */ + private void processAndSendEvent( + MddClientEvent.Code eventCode, + MddLogData.Builder logData, + long sampleInterval, + StableSamplingInfo stableSamplingInfo) { + if (eventCode.equals(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED)) { + LogUtil.e("%s: unspecified code used, skipping event log", TAG); + // return early for unspecified codes. + return; + } + + logData + .setSamplingInterval(sampleInterval) + .setDeviceInfo( + MddDeviceInfo.newBuilder().setDeviceStorageLow(isDeviceStorageLow(context))) + .setAndroidClientInfo( + AndroidClientInfo.newBuilder() + .setHostPackageName(hostPackageName) + .setModuleVersion(moduleVersion)) + .setStableSamplingInfo(stableSamplingInfo); + logger.log(logData.build(), eventCode.getNumber()); + } + + /** Returns whether the device is in low storage state. */ + private static boolean isDeviceStorageLow(Context context) { + // Check if the system says storage is low, by reading the sticky intent. + return context.registerReceiver(null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)) + != null; } - logData - .setSamplingInterval(sampleInterval) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(isDeviceStorageLow(context))) - .setAndroidClientInfo( - AndroidClientInfo.newBuilder() - .setHostPackageName(hostPackageName) - .setModuleVersion(moduleVersion)) - .setStableSamplingInfo(stableSamplingInfo); - logger.log(logData.build(), eventCode.getNumber()); - } - - /** Returns whether the device is in low storage state. */ - private static boolean isDeviceStorageLow(Context context) { - // Check if the system says storage is low, by reading the sticky intent. - return context.registerReceiver(null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)) - != null; - } } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/NetworkLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/NetworkLogger.java index 9f2ac1f..3109bf2 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/NetworkLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/NetworkLogger.java @@ -19,14 +19,19 @@ import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import android.content.Context; + import com.google.android.libraries.mobiledatadownload.Flags; import com.google.android.libraries.mobiledatadownload.annotations.InstanceId; import com.google.android.libraries.mobiledatadownload.internal.ApplicationContext; import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; +import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; +import com.google.mobiledatadownload.LogProto.MddNetworkStats; import com.google.mobiledatadownload.internal.MetadataProto.FileGroupLoggingState; + import java.util.List; + import javax.inject.Inject; /** @@ -67,11 +72,32 @@ public class NetworkLogger { allDataUsageFuture, this::buildNetworkStats, directExecutor())); } - private Void buildNetworkStats(List allDataUsage) { + private MddNetworkStats buildNetworkStats(List allDataUsage) { long totalMddWifiCount = 0; long totalMddCellularCount = 0; - Void networkStatsBuilder = null; + MddNetworkStats.Builder networkStatsBuilder = MddNetworkStats.newBuilder(); + + for (FileGroupLoggingState fileGroupLoggingState : allDataUsage) { + networkStatsBuilder.addGroupStats( + MddNetworkStats.GroupStats.newBuilder() + .setDataDownloadFileGroupStats( + DataDownloadFileGroupStats.newBuilder() + .setOwnerPackage(fileGroupLoggingState.getGroupKey().getOwnerPackage()) + .setFileGroupName(fileGroupLoggingState.getGroupKey().getGroupName()) + .setFileGroupVersionNumber(fileGroupLoggingState.getFileGroupVersionNumber()) + .setBuildId(fileGroupLoggingState.getBuildId()) + .setVariantId(fileGroupLoggingState.getVariantId()) + .build()) + .setTotalWifiBytes(fileGroupLoggingState.getWifiUsage()) + .setTotalCellularBytes(fileGroupLoggingState.getCellularUsage())); + + totalMddWifiCount += fileGroupLoggingState.getWifiUsage(); + totalMddCellularCount += fileGroupLoggingState.getCellularUsage(); + } - return networkStatsBuilder; + networkStatsBuilder + .setTotalMddWifiBytes(totalMddWifiCount) + .setTotalMddCellularBytes(totalMddCellularCount); + return networkStatsBuilder.build(); } } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/NoOpEventLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/NoOpEventLogger.java index 2f043f5..e28227a 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/NoOpEventLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/NoOpEventLogger.java @@ -22,79 +22,97 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; +import com.google.mobiledatadownload.LogProto.MddDownloadLatency; +import com.google.mobiledatadownload.LogProto.MddLibApiResultLog; +import com.google.mobiledatadownload.LogProto.MddNetworkStats; import com.google.mobiledatadownload.LogProto.MddStorageStats; + import java.util.List; /** No-Op EventLogger implementation. */ public final class NoOpEventLogger implements EventLogger { - @Override - public void logEventSampled(MddClientEvent.Code eventCode) {} - - @Override - public void logEventSampled( - MddClientEvent.Code eventCode, - String fileGroupName, - int fileGroupVersionNumber, - long buildId, - String variantId) {} - - @Override - public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) {} - - @Override - public ListenableFuture logMddFileGroupStats( - AsyncCallable> buildFileGroupStats) { - return immediateVoidFuture(); - } - - @Override - public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) {} - - @Override - public void logMddLibApiResultLog(Void mddLibApiResultLog) {} - - @Override - public ListenableFuture logMddStorageStats( - AsyncCallable buildMddStorageStats) { - return immediateVoidFuture(); - } - - @Override - public ListenableFuture logMddNetworkStats(AsyncCallable buildMddNetworkStats) { - return immediateVoidFuture(); - } - - @Override - public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) {} - - @Override - public void logMddNetworkSavings( - DataDownloadFileGroupStats fileGroupDetails, - int code, - long fullFileSize, - long downloadedFileSize, - String fileId, - int deltaIndex) {} - - @Override - public void logMddDownloadResult( - MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) {} - - @Override - public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) {} - - @Override - public void logMddAndroidSharingLog(Void event) {} - - @Override - public void logMddDownloadLatency( - DataDownloadFileGroupStats fileGroupStats, Void downloadLatency) {} - - @Override - public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) {} - - @Override - public void logNewConfigReceived( - DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) {} + @Override + public void logEventSampled(MddClientEvent.Code eventCode) { + } + + @Override + public void logEventSampled( + MddClientEvent.Code eventCode, + String fileGroupName, + int fileGroupVersionNumber, + long buildId, + String variantId) { + } + + @Override + public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) { + } + + @Override + public ListenableFuture logMddFileGroupStats( + AsyncCallable> buildFileGroupStats) { + return immediateVoidFuture(); + } + + @Override + public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) { + } + + @Override + public void logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog) { + } + + @Override + public ListenableFuture logMddStorageStats( + AsyncCallable buildMddStorageStats) { + return immediateVoidFuture(); + } + + @Override + public ListenableFuture logMddNetworkStats( + AsyncCallable buildMddNetworkStats) { + return immediateVoidFuture(); + } + + @Override + public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) { + } + + @Override + public void logMddNetworkSavings( + DataDownloadFileGroupStats fileGroupDetails, + int code, + long fullFileSize, + long downloadedFileSize, + String fileId, + int deltaIndex) { + } + + @Override + public void logMddDownloadResult( + MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) { + } + + @Override + public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) { + } + + @Override + public void logMddAndroidSharingLog(Void event) { + } + + @Override + public void logMddDownloadLatency( + DataDownloadFileGroupStats fileGroupStats, MddDownloadLatency downloadLatency) { + } + + @Override + public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) { + } + + @Override + public void logNewConfigReceived( + DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) { + } } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/SharedPreferencesLoggingState.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/SharedPreferencesLoggingState.java index 7fd90ef..c41a3af 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/SharedPreferencesLoggingState.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/SharedPreferencesLoggingState.java @@ -22,8 +22,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.content.Context; import android.content.SharedPreferences; -import androidx.annotation.VisibleForTesting; - import com.google.android.libraries.mobiledatadownload.TimeSource; import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupsMetadataUtil; import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupsMetadataUtil.GroupKeyDeserializationException; @@ -58,8 +56,7 @@ public final class SharedPreferencesLoggingState implements LoggingStateStore { private static final String LAST_MAINTENANCE_RUN_SECS_KEY = "last_maintenance_secs"; - @VisibleForTesting - static final String SALT_KEY = "stable_log_sampling_salt"; + private static final String SALT_KEY = "stable_log_sampling_salt"; private static final String SALT_TIMESTAMP_MILLIS_KEY = "log_sampling_salt_set_timestamp_millis"; diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/logging/testing/FakeEventLogger.java b/java/com/google/android/libraries/mobiledatadownload/internal/logging/testing/FakeEventLogger.java index ea5134c..6fafad7 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/logging/testing/FakeEventLogger.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/logging/testing/FakeEventLogger.java @@ -18,152 +18,160 @@ package com.google.android.libraries.mobiledatadownload.internal.logging.testing import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger; -import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger.FileGroupStatusWithDetails; import com.google.common.collect.ArrayListMultimap; import com.google.common.util.concurrent.AsyncCallable; import com.google.common.util.concurrent.ListenableFuture; import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; +import com.google.mobiledatadownload.LogProto.MddDownloadLatency; +import com.google.mobiledatadownload.LogProto.MddLibApiResultLog; +import com.google.mobiledatadownload.LogProto.MddNetworkStats; import com.google.mobiledatadownload.LogProto.MddStorageStats; + import java.util.ArrayList; import java.util.List; /** Fake implementation of {@link EventLogger} for use in tests. */ public final class FakeEventLogger implements EventLogger { - private final ArrayList loggedCodes = new ArrayList<>(); - private final ArrayListMultimap loggedLatencies = - ArrayListMultimap.create(); - private final ArrayListMultimap loggedNewConfigReceived = - ArrayListMultimap.create(); - private final List loggedMddLibApiResultLog = new ArrayList<>(); - private final ArrayList loggedMddQueryStats = new ArrayList<>(); - - @Override - public void logEventSampled(MddClientEvent.Code eventCode) { - loggedCodes.add(eventCode); - } - - @Override - public void logEventSampled( - MddClientEvent.Code eventCode, - String fileGroupName, - int fileGroupVersionNumber, - long buildId, - String variantId) { - loggedCodes.add(eventCode); - } - - @Override - public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) { - loggedCodes.add(eventCode); - } - - @Override - public ListenableFuture logMddFileGroupStats( - AsyncCallable> buildFileGroupStats) { - return immediateFailedFuture( - new UnsupportedOperationException("This method is not implemented in the fake yet.")); - } - - @Override - public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logMddLibApiResultLog(Void mddLibApiResultLog) { - loggedMddLibApiResultLog.add(mddLibApiResultLog); - } - - public List getLoggedMddLibApiResultLogs() { - return loggedMddLibApiResultLog; - } - - @Override - public ListenableFuture logMddStorageStats( - AsyncCallable buildMddStorageStats) { - return immediateFailedFuture( - new UnsupportedOperationException("This method is not implemented in the fake yet.")); - } - - @Override - public ListenableFuture logMddNetworkStats(AsyncCallable buildMddNetworkStats) { - return immediateFailedFuture( - new UnsupportedOperationException("This method is not implemented in the fake yet.")); - } - - @Override - public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logMddNetworkSavings( - DataDownloadFileGroupStats fileGroupDetails, - int code, - long fullFileSize, - long downloadedFileSize, - String fileId, - int deltaIndex) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logMddDownloadResult( - MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) { - loggedMddQueryStats.add(fileGroupDetails); - } - - @Override - public void logMddAndroidSharingLog(Void event) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logMddDownloadLatency( - DataDownloadFileGroupStats fileGroupStats, Void downloadLatency) { - loggedLatencies.put(fileGroupStats, downloadLatency); - } - - @Override - public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) { - throw new UnsupportedOperationException("This method is not implemented in the fake yet."); - } - - @Override - public void logNewConfigReceived( - DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) { - loggedNewConfigReceived.put(fileGroupDetails, newConfigReceivedInfo); - } - - public void reset() { - loggedCodes.clear(); - loggedLatencies.clear(); - loggedMddQueryStats.clear(); - loggedNewConfigReceived.clear(); - loggedMddLibApiResultLog.clear(); - } - - public ArrayListMultimap getLoggedNewConfigReceived() { - return loggedNewConfigReceived; - } - - public List getLoggedCodes() { - return loggedCodes; - } - - public ArrayListMultimap getLoggedLatencies() { - return loggedLatencies; - } - - public ArrayList getLoggedMddQueryStats() { - return loggedMddQueryStats; - } + private final ArrayList loggedCodes = new ArrayList<>(); + private final ArrayListMultimap + loggedLatencies = + ArrayListMultimap.create(); + private final ArrayListMultimap loggedNewConfigReceived = + ArrayListMultimap.create(); + private final List loggedMddLibApiResultLog = new ArrayList<>(); + private final ArrayList loggedMddQueryStats = new ArrayList<>(); + + @Override + public void logEventSampled(MddClientEvent.Code eventCode) { + loggedCodes.add(eventCode); + } + + @Override + public void logEventSampled( + MddClientEvent.Code eventCode, + String fileGroupName, + int fileGroupVersionNumber, + long buildId, + String variantId) { + loggedCodes.add(eventCode); + } + + @Override + public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) { + loggedCodes.add(eventCode); + } + + @Override + public ListenableFuture logMddFileGroupStats( + AsyncCallable> buildFileGroupStats) { + return immediateFailedFuture( + new UnsupportedOperationException( + "This method is not implemented in the fake yet.")); + } + + @Override + public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog) { + loggedMddLibApiResultLog.add(mddLibApiResultLog); + } + + public List getLoggedMddLibApiResultLogs() { + return loggedMddLibApiResultLog; + } + + @Override + public ListenableFuture logMddStorageStats( + AsyncCallable buildMddStorageStats) { + return immediateFailedFuture( + new UnsupportedOperationException( + "This method is not implemented in the fake yet.")); + } + + @Override + public ListenableFuture logMddNetworkStats( + AsyncCallable buildMddNetworkStats) { + return immediateFailedFuture( + new UnsupportedOperationException( + "This method is not implemented in the fake yet.")); + } + + @Override + public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logMddNetworkSavings( + DataDownloadFileGroupStats fileGroupDetails, + int code, + long fullFileSize, + long downloadedFileSize, + String fileId, + int deltaIndex) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logMddDownloadResult( + MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) { + loggedMddQueryStats.add(fileGroupDetails); + } + + @Override + public void logMddAndroidSharingLog(Void event) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logMddDownloadLatency( + DataDownloadFileGroupStats fileGroupStats, MddDownloadLatency downloadLatency) { + loggedLatencies.put(fileGroupStats, downloadLatency); + } + + @Override + public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) { + throw new UnsupportedOperationException("This method is not implemented in the fake yet."); + } + + @Override + public void logNewConfigReceived( + DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) { + loggedNewConfigReceived.put(fileGroupDetails, newConfigReceivedInfo); + } + + public void reset() { + loggedCodes.clear(); + loggedLatencies.clear(); + loggedMddQueryStats.clear(); + loggedNewConfigReceived.clear(); + loggedMddLibApiResultLog.clear(); + } + + public ArrayListMultimap getLoggedNewConfigReceived() { + return loggedNewConfigReceived; + } + + public List getLoggedCodes() { + return loggedCodes; + } + + public ArrayListMultimap getLoggedLatencies() { + return loggedLatencies; + } + + public ArrayList getLoggedMddQueryStats() { + return loggedMddQueryStats; + } } diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/proto/BUILD b/java/com/google/android/libraries/mobiledatadownload/internal/proto/BUILD index 6be1b57..28d6216 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/proto/BUILD +++ b/java/com/google/android/libraries/mobiledatadownload/internal/proto/BUILD @@ -13,7 +13,9 @@ # limitations under the License. package( default_applicable_licenses = ["//:license"], - default_visibility = ["//:__subpackages__"], + default_visibility = [ + "//visibility:public", + ], licenses = ["notice"], ) diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/proto/metadata.proto b/java/com/google/android/libraries/mobiledatadownload/internal/proto/metadata.proto index 406133c..9fabf90 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/proto/metadata.proto +++ b/java/com/google/android/libraries/mobiledatadownload/internal/proto/metadata.proto @@ -573,21 +573,21 @@ message SharedFile { } // Metadata used by -// com.google.android.libraries.mdi.download.MobileDataDownloadManager +// com.google.android.libraries.mobiledatadownload.MobileDataDownloadManager message MobileDataDownloadManagerMetadata { optional bool mdd_migrated_to_offroad = 1; optional int32 reset_trigger = 2; } // Metadata used by -// com.google.android.libraries.mdi.download.SharedFileManager +// com.google.android.libraries.mobiledatadownload.SharedFileManager message SharedFileManagerMetadata { optional bool migrated_to_new_file_key = 1; optional int64 next_file_name = 2; } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.Migrations +// com.google.android.libraries.mobiledatadownload.internal.Migrations message MigrationsStore { enum FileKeyVersion { NEW_FILE_KEY = 0; @@ -599,7 +599,7 @@ message MigrationsStore { } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.FileGroupsMetadata +// com.google.android.libraries.mobiledatadownload.internal.FileGroupsMetadata message FileGroupsMetadataStore { // Key must be a serialized GroupKey. map data_file_groups = 1; @@ -609,7 +609,7 @@ message FileGroupsMetadataStore { } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.SharedFilesMetadata +// com.google.android.libraries.mobiledatadownload.internal.SharedFilesMetadata message SharedFilesMetadataStore { // The key must be a serialized NewFileKey. map shared_files = 1; diff --git a/java/com/google/android/libraries/mobiledatadownload/internal/util/FileGroupsMetadataUtil.java b/java/com/google/android/libraries/mobiledatadownload/internal/util/FileGroupsMetadataUtil.java index 2948df6..f64ac06 100644 --- a/java/com/google/android/libraries/mobiledatadownload/internal/util/FileGroupsMetadataUtil.java +++ b/java/com/google/android/libraries/mobiledatadownload/internal/util/FileGroupsMetadataUtil.java @@ -20,9 +20,9 @@ import android.util.Base64; import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; -import com.google.protobuf.InvalidProtocolBufferException; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; +import com.google.protobuf.InvalidProtocolBufferException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; diff --git a/java/com/google/android/libraries/mobiledatadownload/lite/BUILD b/java/com/google/android/libraries/mobiledatadownload/lite/BUILD index 4f8c1f5..9d978ac 100644 --- a/java/com/google/android/libraries/mobiledatadownload/lite/BUILD +++ b/java/com/google/android/libraries/mobiledatadownload/lite/BUILD @@ -44,6 +44,7 @@ android_library( "//java/com/google/android/libraries/mobiledatadownload/internal/util:DownloadFutureMap", "//java/com/google/android/libraries/mobiledatadownload/tracing", "//java/com/google/android/libraries/mobiledatadownload/tracing:concurrent", + "@androidx_annotation_annotation", "@androidx_core_core", "@com_google_auto_value", "@com_google_errorprone_error_prone_annotations", diff --git a/java/com/google/android/libraries/mobiledatadownload/lite/DownloaderImpl.java b/java/com/google/android/libraries/mobiledatadownload/lite/DownloaderImpl.java index 8472667..75bafd3 100644 --- a/java/com/google/android/libraries/mobiledatadownload/lite/DownloaderImpl.java +++ b/java/com/google/android/libraries/mobiledatadownload/lite/DownloaderImpl.java @@ -438,7 +438,7 @@ final class DownloaderImpl implements Downloader { notificationManager.notify(notificationKey, notification.build()); } else { NotificationUtil.cancelNotificationForKey( - context, downloadRequest.destinationFileUri().toString()); + context, foregroundDownloadKey.toString()); } return immediateVoidFuture(); diff --git a/java/com/google/android/libraries/mobiledatadownload/populator/proto/BUILD b/java/com/google/android/libraries/mobiledatadownload/populator/proto/BUILD index 637afee..240b864 100644 --- a/java/com/google/android/libraries/mobiledatadownload/populator/proto/BUILD +++ b/java/com/google/android/libraries/mobiledatadownload/populator/proto/BUILD @@ -14,7 +14,7 @@ package( default_applicable_licenses = ["//:license"], default_visibility = [ - "//:__subpackages__", + "//visibility:public", ], licenses = ["notice"], ) @@ -23,7 +23,6 @@ proto_library( name = "metadata_proto", srcs = ["metadata.proto"], cc_api_version = 2, - deps = [], alwayslink = 1, ) diff --git a/javatests/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLoggerTest.java b/javatests/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLoggerTest.java index 468f7fe..0c94bb2 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLoggerTest.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/internal/logging/MddEventLoggerTest.java @@ -16,13 +16,16 @@ package com.google.android.libraries.mobiledatadownload.internal.logging; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import android.content.Context; + import androidx.test.core.app.ApplicationProvider; + import com.google.android.libraries.mobiledatadownload.Logger; import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; import com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies; @@ -36,8 +39,7 @@ import com.google.mobiledatadownload.LogProto.MddDeviceInfo; import com.google.mobiledatadownload.LogProto.MddDownloadResultLog; import com.google.mobiledatadownload.LogProto.MddLogData; import com.google.mobiledatadownload.LogProto.StableSamplingInfo; -import java.security.SecureRandom; -import java.util.Random; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -47,226 +49,233 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import java.security.SecureRandom; +import java.util.Random; + @RunWith(RobolectricTestRunner.class) public class MddEventLoggerTest { - @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - - private static final int SOME_MODULE_VERSION = 42; - private static final int SAMPLING_ALWAYS = 1; - private static final int SAMPLING_NEVER = 0; - - @Mock private Logger mockLogger; - private MddEventLogger mddEventLogger; - - private final Context context = ApplicationProvider.getApplicationContext(); - private final TestFlags flags = new TestFlags(); - - @Before - public void setUp() throws Exception { - mddEventLogger = - new MddEventLogger( - context, - mockLogger, - SOME_MODULE_VERSION, - new LogSampler(flags, new SecureRandom()), - flags); - mddEventLogger.setLoggingStateStore( - MddTestDependencies.LoggingStateStoreImpl.SHARED_PREFERENCES.loggingStateStore( - context, Optional.absent(), new FakeTimeSource(), directExecutor(), new Random(0))); - } - - private MddLogData.Builder newLogDataBuilderWithClientInfo() { - return MddLogData.newBuilder() - .setAndroidClientInfo( - AndroidClientInfo.newBuilder() - .setModuleVersion(SOME_MODULE_VERSION) - .setHostPackageName(context.getPackageName())); - } - - @Test - public void testSampleInterval_zero_none() { - assertFalse(LogUtil.shouldSampleInterval(0)); - } - - @Test - public void testSampleInterval_negative_none() { - assertFalse(LogUtil.shouldSampleInterval(-1)); - } - - @Test - public void testSampleInterval_always() { - assertTrue(LogUtil.shouldSampleInterval(1)); - } - - @Test - public void testLogMddEvents_noLog() { - overrideDefaultSampleInterval(SAMPLING_NEVER); - - mddEventLogger.logEventSampled( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, - "fileGroup", - /* fileGroupVersionNumber= */ 0, - /* buildId= */ 0, - /* variantId= */ ""); - verifyNoInteractions(mockLogger); - } - - @Test - public void testLogMddEvents() { - overrideDefaultSampleInterval(SAMPLING_ALWAYS); - mddEventLogger.logEventSampled( - MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, - "fileGroup", - /* fileGroupVersionNumber= */ 1, - /* buildId= */ 123, - /* variantId= */ "testVariant"); - - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setSamplingInterval(SAMPLING_ALWAYS) - .setDataDownloadFileGroupStats( + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + private static final int SOME_MODULE_VERSION = 42; + private static final int SAMPLING_ALWAYS = 1; + private static final int SAMPLING_NEVER = 0; + + @Mock + private Logger mockLogger; + private MddEventLogger mddEventLogger; + + private final Context context = ApplicationProvider.getApplicationContext(); + private final TestFlags flags = new TestFlags(); + + @Before + public void setUp() throws Exception { + mddEventLogger = + new MddEventLogger( + context, + mockLogger, + SOME_MODULE_VERSION, + new LogSampler(flags, new SecureRandom()), + flags); + mddEventLogger.setLoggingStateStore( + MddTestDependencies.LoggingStateStoreImpl.SHARED_PREFERENCES.loggingStateStore( + context, Optional.absent(), new FakeTimeSource(), directExecutor(), + new Random(0))); + } + + private MddLogData.Builder newLogDataBuilderWithClientInfo() { + return MddLogData.newBuilder() + .setAndroidClientInfo( + AndroidClientInfo.newBuilder() + .setModuleVersion(SOME_MODULE_VERSION) + .setHostPackageName(context.getPackageName())); + } + + @Test + public void testSampleInterval_zero_none() { + assertFalse(LogUtil.shouldSampleInterval(0)); + } + + @Test + public void testSampleInterval_negative_none() { + assertFalse(LogUtil.shouldSampleInterval(-1)); + } + + @Test + public void testSampleInterval_always() { + assertTrue(LogUtil.shouldSampleInterval(1)); + } + + @Test + public void testLogMddEvents_noLog() { + overrideDefaultSampleInterval(SAMPLING_NEVER); + + mddEventLogger.logEventSampled( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, + "fileGroup", + /* fileGroupVersionNumber= */ 0, + /* buildId= */ 0, + /* variantId= */ ""); + verifyNoInteractions(mockLogger); + } + + @Test + public void testLogMddEvents() { + overrideDefaultSampleInterval(SAMPLING_ALWAYS); + mddEventLogger.logEventSampled( + MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, + "fileGroup", + /* fileGroupVersionNumber= */ 1, + /* buildId= */ 123, + /* variantId= */ "testVariant"); + + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setSamplingInterval(SAMPLING_ALWAYS) + .setDataDownloadFileGroupStats( + DataDownloadFileGroupStats.newBuilder() + .setFileGroupName("fileGroup") + .setFileGroupVersionNumber(1) + .setBuildId(123) + .setVariantId("testVariant")) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); + } + + @Test + public void testLogExpirationHandlerRemoveUnaccountedFilesSampled() { + final int unaccountedFileCount = 5; + overrideDefaultSampleInterval(SAMPLING_ALWAYS); + mddEventLogger.logMddDataDownloadFileExpirationEvent(0, unaccountedFileCount); + + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setSamplingInterval(SAMPLING_ALWAYS) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); + } + + @Test + public void testLogMddNetworkSavingsSampled() { + overrideDefaultSampleInterval(SAMPLING_ALWAYS); + DataDownloadFileGroupStats icingDataDownloadFileGroupStats = DataDownloadFileGroupStats.newBuilder() - .setFileGroupName("fileGroup") - .setFileGroupVersionNumber(1) - .setBuildId(123) - .setVariantId("testVariant")) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); - } - - @Test - public void testLogExpirationHandlerRemoveUnaccountedFilesSampled() { - final int unaccountedFileCount = 5; - overrideDefaultSampleInterval(SAMPLING_ALWAYS); - mddEventLogger.logMddDataDownloadFileExpirationEvent(0, unaccountedFileCount); - - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setSamplingInterval(SAMPLING_ALWAYS) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); - } - - @Test - public void testLogMddNetworkSavingsSampled() { - overrideDefaultSampleInterval(SAMPLING_ALWAYS); - DataDownloadFileGroupStats icingDataDownloadFileGroupStats = - DataDownloadFileGroupStats.newBuilder() - .setFileGroupName("fileGroup") - .setFileGroupVersionNumber(1) - .build(); - mddEventLogger.logMddNetworkSavings( - icingDataDownloadFileGroupStats, 0, 200L, 100L, "file-id", 1); - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setSamplingInterval(SAMPLING_ALWAYS) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); - } - - @Test - public void testLogMddDownloadResult() { - overrideDefaultSampleInterval(SAMPLING_ALWAYS); - DataDownloadFileGroupStats icingDataDownloadFileGroupStats = - DataDownloadFileGroupStats.newBuilder() - .setFileGroupName("fileGroup") - .setFileGroupVersionNumber(1) - .build(); - mddEventLogger.logMddDownloadResult( - MddDownloadResult.Code.LOW_DISK_ERROR, icingDataDownloadFileGroupStats); - - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setSamplingInterval(SAMPLING_ALWAYS) - .setMddDownloadResultLog( - MddDownloadResultLog.newBuilder() - .setResult(MddDownloadResult.Code.LOW_DISK_ERROR) - .setDataDownloadFileGroupStats(icingDataDownloadFileGroupStats)) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG_VALUE); - } - - @Test - public void testLogMddUsageEvent() { - overrideDefaultSampleInterval(SAMPLING_ALWAYS); - - DataDownloadFileGroupStats icingDataDownloadFileGroupStats = - DataDownloadFileGroupStats.newBuilder() - .setFileGroupName("fileGroup") - .setFileGroupVersionNumber(1) - .setBuildId(123) - .setVariantId("variant-id") - .build(); - - Void usageEventLog = null; - - mddEventLogger.logMddUsageEvent(icingDataDownloadFileGroupStats, usageEventLog); - - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setDataDownloadFileGroupStats(icingDataDownloadFileGroupStats) - .setSamplingInterval(SAMPLING_ALWAYS) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); - } - - @Test - public void testlogMddLibApiResultLog() { - overrideApiLoggingSampleInterval(SAMPLING_ALWAYS); - - DataDownloadFileGroupStats icingDataDownloadFileGroupStats = - DataDownloadFileGroupStats.newBuilder() - .setFileGroupName("fileGroup") - .setFileGroupVersionNumber(1) - .build(); - - Void mddLibApiResultLog = null; - mddEventLogger.logMddLibApiResultLog(mddLibApiResultLog); - - MddLogData expectedData = - newLogDataBuilderWithClientInfo() - .setSamplingInterval(SAMPLING_ALWAYS) - .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) - .setStableSamplingInfo(getStableSamplingInfo()) - .build(); - - verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); - } - - private void overrideDefaultSampleInterval(int sampleInterval) { - flags.mddDefaultSampleInterval = Optional.of(sampleInterval); - } - - private void overrideApiLoggingSampleInterval(int sampleInterval) { - flags.apiLoggingSampleInterval = Optional.of(sampleInterval); - } - - private StableSamplingInfo getStableSamplingInfo() { - if (flags.enableRngBasedDeviceStableSampling()) { - return StableSamplingInfo.newBuilder() - .setStableSamplingUsed(true) - .setStableSamplingFirstEnabledTimestampMs(0) - .setPartOfAlwaysLoggingGroup(false) - .setInvalidSamplingRateUsed(false) - .build(); + .setFileGroupName("fileGroup") + .setFileGroupVersionNumber(1) + .build(); + mddEventLogger.logMddNetworkSavings( + icingDataDownloadFileGroupStats, 0, 200L, 100L, "file-id", 1); + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setSamplingInterval(SAMPLING_ALWAYS) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); + } + + @Test + public void testLogMddDownloadResult() { + overrideDefaultSampleInterval(SAMPLING_ALWAYS); + DataDownloadFileGroupStats icingDataDownloadFileGroupStats = + DataDownloadFileGroupStats.newBuilder() + .setFileGroupName("fileGroup") + .setFileGroupVersionNumber(1) + .build(); + mddEventLogger.logMddDownloadResult( + MddDownloadResult.Code.LOW_DISK_ERROR, icingDataDownloadFileGroupStats); + + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setSamplingInterval(SAMPLING_ALWAYS) + .setMddDownloadResultLog( + MddDownloadResultLog.newBuilder() + .setResult(MddDownloadResult.Code.LOW_DISK_ERROR) + .setDataDownloadFileGroupStats( + icingDataDownloadFileGroupStats)) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG_VALUE); + } + + @Test + public void testLogMddUsageEvent() { + overrideDefaultSampleInterval(SAMPLING_ALWAYS); + + DataDownloadFileGroupStats icingDataDownloadFileGroupStats = + DataDownloadFileGroupStats.newBuilder() + .setFileGroupName("fileGroup") + .setFileGroupVersionNumber(1) + .setBuildId(123) + .setVariantId("variant-id") + .build(); + + Void usageEventLog = null; + + mddEventLogger.logMddUsageEvent(icingDataDownloadFileGroupStats, usageEventLog); + + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setDataDownloadFileGroupStats(icingDataDownloadFileGroupStats) + .setSamplingInterval(SAMPLING_ALWAYS) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); } - return StableSamplingInfo.newBuilder().setStableSamplingUsed(false).build(); - } + @Test + public void testlogMddLibApiResultLog() { + overrideApiLoggingSampleInterval(SAMPLING_ALWAYS); + + DataDownloadFileGroupStats icingDataDownloadFileGroupStats = + DataDownloadFileGroupStats.newBuilder() + .setFileGroupName("fileGroup") + .setFileGroupVersionNumber(1) + .build(); + + Void mddLibApiResultLog = null; + mddEventLogger.logMddLibApiResultLog(mddLibApiResultLog); + + MddLogData expectedData = + newLogDataBuilderWithClientInfo() + .setSamplingInterval(SAMPLING_ALWAYS) + .setDeviceInfo(MddDeviceInfo.newBuilder().setDeviceStorageLow(false)) + .setStableSamplingInfo(getStableSamplingInfo()) + .build(); + + verify(mockLogger).log(expectedData, MddClientEvent.Code.EVENT_CODE_UNSPECIFIED_VALUE); + } + + private void overrideDefaultSampleInterval(int sampleInterval) { + flags.mddDefaultSampleInterval = Optional.of(sampleInterval); + } + + private void overrideApiLoggingSampleInterval(int sampleInterval) { + flags.apiLoggingSampleInterval = Optional.of(sampleInterval); + } + + private StableSamplingInfo getStableSamplingInfo() { + if (flags.enableRngBasedDeviceStableSampling()) { + return StableSamplingInfo.newBuilder() + .setStableSamplingUsed(true) + .setStableSamplingFirstEnabledTimestampMs(0) + .setPartOfAlwaysLoggingGroup(false) + .setInvalidSamplingRateUsed(false) + .build(); + } + + return StableSamplingInfo.newBuilder().setStableSamplingUsed(false).build(); + } } diff --git a/proto/BUILD b/proto/BUILD index 09df231..7b46203 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -2,7 +2,9 @@ load("//third_party/bazel_rules/rules_java/java:defs.bzl", "java_proto_library") package( default_applicable_licenses = ["//:license"], - default_visibility = ["//visibility:public"], + default_visibility = [ + "//visibility:public", + ], licenses = ["notice"], ) @@ -20,6 +22,11 @@ java_lite_proto_library( deps = [":client_config_proto"], ) +java_proto_library( + name = "client_config_java_proto", + deps = [":client_config_proto"], +) + proto_library( name = "download_config_proto", srcs = ["download_config.proto"], @@ -41,6 +48,11 @@ java_lite_proto_library( deps = [":download_config_proto"], ) +java_proto_library( + name = "download_config_java_proto", + deps = [":download_config_proto"], +) + proto_library( name = "transform_proto", srcs = ["transform.proto"], diff --git a/proto/client_config.proto b/proto/client_config.proto index f6260c7..e5c0034 100644 --- a/proto/client_config.proto +++ b/proto/client_config.proto @@ -13,11 +13,10 @@ // limitations under the License. syntax = "proto2"; -package com.google.android.libraries.mdi.download; +package com.google.android.libraries.mobiledatadownload; import "google/protobuf/any.proto"; -//option jspb_use_correct_proto2_semantics = false; // option java_package = "com.google.mobiledatadownload"; option java_outer_classname = "ClientConfigProto"; option objc_class_prefix = "ICN"; diff --git a/proto/download_config.proto b/proto/download_config.proto index 1b88c23..ec4478a 100644 --- a/proto/download_config.proto +++ b/proto/download_config.proto @@ -21,7 +21,6 @@ import "transform.proto"; option java_package = "com.google.mobiledatadownload"; option java_outer_classname = "DownloadConfigProto"; option objc_class_prefix = "Icing"; -//option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See . // The top-level proto for Mobile Data Download (). message DownloadConfig { @@ -530,8 +529,6 @@ message ManifestConfig { // prefix encoding, however, for the S2CellIds the high-order bits // encode the face-ID and as a result we often end up with large // numbers. -// optional fixed64 s2_cell_id = 1 [ -// (datapol.semantic_type) = ST_LOCATION optional fixed64 s2_cell_id = 1; } diff --git a/proto/log_enums.proto b/proto/log_enums.proto index a86c611..2f8d93c 100644 --- a/proto/log_enums.proto +++ b/proto/log_enums.proto @@ -43,13 +43,42 @@ message MddClientEvent { // Logged with DataDownloadFileGroupStats, MddFileGroupStatus. DATA_DOWNLOAD_FILE_GROUP_STATUS = 1044; + // MDD download result log. + DATA_DOWNLOAD_RESULT_LOG = 1068; + // Log MddStorageStats in daily maintenance. DATA_DOWNLOAD_STORAGE_STATS = 1055; - // MDD download result log. - DATA_DOWNLOAD_RESULT_LOG = 1068; + // Log event for MDD Lib api result. + DATA_DOWNLOAD_LIB_API_RESULT = 1108; + + // Log MddNetworkStats in daily maintenance. + DATA_DOWNLOAD_NETWORK_STATS = 1056; + + // File group download started. + DATA_DOWNLOAD_STARTED = 1070; - reserved 1000 to 1043, 1045 to 1054, 1056 to 1067, 1069 to 1113; + // File group download complete. + DATA_DOWNLOAD_COMPLETE = 1007; + + // The log event for MDD download latency. + DATA_DOWNLOAD_LATENCY_LOG = 1080; + + // All files in the group were already available when the file group was + // added. + DATA_DOWNLOAD_COMPLETE_IMMEDIATE = 1032; + + DATA_DOWNLOAD_PENDING_GROUP_REPLACED = 1115; + + reserved 1000 to 1006; + reserved 1008 to 1031; + reserved 1033 to 1043; + reserved 1045 to 1054; + reserved 1057 to 1067; + reserved 1069; + reserved 1071 to 1079; + reserved 1081 to 1107; + reserved 1109 to 1114; reserved 2000 to 2999, 3000 to 3999, 4000 to 4099, 4100 to 4199, 5000 to 5999, 6000 to 6999, 7000 to 7999, 8000 to 8999, 9000 to 9999, @@ -171,3 +200,77 @@ message MddDownloadResult { reserved 1000 to 3000; } } + +// Collection of MDD Lib's Public API methods used when logging the result of an +// MDD Lib API call. +message MddLibApiName { + enum Code { + UNKNOWN = 0; + + // File Group metadata management APIs. + // NOTE: These APIs will include DataDownloadFileGroupStats in their + // logs. + ADD_FILE_GROUP = 1; + GET_FILE_GROUP = 2; + REMOVE_FILE_GROUP = 3; + REPORT_USAGE = 4; + + // File Group data management APIs. + // NOTE: These APIs will include DataDownloadFileGroupStats in their + // logs. + CANCEL_FOREGROUND_DOWNLOAD = 5; + DOWNLOAD_FILE_GROUP = 6; + DOWNLOAD_FILE_GROUP_WITH_FOREGROUND_SERVICE = 7; + IMPORT_FILES = 8; + + // File Group metadata bulk management APIs + // NOTE: These APIs will not include DataDownloadFileGroupStats in + // their logs. + CLEAR = 9; + GET_FILE_GROUPS_BY_FILTER = 10; + MAINTENANCE = 11; + REMOVE_FILE_GROUPS_BY_FILTER = 12; + + // File data management APIs + // NOTE: These APIs will not include DataDownloadFileGroupStats in + // their logs. + DOWNLOAD_FILE = 13; + DOWNLOAD_FILE_WITH_FOREGROUND_SERVICE = 14; + + // Task scheduling APIs. + // NOTE: These APIs will not include DataDownloadFileGroupStats in + // their logs. + HANDLE_TASK = 15; + SCHEDULE_PERIODIC_BACKGROUND_TASKS = 16; + SYNC = 17; + + // Calls to phenotype external experiment id setting + + // NOTE: this isn't actually an MDD API but the data is in the same format. + // DataDownloadFileGroupStats will be populated when available. + PHENOTYPE_CLEAR_EXPERIMENT_IDS = 18; + PHENOTYPE_UPDATE_EXPERIMENT_IDS = 19; + PHENOTYPE_CLEAR_ALL = 20; + } +} + +// Result enum when logging the result of an MDD Lib API call. +message MddLibApiResult { + enum Code { + RESULT_UNKNOWN = 0; + RESULT_SUCCESS = 1; + + // Codes for failures + // Used for failures whose is reason is unknown. + RESULT_FAILURE = 2; + // Request cancelled + RESULT_CANCELLED = 3; + // Interrupted + RESULT_INTERRUPTED = 4; + RESULT_IO_ERROR = 5; + RESULT_ILLEGAL_STATE = 6; + RESULT_ILLEGAL_ARGUMENT = 7; + RESULT_UNSUPPORTED_OPERATION = 8; + RESULT_DOWNLOAD_ERROR = 9; + } +} diff --git a/proto/logs.proto b/proto/logs.proto index b35aa07..e3d6582 100644 --- a/proto/logs.proto +++ b/proto/logs.proto @@ -19,7 +19,6 @@ package mobiledatadownload.logs; import "log_enums.proto"; -//option jspb_use_correct_proto2_semantics = false; // option java_package = "com.google.mobiledatadownload"; option java_outer_classname = "LogProto"; @@ -182,8 +181,17 @@ message MddLogData { // MDD download result log. optional MddDownloadResultLog mdd_download_result_log = 63; - reserved 1 to 9, 11 to 20, 22 to 31, 33 to 39, 41 to 45, 47 to 50, 52 to 62, - 64 to 71, 73; + // MDD download latency log. + optional MddDownloadLatency mdd_download_latency = 67; + + // MDD Api Result event + optional MddLibApiResultLog mdd_lib_api_result_log = 71; + + // MDD File Group Network Stats. Additional info necessary for Network Stats. + optional MddNetworkStats mdd_network_stats = 49; + + reserved 1 to 9, 11 to 20, 22 to 31, 33 to 39, 41 to 45, 47 to 48, 50, + 52 to 62, 64 to 66, 68 to 70, 73; } // Info on sampling method used for log events. Stable sampling means if a @@ -268,4 +276,74 @@ message MddStorageStats { // // See for more info. optional int32 days_since_last_log = 6; +} + +// MDD download latency log. +// Next tag: 4 +message MddDownloadLatency { + // The number of download attempts needed to fully download the file group. + optional int32 download_attempt_count = 1; + // The download latency in milliseconds, which is the time elapsed between + // download started and download complete. + optional int64 download_latency_ms = 2; + // The total MDD download latency in milliseconds, which is the time elapsed + // between new config received from P/H and download complete. + // True E2E download latency = PH propagation latency + MDD total download + // latency. Here we are talking about the later. + optional int64 total_latency_ms = 3; +} + +// MDD Lib API result log. +// This log will be generated for each MDD Lib API call. +// +// Next tag: 5 +message MddLibApiResultLog { + // The API which generated this result. + optional MddLibApiName.Code api_used = 1; + + // The result of the API call. + optional MddLibApiResult.Code result = 2; + + // Will be populated with relevant file group details depending on the api + // type. See MddLibApiName for more details. + repeated DataDownloadFileGroupStats data_download_file_group_stats = 3; + + // The latency in nano seconds. + optional int64 latency_ns = 4; +} + +// MDD File Group Network stats. +message MddGroupNetworkStats { + optional DataDownloadFileGroupStats data_download_file_group_stats = 1; + + // The total bytes downloaded through Wifi by the file group. + optional int64 total_wifi_bytes = 2; + + // The total bytes downloaded through Cellular by the file group. + optional int64 total_cellular_bytes = 3; + + // The total bytes downloaded through ways other than wifi or Cellular by the + // file group. E.g. import from local storage & etc. + optional int64 total_other_bytes = 4; +} + +// MDD Network stats +message MddNetworkStats { + message GroupStats { + optional DataDownloadFileGroupStats data_download_file_group_stats = 1; + + // The total bytes downloaded through Wifi by the file group. + optional uint64 total_wifi_bytes = 2; + + // The total bytes downloaded through Cellular by the file group. + optional uint64 total_cellular_bytes = 3; + } + + repeated GroupStats group_stats = 1; + + // Total bytes downloaded by all MDD file groups through Wifi. + optional uint64 total_mdd_wifi_bytes = 2; + + // Total bytes downloaded by all MDD file groups through Cellular. + optional uint64 total_mdd_cellular_bytes = 3; } \ No newline at end of file diff --git a/proto/metadata.proto b/proto/metadata.proto index 6e6d8dc..4082a40 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -573,21 +573,21 @@ message SharedFile { } // Metadata used by -// com.google.android.libraries.mdi.download.MobileDataDownloadManager +// com.google.android.libraries.mobiledatadownload.MobileDataDownloadManager message MobileDataDownloadManagerMetadata { optional bool mdd_migrated_to_offroad = 1; optional int32 reset_trigger = 2; } // Metadata used by -// com.google.android.libraries.mdi.download.SharedFileManager +// com.google.android.libraries.mobiledatadownload.SharedFileManager message SharedFileManagerMetadata { optional bool migrated_to_new_file_key = 1; optional int64 next_file_name = 2; } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.Migrations +// com.google.android.libraries.mobiledatadownload.internal.Migrations message MigrationsStore { enum FileKeyVersion { NEW_FILE_KEY = 0; @@ -599,7 +599,7 @@ message MigrationsStore { } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.FileGroupsMetadata +// com.google.android.libraries.mobiledatadownload.internal.FileGroupsMetadata message FileGroupsMetadataStore { // Key must be a serialized GroupKey. map data_file_groups = 1; @@ -609,7 +609,7 @@ message FileGroupsMetadataStore { } // Collects all data used by -// com.google.android.libraries.mdi.download.internal.SharedFilesMetadata +// com.google.android.libraries.mobiledatadownload.internal.SharedFilesMetadata message SharedFilesMetadataStore { // The key must be a serialized NewFileKey. map shared_files = 1; @@ -641,7 +641,7 @@ message NewFileKey { optional string checksum = 3; optional DataFileGroupInternal.AllowedReaders allowed_readers = 4; optional mobstore.proto.Transforms download_transforms = 5 - [deprecated = true]; + [deprecated = true]; } // This proto is used to store state for logging. See details at -- cgit v1.2.3