1 /* 2 * Copyright (C) 2024 The Android Open Source Project 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 17 package com.android.providers.media.photopicker.sync; 18 19 import android.util.Log; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.VisibleForTesting; 23 import androidx.work.WorkInfo; 24 import androidx.work.WorkManager; 25 26 import com.google.common.util.concurrent.ListenableFuture; 27 28 import java.util.List; 29 import java.util.concurrent.CompletableFuture; 30 import java.util.concurrent.ExecutionException; 31 import java.util.concurrent.TimeUnit; 32 import java.util.concurrent.TimeoutException; 33 34 public class SyncCompletionWaiter { 35 private static final String TAG = "SyncCompletionWaiter"; 36 37 /** 38 * Will try it's best to wait for the existing sync requests to complete. It may not wait for 39 * new sync requests received after this method starts running. 40 */ waitForSync( @onNull WorkManager workManager, @NonNull SyncTracker syncTracker, @NonNull String uniqueWorkName)41 public static void waitForSync( 42 @NonNull WorkManager workManager, 43 @NonNull SyncTracker syncTracker, 44 @NonNull String uniqueWorkName) { 45 try { 46 final CompletableFuture<Void> completableFuture = 47 CompletableFuture.allOf( 48 syncTracker.pendingSyncFutures().toArray(new CompletableFuture[0])); 49 50 waitForSync(workManager, completableFuture, uniqueWorkName, /* retryCount */ 30); 51 } catch (ExecutionException | InterruptedException e) { 52 Log.w(TAG, "Could not wait for the sync to finish: " + e); 53 } 54 } 55 56 /** 57 * Wait for sync tracked by the input future to complete. In case the future takes an unusually 58 * long time to complete, check the relevant unique work status from Work Manager. 59 */ 60 @VisibleForTesting waitForSync( @onNull WorkManager workManager, @NonNull CompletableFuture<Void> completableFuture, @NonNull String uniqueWorkName, int retryCount)61 public static int waitForSync( 62 @NonNull WorkManager workManager, 63 @NonNull CompletableFuture<Void> completableFuture, 64 @NonNull String uniqueWorkName, 65 int retryCount) throws ExecutionException, InterruptedException { 66 for (; retryCount > 0; retryCount--) { 67 try { 68 completableFuture.get(/* timeout */ 3, TimeUnit.SECONDS); 69 return retryCount; 70 } catch (TimeoutException e) { 71 if (isUniqueWorkPending(workManager, uniqueWorkName)) { 72 Log.i(TAG, "Waiting for the sync again." 73 + " Unique work name: " + uniqueWorkName 74 + " Retry count: " + retryCount); 75 } else { 76 Log.e(TAG, "Either immediate unique work is complete and the sync futures " 77 + "were not cleared, or a proactive sync might be blocking the query. " 78 + "Unblocking the query now for " + uniqueWorkName); 79 return retryCount; 80 } 81 } 82 } 83 84 if (retryCount == 0) { 85 Log.e(TAG, "Retry count exhausted, could not wait for sync anymore."); 86 } 87 return retryCount; 88 } 89 90 /** 91 * Will wait for the existing sync requests to complete till the provided timeout. It may 92 * not wait for new sync requests received after this method starts running. 93 */ waitForSyncWithTimeout( @onNull SyncTracker syncTracker, int timeoutInMillis)94 public static boolean waitForSyncWithTimeout( 95 @NonNull SyncTracker syncTracker, 96 int timeoutInMillis) { 97 try { 98 final CompletableFuture<Void> completableFuture = 99 CompletableFuture.allOf( 100 syncTracker.pendingSyncFutures().toArray(new CompletableFuture[0])); 101 completableFuture.get(timeoutInMillis, TimeUnit.MILLISECONDS); 102 return true; 103 } catch (ExecutionException | InterruptedException | TimeoutException e) { 104 Log.w(TAG, "Could not wait for the sync with timeout to finish: " + e); 105 return false; 106 } 107 } 108 109 /** 110 * Returns true if the given unique work is pending. In case the unique work is complete or 111 * there was an error in getting the work state, it returns false. 112 */ isUniqueWorkPending(WorkManager workManager, String uniqueWorkName)113 public static boolean isUniqueWorkPending(WorkManager workManager, String uniqueWorkName) { 114 ListenableFuture<List<WorkInfo>> future = 115 workManager.getWorkInfosForUniqueWork(uniqueWorkName); 116 try { 117 List<WorkInfo> workInfos = future.get(); 118 for (WorkInfo workInfo : workInfos) { 119 if (!workInfo.getState().isFinished()) { 120 return true; 121 } 122 } 123 return false; 124 } catch (InterruptedException | ExecutionException e) { 125 Log.e(TAG, "Error occurred in fetching work info - ignore pending work"); 126 return false; 127 } 128 } 129 } 130