1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.libraries.mobiledatadownload; 17 18 import com.google.android.libraries.mobiledatadownload.TaskScheduler.ConstraintOverrides; 19 import com.google.common.base.Optional; 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.util.concurrent.Futures; 22 import com.google.common.util.concurrent.ListenableFuture; 23 import com.google.errorprone.annotations.CanIgnoreReturnValue; 24 import com.google.errorprone.annotations.CheckReturnValue; 25 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup; 26 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; 27 import java.util.Map; 28 29 /** The root object and entry point for the MobileDataDownload library. */ 30 public interface MobileDataDownload { 31 32 /** 33 * Adds for download the data file group in {@link AddFileGroupRequest}, after running validation 34 * on the group. This group will replace any previous version of this group once it is downloaded. 35 * 36 * <p>This api takes {@link AddFileGroupRequest} that contains data file group, and it can be used 37 * to set extra params such as account. 38 * 39 * <p>This doesn't start the download right away. The download starts later when the tasks 40 * scheduled via {@link #schedulePeriodicTasks} are run. 41 * 42 * <p>Calling this api with the exact same parameters multiple times is a no-op. 43 * 44 * @param addFileGroupRequest The request to add file group in MDD. 45 * @return ListenableFuture of true if the group was successfully added, or the group was already 46 * present; ListenableFuture of false if the group is invalid or an I/O error occurs. 47 */ addFileGroup(AddFileGroupRequest addFileGroupRequest)48 ListenableFuture<Boolean> addFileGroup(AddFileGroupRequest addFileGroupRequest); 49 50 /** 51 * Removes all versions of the data file group that matches {@link RemoveFileGroupRequest} from 52 * MDD. If no data file group matches, this call is a no-op. 53 * 54 * <p>This api takes {@link RemoveFileGroupRequest} that contains data file group, and it can be 55 * used to set extra params such as account. 56 * 57 * @param removeFileGroupRequest The request to remove file group from MDD. 58 * @return Listenable of true if the group was successfully removed, or no group matches; 59 * Listenable of false if the matching group fails to be removed. 60 */ removeFileGroup(RemoveFileGroupRequest removeFileGroupRequest)61 ListenableFuture<Boolean> removeFileGroup(RemoveFileGroupRequest removeFileGroupRequest); 62 63 /** 64 * Removes all versions of the data file groups that match {@link RemoveFileGroupsByFilterRequest} 65 * from MDD. If no data file group matches, this call is a no-op. 66 * 67 * <p>This api takes a {@link RemoveFileGroupsByFilterRequest} that contains optional filters for 68 * the group name, group source, associated account, etc. 69 * 70 * <p>A resolved future will only be returned if the removal completes successfully for all 71 * matching file groups. If any failures occur during this method, it will return a failed future 72 * with an {@link AggregateException} containing the failures that occurred. 73 * 74 * <p>NOTE: This only removes the metadata from MDD, not file content. Downloaded files that are 75 * no longer needed are deleted during MDD's daily maintenance task. 76 * 77 * @param removeFileGroupsByFilterRequest The request to remove file group from MDD. 78 * @return ListenableFuture that resolves with {@link RemoveFileGroupsByFilterResponse}, or fails 79 * with {@link AggregateException} 80 */ removeFileGroupsByFilter( RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest)81 ListenableFuture<RemoveFileGroupsByFilterResponse> removeFileGroupsByFilter( 82 RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest); 83 84 /** 85 * Gets the file group definition that was added to MDD. This API cannot be used to access files, 86 * but it can be accessed by populators to manipulate the existing file group state - eg, to 87 * rename a file group, or otherwise migrate from one format to another. 88 * 89 * @return DataFileGroup if downloaded file group is found, otherwise a failing LF. 90 */ readDataFileGroup( ReadDataFileGroupRequest readDataFileGroupRequest)91 default ListenableFuture<DataFileGroup> readDataFileGroup( 92 ReadDataFileGroupRequest readDataFileGroupRequest) { 93 throw new UnsupportedOperationException(); 94 } 95 96 /** 97 * Gets DataFileGroup definitions that were added to MDD by filter. This API cannot be used to 98 * access files. 99 * 100 * <p>Only present fields in {@link ReadDataFileGroupsByFilterRequest} will be used to perform the 101 * filtering. For example, if no account is specified in the filter, file groups won't be filtered 102 * based on account. 103 * 104 * @param readDataFileGroupsByFilterRequest The request to get multiple data file groups after 105 * filtering. 106 * @return The ListenableFuture that will resolve to a list of the requested data file groups. 107 * This ListenableFuture will resolve to all data file groups when {@code 108 * readDataFileGroupsByFilterRequest.includeAllGroups} is true. 109 */ readDataFileGroupsByFilter( ReadDataFileGroupsByFilterRequest readDataFileGroupsByFilterRequest)110 default ListenableFuture<ImmutableList<DataFileGroup>> readDataFileGroupsByFilter( 111 ReadDataFileGroupsByFilterRequest readDataFileGroupsByFilterRequest) { 112 throw new UnsupportedOperationException(); 113 } 114 115 /** 116 * Returns the latest downloaded data that we have for the given group name. 117 * 118 * <p>This api takes an instance of {@link GetFileGroupRequest} that contains group name, and it 119 * can be used to set extra params such as account. 120 * 121 * <p>This listenable future will return null if no group exists or has been downloaded for the 122 * given group name. 123 * 124 * <p>Note: getFileGroup returns a snapshot of the latest state, but it's possible for the state 125 * to change between a getFileGroup call and accessing the files if the ClientFileGroup gets 126 * cached. Caching the returned ClientFileGroup is therefore discouraged. 127 * 128 * @param getFileGroupRequest The request to get a single file group. 129 * @return The ListenableFuture of requested client file group for the given request. 130 */ getFileGroup(GetFileGroupRequest getFileGroupRequest)131 ListenableFuture<ClientFileGroup> getFileGroup(GetFileGroupRequest getFileGroupRequest); 132 133 /** 134 * Returns all the data that we have for the given {@link GetFileGroupsByFilterRequest}. 135 * 136 * <p>This listenable future will return a list of file groups with their current download status. 137 * 138 * <p>Only present fields in {@link GetFileGroupsByFilterRequest} will be used to perform the 139 * filtering, i.e. when no account is specified in the filter, file groups won't be filtered based 140 * on account. 141 * 142 * <p>Note: getFileGroupsByFilter returns a snapshot of the latest state, but it's possible for 143 * the state to change between a getFileGroupsByFilter call and accessing the files if the 144 * ClientFileGroup gets cached. Caching the returned ClientFileGroup is therefore discouraged. 145 * 146 * @param getFileGroupsByFilterRequest The request to get multiple file groups after filtering. 147 * @return The ListenableFuture that will resolve to a list of the requested client file groups, 148 * including pending and downloaded versions; this ListenableFuture will resolve to all client 149 * file groups when {@code getFileGroupsByFilterRequest.includeAllGroups} is true. 150 */ getFileGroupsByFilter( GetFileGroupsByFilterRequest getFileGroupsByFilterRequest)151 ListenableFuture<ImmutableList<ClientFileGroup>> getFileGroupsByFilter( 152 GetFileGroupsByFilterRequest getFileGroupsByFilterRequest); 153 154 /** 155 * Imports Inline Files into an Existing MDD File Group. 156 * 157 * <p>This api takes a {@link ImportFilesRequest} containing identifying information about an 158 * existing File Group, an optional list of {@link DataFile}s to import into the existing File 159 * Group, and a Map of file content to import into MDD. 160 * 161 * <p>The identifying information is used to identify a file group and its specific version. This 162 * prevents the caller from accidentally importing files into the wrong file group or the wrong 163 * version of the file group. An optional {@link Account} parameter can also be specified if the 164 * existing file group was associated with an account. 165 * 166 * <p>The given {@link DataFile} list allows updated files (still compatible with a given file 167 * group version) to be imported into MDD. This API wll merge the given DataFiles into the 168 * existing file group in the following manner: 169 * 170 * <ul> 171 * <li>DataFiles included in the DataFile list but not the existing file group will be added as 172 * new DataFiles 173 * <li>DataFiles included in the DataFile list will replace DataFiles in the existing file group 174 * if their file Ids match. 175 * <li>DataFiles included in the existing file group but not the DataFile list will remain 176 * untouched. 177 * </ul> 178 * 179 * <p>{@link ImportFilesRequest} also requires a Map of file sources that should be imported by 180 * MDD. The Map is keyed by the fileIds of DataFiles and contains the contents of the file to 181 * import within a {@link ByteString}. This Map must contains an entry for all {@link DataFile}s 182 * which require an inline file source. Only "Inline" {@link DataFile}s should be included in this 183 * map (see details below). 184 * 185 * <p>An inline {@link DataFile} is the same as a standard {@link DataFile}, but instead of an 186 * "https" url, the url should match the following format: 187 * 188 * <pre>{@code "inlinefile:<key>"}</pre> 189 * 190 * <p>Where {@code key} is a unique identifier of the file. In most cases, the checksum should be 191 * used as this key. If the checksum is not used, another unique identifier should be used to 192 * allow proper deduping of the file import within MDD. 193 * 194 * <p>Example inline file url: 195 * 196 * <pre>{@code inlinefile:sha1:9a4ea3ca81d3f1d631531cbc216a62d9b10509ee}</pre> 197 * 198 * <p>NOTE: Inline files can be specified by the given DataFile list in {@link 199 * ImportFilesRequest}, but can also be specified by a {@link DataFileGroup} added via {@link 200 * #addFileGroup}. A File Group that contains inline files will not be considered DOWNLOADED until 201 * all inline files are imported via this API. 202 * 203 * <p>Because this method performs an update to the stored File Group metadata, the given {@link 204 * ImportFilesRequest} must satisfy the following conditions: 205 * 206 * <ul> 207 * <li>The requests identifying information must match an existing File Group 208 * <li>All inline DataFiles must have file content specified in the request's Inline File Map 209 * </ul> 210 * 211 * <p>If either of these conditions is not met, this operation will return a failed 212 * ListenableFuture. 213 * 214 * <p>Finally, this API is a atomic operation. That is, <em>either all inline files will be 215 * imported successfully or none will be imported</em>. If there is a failure with importing a 216 * file, MDD will not update the file group (i.e. future calls to {@link #getFileGroup} will 217 * return the same {@link ClientFileGroup} as before this call). 218 * 219 * @param importFilesRequest Request containing required parameters to perform import files 220 * operation. 221 * @return ListenableFuture that resolves when all inline files are successfully imported. 222 */ importFiles(ImportFilesRequest importFilesRequest)223 ListenableFuture<Void> importFiles(ImportFilesRequest importFilesRequest); 224 225 /** 226 * Downloads a single file. 227 * 228 * <p>This api takes {@link SingleFileDownloadRequest}, which contains a download url of the file 229 * to download. the destination location on device must also be specified. See 230 * SingleFileDownloadRequest for full list of required/optional parameters. 231 * 232 * <p>The returned ListenableFuture will fail if there is an error during the download. The caller 233 * is responsible for calling downloadFile again to restart the download. 234 * 235 * <p>The caller can be notified of progress by providing a {@link SingleFileDownloadListener}. 236 * This listener will also provide callbacks for a completed download, failed download, or paused 237 * download due to connectivity loss. 238 * 239 * <p>The caller can specify constraints that should be used for the download by providing a 240 * {@link com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints}. This 241 * allows downloads to only start when on Wifi, for example. By default, no constraints are 242 * specified. 243 * 244 * @param singleFileDownloadRequest The request to download a file. 245 * @return ListenableFuture that resolves when file is downloaded. 246 */ 247 @CheckReturnValue downloadFile(SingleFileDownloadRequest singleFileDownloadRequest)248 ListenableFuture<Void> downloadFile(SingleFileDownloadRequest singleFileDownloadRequest); 249 250 /** 251 * Downloads and returns the latest downloaded data that we have for the given group name. 252 * 253 * <p>This api takes {@link DownloadFileGroupRequest} that contains group name, and it can be used 254 * to set extra params such as account, download conditions, and download listener. 255 * 256 * <p>The group name must be added using {@link #addFileGroup} before downloading the file group. 257 * 258 * <p>The returned ListenableFuture will be resolved when the file group is downloaded. It can 259 * also be used to cancel the download. 260 * 261 * <p>The returned ListenableFuture would fail if there is any error during the download. Client 262 * is responsible to call the downloadFileGroup to resume the download. 263 * 264 * <p>Download progress is supported through the DownloadListener. 265 * 266 * <p>To download under any conditions, clients should use {@link 267 * Constants.NO_RESTRICTIONS_DOWNLOAD_CONDITIONS} 268 * 269 * @param downloadFileGroupRequest The request to download file group. 270 */ downloadFileGroup( DownloadFileGroupRequest downloadFileGroupRequest)271 ListenableFuture<ClientFileGroup> downloadFileGroup( 272 DownloadFileGroupRequest downloadFileGroupRequest); 273 274 /** 275 * Downloads a file using a foreground service and notification. 276 * 277 * <p>This is similar to {@link #downloadFile}, but allows the download to continue running when 278 * the app enters the background. 279 * 280 * <p>The notification created for the download includes a cancel action. This will allow the 281 * download to be cancelled even when the app is in the background. 282 * 283 * <p>The cancel action in the notification menu requires the ForegroundService to be registered 284 * with the application (via the AndroidManifest.xml). This allows the cancellation intents to be 285 * properly picked up. To register the service, the following lines must be included in the app's 286 * {@code AndroidManifest.xml}: 287 * 288 * <pre>{@code 289 * <!-- Needed by foreground download service --> 290 * <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 291 * 292 * <!-- Service for MDD foreground downloads --> 293 * <service 294 * android:name="com.google.android.libraries.mobiledatadownload.foreground.sting.ForegroundDownloadService" 295 * android:exported="false" /> 296 * }</pre> 297 * 298 * <p>NOTE: The above excerpt is for Framework and Sting apps. Dagger apps should use the same 299 * excerpt, but change the {@code android:name} property to: 300 * 301 * <pre>{@code 302 * android:name="com.google.android.libraries.mobiledatadownload.foreground.dagger.ForegroundDownloadService" 303 * }</pre> 304 */ 305 @CheckReturnValue downloadFileWithForegroundService( SingleFileDownloadRequest singleFileDownloadRequest)306 ListenableFuture<Void> downloadFileWithForegroundService( 307 SingleFileDownloadRequest singleFileDownloadRequest); 308 309 /** 310 * Download a file group and show foreground download progress in a notification. User can cancel 311 * the download from the notification menu. 312 * 313 * <p>The cancel action in the notification menu requires the ForegroundService to be registered 314 * with the application (via the AndroidManifest.xml). This allows the cancellation intents to be 315 * properly picked up. To register the service, the following lines must be included in the app's 316 * {@code AndroidManifest.xml}: 317 * 318 * <pre>{@code 319 * <!-- Needed by foreground download service --> 320 * <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 321 * 322 * <!-- Service for MDD foreground downloads --> 323 * <service 324 * android:name="com.google.android.libraries.mobiledatadownload.foreground.sting.ForegroundDownloadService" 325 * android:exported="false" /> 326 * }</pre> 327 * 328 * <p>NOTE: The above excerpt is for Framework and Sting apps. Dagger apps should use the same 329 * excerpt, but change the {@code android:name} property to: 330 * 331 * <pre>{@code 332 * android:name="com.google.android.libraries.mobiledatadownload.foreground.dagger.ForegroundDownloadService" 333 * }</pre> 334 */ downloadFileGroupWithForegroundService( DownloadFileGroupRequest downloadFileGroupRequest)335 ListenableFuture<ClientFileGroup> downloadFileGroupWithForegroundService( 336 DownloadFileGroupRequest downloadFileGroupRequest); 337 338 /** 339 * Cancel an on-going foreground download. 340 * 341 * <p>Attempts to cancel an on-going foreground download using best effort. If download is unknown 342 * to MDD, this operation is a noop. 343 * 344 * <p>The key passed here must be created using {@link ForegroundDownloadKey}, and must match the 345 * properties used from the request. Depending on which API was used to start the download, this 346 * would be {@link DownloadFileGroupRequest} for {@link SingleFileDownloadRequest}. 347 * 348 * <p><b>NOTE:</b> In most cases, clients will not need to call this -- it is meant to allow the 349 * ForegroundDownloadService to cancel a download via the Cancel action registered to a 350 * notification. 351 * 352 * <p>Clients should prefer to cancel the future returned to them from the download call. 353 * 354 * @param downloadKey the key associated with the download 355 */ cancelForegroundDownload(String downloadKey)356 void cancelForegroundDownload(String downloadKey); 357 358 /** 359 * Triggers the execution of MDD maintenance. 360 * 361 * <p>MDD needs to run maintenance task once a day. If you call {@link 362 * #schedulePeriodicBackgroundTasks} api, the maintenance will be called automatically. In case 363 * you don't want to schedule MDD tasks, you can call this maintenance method directly. 364 * 365 * <p>If you do need to call this api, make sure that this api is called exactly once every day. 366 * 367 * <p>The returned ListenableFuture would fail if the maintenance execution doesn't succeed. 368 */ maintenance()369 ListenableFuture<Void> maintenance(); 370 371 /** 372 * Perform garbage collection, which includes removing expired file groups and unreferenced files. 373 * 374 * <p>By default, this is run as part of {@link #maintenance} so doesn't need to be invoked 375 * directly by client code. If you disabled that behavior via {@link 376 * Flags#mddEnableGarbageCollection} then this method should be periodically called to clean up 377 * unused files. 378 */ collectGarbage()379 ListenableFuture<Void> collectGarbage(); 380 381 /** 382 * Schedule periodic tasks that will download and verify all file groups when the required 383 * conditions are met, using the given {@link TaskScheduler}. 384 * 385 * <p>If the host app doesn't provide a TaskScheduler, calling this API will be a no-op. 386 * 387 * @deprecated Use the {@link schedulePeriodicBackgroundTasks} instead. 388 */ 389 @Deprecated schedulePeriodicTasks()390 void schedulePeriodicTasks(); 391 392 /** 393 * Schedule periodic background tasks that will download and verify all file groups when the 394 * required conditions are met, using the given {@link TaskScheduler}. 395 * 396 * <p>If the host app doesn't provide a TaskScheduler, calling this API will be a no-op. 397 */ schedulePeriodicBackgroundTasks()398 ListenableFuture<Void> schedulePeriodicBackgroundTasks(); 399 400 /** 401 * Schedule periodic background tasks that will download and verify all file groups when the 402 * required conditions are met, using the given {@link TaskScheduler}. 403 * 404 * <p>If the host app doesn't provide a TaskScheduler, calling this API will be a no-op. 405 * 406 * @param constraintOverridesMap to allow clients to override constraints requirements. 407 * <p><code>{@code 408 * ConstraintOverrides wifiOverrides = 409 * ConstraintOverrides.newBuilder() 410 * .setRequiresCharging(false) 411 * .setRequiresDeviceIdle(true) 412 * .build(); 413 * ConstraintOverrides cellularOverrides = 414 * ConstraintOverrides.newBuilder() 415 * .setRequiresCharging(true) 416 * .setRequiresDeviceIdle(false) 417 * .build(); 418 * 419 * Map<String, ConstraintOverrides> constraintOverridesMap = new HashMap<>(); 420 * constraintOverridesMap.put(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK, wifiOverrides); 421 * constraintOverridesMap.put(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK, cellularOverrides); 422 * 423 * mobileDataDownload.schedulePeriodicBackgroundTasks(Optional.of(constraintOverridesMap)).get(); 424 * }</code> 425 */ schedulePeriodicBackgroundTasks( Optional<Map<String, ConstraintOverrides>> constraintOverridesMap)426 ListenableFuture<Void> schedulePeriodicBackgroundTasks( 427 Optional<Map<String, ConstraintOverrides>> constraintOverridesMap); 428 429 /** 430 * Cancels previously-scheduled periodic background tasks using the given {@link TaskScheduler}. 431 * Cancelling is best-effort and only meant to be used in an emergency; most apps will never need 432 * to call it. 433 * 434 * <p>If the host app doesn't provide a TaskScheduler, calling this API is a no-op. 435 */ cancelPeriodicBackgroundTasks()436 default ListenableFuture<Void> cancelPeriodicBackgroundTasks() { 437 // TODO(b/223822302): remove default once all implementations have been updated to include it 438 return Futures.immediateVoidFuture(); 439 } 440 441 /** 442 * Handle a task scheduled via a task scheduling service. 443 * 444 * <p>This method should not be called on the main thread, as it does work on the thread it is 445 * called on. 446 * 447 * @return a listenable future which indicates when any async task scheduled is complete. 448 */ handleTask(String tag)449 ListenableFuture<Void> handleTask(String tag); 450 451 /** Clear MDD metadata and its managed files. MDD will be reset to a clean state. */ clear()452 ListenableFuture<Void> clear(); 453 454 /** 455 * Return MDD debug info as a string. This could return some PII information so it's not 456 * recommended to be called in production build. 457 * 458 * <p>This debug info string could be very long. In order to print them in adb logcat, we have to 459 * split the string. See how it is done in our sample app: <internal> 460 */ getDebugInfoAsString()461 String getDebugInfoAsString(); 462 463 /** 464 * Reports usage of a file group back to MDD. This can be used to track errors with file group 465 * roll outs. Each usage of the file group should result in a single call of this method in order 466 * to allow for accurate metrics server side. 467 * 468 * @param usageEvent that will be logged. 469 * @return a listenable future which indicates that the UsageEvent has been logged. 470 */ 471 @CanIgnoreReturnValue reportUsage(UsageEvent usageEvent)472 ListenableFuture<Void> reportUsage(UsageEvent usageEvent); 473 } 474