RequirementsWatcher.java
/*
* 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 androidx.media3.exoplayer.scheduler;
import static androidx.media3.common.util.Assertions.checkNotNull;
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.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
/**
* Watches whether the {@link Requirements} are met and notifies the {@link Listener} on changes.
*/
@UnstableApi
public final class RequirementsWatcher {
/**
* Notified when RequirementsWatcher instance first created and on changes whether the {@link
* Requirements} are met.
*/
public interface Listener {
/**
* Called when there is a change on the met requirements.
*
* @param requirementsWatcher Calling instance.
* @param notMetRequirements {@link Requirements.RequirementFlags RequirementFlags} that are not
* met, or 0.
*/
void onRequirementsStateChanged(
RequirementsWatcher requirementsWatcher,
@Requirements.RequirementFlags int notMetRequirements);
}
private final Context context;
private final Listener listener;
private final Requirements requirements;
private final Handler handler;
@Nullable private DeviceStatusChangeReceiver receiver;
private @Requirements.RequirementFlags int notMetRequirements;
@Nullable private NetworkCallback 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.context = context.getApplicationContext();
this.listener = listener;
this.requirements = requirements;
handler = Util.createHandlerForCurrentOrMainLooper();
}
/**
* Starts watching for changes. Must be called from a thread that has an associated {@link
* Looper}. Listener methods are called on the caller thread.
*
* @return Initial {@link Requirements.RequirementFlags RequirementFlags} that are not met, or 0.
*/
public @Requirements.RequirementFlags int start() {
notMetRequirements = requirements.getNotMetRequirements(context);
IntentFilter filter = new IntentFilter();
if (requirements.isNetworkRequired()) {
if (Util.SDK_INT >= 24) {
registerNetworkCallbackV24();
} 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);
}
}
if (requirements.isStorageNotLowRequired()) {
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
}
receiver = new DeviceStatusChangeReceiver();
context.registerReceiver(receiver, filter, /* broadcastPermission= */ null, handler);
return notMetRequirements;
}
/** Stops watching for changes. */
public void stop() {
context.unregisterReceiver(checkNotNull(receiver));
receiver = null;
if (Util.SDK_INT >= 24 && networkCallback != null) {
unregisterNetworkCallbackV24();
}
}
/** Returns watched {@link Requirements}. */
public Requirements getRequirements() {
return requirements;
}
@RequiresApi(24)
private void registerNetworkCallbackV24() {
ConnectivityManager connectivityManager =
checkNotNull((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
networkCallback = new NetworkCallback();
connectivityManager.registerDefaultNetworkCallback(networkCallback);
}
@RequiresApi(24)
private void unregisterNetworkCallbackV24() {
ConnectivityManager connectivityManager =
checkNotNull((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
connectivityManager.unregisterNetworkCallback(checkNotNull(networkCallback));
networkCallback = null;
}
private void checkRequirements() {
@Requirements.RequirementFlags
int notMetRequirements = requirements.getNotMetRequirements(context);
if (this.notMetRequirements != notMetRequirements) {
this.notMetRequirements = notMetRequirements;
listener.onRequirementsStateChanged(this, notMetRequirements);
}
}
/**
* Re-checks the requirements if there are network requirements that are currently not met.
*
* <p>When we receive an event that implies newly established network connectivity, we re-check
* the requirements by calling {@link #checkRequirements()}. This check sometimes sees that there
* is still no active network, meaning that any network requirements will remain not met. By
* calling this method when we receive other events that imply continued network connectivity, we
* can detect that the requirements are met once an active network does exist.
*/
private void recheckNotMetNetworkRequirements() {
if ((notMetRequirements & (Requirements.NETWORK | Requirements.NETWORK_UNMETERED)) == 0) {
// No unmet network requirements to recheck.
return;
}
checkRequirements();
}
private class DeviceStatusChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!isInitialStickyBroadcast()) {
checkRequirements();
}
}
}
@RequiresApi(24)
private final class NetworkCallback extends ConnectivityManager.NetworkCallback {
private boolean receivedCapabilitiesChange;
private boolean networkValidated;
@Override
public void onAvailable(Network network) {
postCheckRequirements();
}
@Override
public void onLost(Network network) {
postCheckRequirements();
}
@Override
public void onBlockedStatusChanged(Network network, boolean blocked) {
if (!blocked) {
postRecheckNotMetNetworkRequirements();
}
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
boolean networkValidated =
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
if (!receivedCapabilitiesChange || this.networkValidated != networkValidated) {
receivedCapabilitiesChange = true;
this.networkValidated = networkValidated;
postCheckRequirements();
} else if (networkValidated) {
postRecheckNotMetNetworkRequirements();
}
}
private void postCheckRequirements() {
handler.post(
() -> {
if (networkCallback != null) {
checkRequirements();
}
});
}
private void postRecheckNotMetNetworkRequirements() {
handler.post(
() -> {
if (networkCallback != null) {
recheckNotMetNetworkRequirements();
}
});
}
}
}