/*
 * Copyright (C) 2017 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.scheduler;

import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.support.annotation.RequiresApi;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;

/**
 * Watches whether the {@link Requirements} are met and notifies the {@link Listener} on changes.
 */
public final class RequirementsWatcher {

  /**
   * Notified when RequirementsWatcher instance first created and on changes whether the {@link
   * Requirements} are met.
   */
  public interface Listener {

    /**
     * Called when all of the requirements are met.
     *
     * @param requirementsWatcher Calling instance.
     */
    void requirementsMet(RequirementsWatcher requirementsWatcher);

    /**
     * Called when there is at least one not met requirement and there is a change on which of the
     * requirements are not met.
     *
     * @param requirementsWatcher Calling instance.
     */
    void requirementsNotMet(RequirementsWatcher requirementsWatcher);
  }

  private static final String TAG = "RequirementsWatcher";

  private final Context context;
  private final Listener listener;
  private final Requirements requirements;
  private DeviceStatusChangeReceiver receiver;

  private int notMetRequirements;
  private CapabilityValidatedCallback networkCallback;

  /**
   * @param context Any context.
   * @param listener Notified whether the {@link Requirements} are met.
   * @param requirements The requirements to watch.
   */
  public RequirementsWatcher(Context context, Listener listener, Requirements requirements) {
    this.requirements = requirements;
    this.listener = listener;
    this.context = context.getApplicationContext();
    logd(this + " created");
  }

  /**
   * Starts watching for changes. Must be called from a thread that has an associated {@link
   * Looper}. Listener methods are called on the caller thread.
   */
  public void start() {
    Assertions.checkNotNull(Looper.myLooper());

    notMetRequirements = requirements.getNotMetRequirements(context);

    IntentFilter filter = new IntentFilter();
    if (requirements.getRequiredNetworkType() != Requirements.NETWORK_TYPE_NONE) {
      if (Util.SDK_INT >= 23) {
        registerNetworkCallbackV23();
      } else {
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
      }
    }
    if (requirements.isChargingRequired()) {
      filter.addAction(Intent.ACTION_POWER_CONNECTED);
      filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
    }
    if (requirements.isIdleRequired()) {
      if (Util.SDK_INT >= 23) {
        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
      } else {
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
      }
    }
    receiver = new DeviceStatusChangeReceiver();
    context.registerReceiver(receiver, filter, null, new Handler());
    logd(this + " started");
  }

  /** Stops watching for changes. */
  public void stop() {
    context.unregisterReceiver(receiver);
    receiver = null;
    if (networkCallback != null) {
      unregisterNetworkCallback();
    }
    logd(this + " stopped");
  }

  /** Returns watched {@link Requirements}. */
  public Requirements getRequirements() {
    return requirements;
  }

  @Override
  public String toString() {
    if (!Scheduler.DEBUG) {
      return super.toString();
    }
    return "RequirementsWatcher{" + requirements + '}';
  }

  @TargetApi(23)
  private void registerNetworkCallbackV23() {
    ConnectivityManager connectivityManager =
        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkRequest request =
        new NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
            .build();
    networkCallback = new CapabilityValidatedCallback();
    connectivityManager.registerNetworkCallback(request, networkCallback);
  }

  private void unregisterNetworkCallback() {
    if (Util.SDK_INT >= 21) {
      ConnectivityManager connectivityManager =
          (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
      connectivityManager.unregisterNetworkCallback(networkCallback);
      networkCallback = null;
    }
  }

  private void checkRequirements() {
    int notMetRequirements = requirements.getNotMetRequirements(context);
    if (this.notMetRequirements == notMetRequirements) {
      logd("notMetRequirements hasn't changed: " + notMetRequirements);
      return;
    }
    this.notMetRequirements = notMetRequirements;
    if (notMetRequirements == 0) {
      logd("start job");
      listener.requirementsMet(this);
    } else {
      logd("stop job");
      listener.requirementsNotMet(this);
    }
  }

  private static void logd(String message) {
    if (Scheduler.DEBUG) {
      Log.d(TAG, message);
    }
  }

  private class DeviceStatusChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (!isInitialStickyBroadcast()) {
        logd(RequirementsWatcher.this + " received " + intent.getAction());
        checkRequirements();
      }
    }
  }

  @RequiresApi(api = 21)
  private final class CapabilityValidatedCallback extends ConnectivityManager.NetworkCallback {
    @Override
    public void onAvailable(Network network) {
      super.onAvailable(network);
      logd(RequirementsWatcher.this + " NetworkCallback.onAvailable");
      checkRequirements();
    }

    @Override
    public void onLost(Network network) {
      super.onLost(network);
      logd(RequirementsWatcher.this + " NetworkCallback.onLost");
      checkRequirements();
    }
  }
}

@@ -2366,16 +2366,43 @@
 allback;
+%0A  private Handler handler;
 %0A%0A  /**%0A
@@ -3052,16 +3052,45 @@
 oper());
+%0A    handler = new Handler();
 %0A%0A    no
@@ -3992,29 +3992,23 @@
 , null, 
-new H
+h
 andler
-()
 );%0A    l
@@ -6479,226 +6479,254 @@
     
-super.onAvailable(network);%0A      logd(RequirementsWatcher.this + %22 NetworkCallback.onAvailable%22);%0A      checkRequirements();%0A    %7D%0A%0A    @Override%0A    public void onLost(Network network) %7B%0A      super.onLost(network);%0A
+onNetworkCallback();%0A    %7D%0A%0A    @Override%0A    public void onLost(Network network) %7B%0A      onNetworkCallback();%0A    %7D%0A%0A    private void onNetworkCallback() %7B%0A      handler.post(%0A          () -%3E %7B%0A            if (networkCallback != null) %7B%0A        
     
@@ -6780,19 +6780,20 @@
 back
-.onLost
 %22);%0A
+        
     
@@ -6811,24 +6811,52 @@
 irements();%0A
+            %7D%0A          %7D);%0A
     %7D%0A  %7D%0A%7D%0A

Call Listener methods on the thread started RequirementsWatcher