/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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.exoplayer2.ext.workmanager;

import android.content.Context;
import android.content.Intent;
import androidx.annotation.RequiresApi;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.Scheduler;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;

/** A {@link Scheduler} that uses {@link WorkManager}. */
public final class WorkManagerScheduler implements Scheduler {

  private static final String TAG = "WorkManagerScheduler";
  private static final String KEY_SERVICE_ACTION = "service_action";
  private static final String KEY_SERVICE_PACKAGE = "service_package";
  private static final String KEY_REQUIREMENTS = "requirements";
  private static final int SUPPORTED_REQUIREMENTS =
      Requirements.NETWORK
          | Requirements.NETWORK_UNMETERED
          | (Util.SDK_INT >= 23 ? Requirements.DEVICE_IDLE : 0)
          | Requirements.DEVICE_CHARGING
          | Requirements.DEVICE_STORAGE_NOT_LOW;

  private final String workName;

  /**
   * @param workName A name for work scheduled by this instance. If the same name was used by a
   *     previous instance, anything scheduled by the previous instance will be canceled by this
   *     instance if {@link #schedule(Requirements, String, String)} or {@link #cancel()} are
   *     called.
   */
  public WorkManagerScheduler(String workName) {
    this.workName = workName;
  }

  @Override
  public boolean schedule(Requirements requirements, String servicePackage, String serviceAction) {
    Constraints constraints = buildConstraints(requirements);
    Data inputData = buildInputData(requirements, servicePackage, serviceAction);
    OneTimeWorkRequest workRequest = buildWorkRequest(constraints, inputData);
    WorkManager.getInstance().enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest);
    return true;
  }

  @Override
  public boolean cancel() {
    WorkManager.getInstance().cancelUniqueWork(workName);
    return true;
  }

  @Override
  public Requirements getSupportedRequirements(Requirements requirements) {
    return requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
  }

  private static Constraints buildConstraints(Requirements requirements) {
    Requirements filteredRequirements = requirements.filterRequirements(SUPPORTED_REQUIREMENTS);
    if (!filteredRequirements.equals(requirements)) {
      Log.w(
          TAG,
          "Ignoring unsupported requirements: "
              + (filteredRequirements.getRequirements() ^ requirements.getRequirements()));
    }

    Constraints.Builder builder = new Constraints.Builder();
    if (requirements.isUnmeteredNetworkRequired()) {
      builder.setRequiredNetworkType(NetworkType.UNMETERED);
    } else if (requirements.isNetworkRequired()) {
      builder.setRequiredNetworkType(NetworkType.CONNECTED);
    } else {
      builder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
    }
    if (Util.SDK_INT >= 23 && requirements.isIdleRequired()) {
      setRequiresDeviceIdle(builder);
    }
    if (requirements.isChargingRequired()) {
      builder.setRequiresCharging(true);
    }
    if (requirements.isStorageNotLowRequired()) {
      builder.setRequiresStorageNotLow(true);
    }

    return builder.build();
  }

  @RequiresApi(23)
  private static void setRequiresDeviceIdle(Constraints.Builder builder) {
    builder.setRequiresDeviceIdle(true);
  }

  private static Data buildInputData(
      Requirements requirements, String servicePackage, String serviceAction) {
    Data.Builder builder = new Data.Builder();

    builder.putInt(KEY_REQUIREMENTS, requirements.getRequirements());
    builder.putString(KEY_SERVICE_PACKAGE, servicePackage);
    builder.putString(KEY_SERVICE_ACTION, serviceAction);

    return builder.build();
  }

  private static OneTimeWorkRequest buildWorkRequest(Constraints constraints, Data inputData) {
    OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(SchedulerWorker.class);

    builder.setConstraints(constraints);
    builder.setInputData(inputData);

    return builder.build();
  }

  /** A {@link Worker} that starts the target service if the requirements are met. */
  // This class needs to be public so that WorkManager can instantiate it.
  public static final class SchedulerWorker extends Worker {

    private final WorkerParameters workerParams;
    private final Context context;

    public SchedulerWorker(Context context, WorkerParameters workerParams) {
      super(context, workerParams);
      this.workerParams = workerParams;
      this.context = context;
    }

    @Override
    public Result doWork() {
      Data inputData = Assertions.checkNotNull(workerParams.getInputData());
      Requirements requirements = new Requirements(inputData.getInt(KEY_REQUIREMENTS, 0));
      int notMetRequirements = requirements.getNotMetRequirements(context);
      if (notMetRequirements == 0) {
        String serviceAction = Assertions.checkNotNull(inputData.getString(KEY_SERVICE_ACTION));
        String servicePackage = Assertions.checkNotNull(inputData.getString(KEY_SERVICE_PACKAGE));
        Intent intent = new Intent(serviceAction).setPackage(servicePackage);
        Util.startForegroundService(context, intent);
        return Result.success();
      } else {
        Log.w(TAG, "Requirements not met: " + notMetRequirements);
        return Result.retry();
      }
    }
  }
}

@@ -2004,31 +2004,363 @@
 nal 
-String workName;%0A%0A  /**
+WorkManager workManager;%0A  private final String workName;%0A%0A  /** @deprecated Call %7B@link #WorkManagerScheduler(Context, String)%7D instead. */%0A  @Deprecated%0A  @SuppressWarnings(%22deprecation%22)%0A  public WorkManagerScheduler(String workName) %7B%0A    this.workName = workName;%0A    workManager = WorkManager.getInstance();%0A  %7D%0A%0A  /**%0A   * @param context A context.
 %0A   
@@ -2688,32 +2688,49 @@
 anagerScheduler(
+Context context, 
 String workName)
@@ -2762,16 +2762,92 @@
 rkName;%0A
+    workManager = WorkManager.getInstance(context.getApplicationContext());%0A
   %7D%0A%0A  @
@@ -3178,25 +3178,25 @@
 tData);%0A    
-W
+w
 orkManager.g
@@ -3194,30 +3194,16 @@
 Manager.
-getInstance().
 enqueueU
@@ -3330,17 +3330,17 @@
 ) %7B%0A    
-W
+w
 orkManag
@@ -3346,22 +3346,8 @@
 ger.
-getInstance().
 canc

Migrate WorkManagerScheduler to non-deprecated WorkManager.getInstance