content: package org.adaway.model.source;
  
  import android.content.Context;
  
  import androidx.annotation.NonNull;
  import androidx.work.Constraints;
  import androidx.work.ExistingPeriodicWorkPolicy;
  import androidx.work.NetworkType;
  import androidx.work.PeriodicWorkRequest;
  import androidx.work.WorkManager;
  import androidx.work.Worker;
  import androidx.work.WorkerParameters;
  
  import org.adaway.AdAwayApplication;
  import org.adaway.helper.NotificationHelper;
  import org.adaway.helper.PreferenceHelper;
  import org.adaway.model.adblocking.AdBlockModel;
  import org.adaway.model.error.HostErrorException;
  
  import static androidx.work.ExistingPeriodicWorkPolicy.KEEP;
  import static androidx.work.ExistingPeriodicWorkPolicy.UPDATE;
  import static androidx.work.ListenableWorker.Result.failure;
  import static androidx.work.ListenableWorker.Result.retry;
  import static androidx.work.ListenableWorker.Result.success;
  import static java.util.concurrent.TimeUnit.HOURS;
  
  import timber.log.Timber;
  
  /**
   * This class is a service to check for hosts sources update.<br/>
   * It could be enabled or disabled for periodic check.<br>
   * The implementation is based on WorkManager from Android X.
   *
   * @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
   */
  public final class SourceUpdateService {
      /**
       * The name of the periodic work.
       */
      private static final String WORK_NAME = "HostsUpdateWork";
  
      /**
       * Private constructor.
       */
      private SourceUpdateService() {
  
      }
  
      /**
       * Enable update service.
       *
       * @param context              The application context.
       * @param unmeteredNetworkOnly <code>true</code> if the update should be done on unmetered network only, <code>false</code> otherwise.
       */
      public static void enable(Context context, boolean unmeteredNetworkOnly) {
          enqueueWork(context, UPDATE, unmeteredNetworkOnly);
      }
  
      /**
       * Disable update service.
       *
       * @param context The application context.
       */
      public static void disable(Context context) {
          // Cancel previous work
          WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME);
      }
  
      /**
       * Sync service on user preferences.
       *
       * @param context The application context.
       */
      static void syncPreferences(Context context) {
          if (PreferenceHelper.getUpdateCheckHostsDaily(context)) {
              enqueueWork(context, KEEP, PreferenceHelper.getUpdateOnlyOnWifi(context));
          } else {
              disable(context);
          }
      }
  
      private static void enqueueWork(Context context, ExistingPeriodicWorkPolicy workPolicy, boolean unmeteredNetworkOnly) {
          // Create work request
          PeriodicWorkRequest workRequest = getWorkRequest(unmeteredNetworkOnly);
          // Enqueue work request
          WorkManager workManager = WorkManager.getInstance(context);
          workManager.enqueueUniquePeriodicWork(WORK_NAME, workPolicy, workRequest);
      }
  
      /**
       * Create source update work request.
       *
       * @param unmeteredNetworkOnly <code>true</code> if the update should be done on unmetered network only, <code>false</code> otherwise.
       * @return The source update work request to queue.
       */
      private static PeriodicWorkRequest getWorkRequest(boolean unmeteredNetworkOnly) {
          // Create worker constraints
          Constraints constraints = new Constraints.Builder()
                  .setRequiredNetworkType(unmeteredNetworkOnly ? NetworkType.UNMETERED : NetworkType.CONNECTED)
                  .setRequiresStorageNotLow(true)
                  .build();
          // Create work request
          return new PeriodicWorkRequest.Builder(HostsSourcesUpdateWorker.class, 6, HOURS)
                  .setConstraints(constraints)
                  .setInitialDelay(3, HOURS)
                  .build();
      }
  
      /**
       * This class is a {@link Worker} to fetch hosts sources updates and install them if needed.
       *
       * @author Bruce BUJON (bruce.bujon(at)gmail(dot)com)
       */
      public static class HostsSourcesUpdateWorker extends Worker {
          /**
           * Constructor.
           *
           * @param context      The application context.
           * @param workerParams The parameters to setup this worker.
           */
          public HostsSourcesUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
              super(context, workerParams);
          }
  
          @NonNull
          @Override
          public Result doWork() {
              Timber.i("Starting update worker");
              // Create model
              AdAwayApplication application = (AdAwayApplication) getApplicationContext();
              SourceModel model = application.getSourceModel();
              // Check for update
              boolean hasUpdate;
              try {
                  hasUpdate = model.checkForUpdate();
              } catch (HostErrorException exception) {
                  // An error occurred, check will be retried
                  Timber.e(exception, "Failed to check for update. Will retry later.");
                  return retry();
              }
              if (hasUpdate) {
                  // Do update
                  try {
                      doUpdate(application);
                  } catch (HostErrorException exception) {
                      // Installation failed. Worker failed.
                      Timber.e(exception, "Failed to apply hosts file during background update.");
                      return failure();
                  }
              }
              // Return as success
              return success();
          }
  
          /**
           * Handle update according user preferences.
           *
           * @param application The application.
           * @throws HostErrorException If the update could not be handled.
           */
          private void doUpdate(AdAwayApplication application) throws HostErrorException {
              // Check if automatic update are enabled
              if (PreferenceHelper.getAutomaticUpdateDaily(application)) {
                  // Retrieve source updates
                  SourceModel sourceModel = application.getSourceModel();
                  sourceModel.retrieveHostsSources();
                  // Apply source updates
                  AdBlockModel adBlockModel = application.getAdBlockModel();
                  adBlockModel.apply();
              } else {
                  // Display update notification
                  NotificationHelper.showUpdateHostsNotification(application);
              }
          }
      }
  }
  