Merge "Defer low priority jobs in poor network conditions."

This commit is contained in:
TreeHugger Robot
2022-02-10 20:04:19 +00:00
committed by Android (Google) Code Review
4 changed files with 702 additions and 12 deletions

View File

@@ -417,6 +417,9 @@ public class JobSchedulerService extends com.android.server.SystemService
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
mConstants.updateConnectivityConstantsLocked();
break;
case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
@@ -489,6 +492,12 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
"conn_use_cell_signal_strength";
private static final String KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
"conn_update_all_jobs_min_interval_ms";
private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
"conn_low_signal_strength_relax_frac";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
@@ -514,6 +523,9 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -569,6 +581,23 @@ public class JobSchedulerService extends com.android.server.SystemService
* we consider matching it against a metered network.
*/
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
/**
* Whether to use the cell signal strength to determine if a particular job is eligible to
* run.
*/
public boolean CONN_USE_CELL_SIGNAL_STRENGTH = DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH;
/**
* When throttling updating all tracked jobs, make sure not to update them more frequently
* than this value.
*/
public long CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS;
/**
* The fraction of a job's running window that must pass before we consider running it on
* low signal strength networks.
*/
public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
/**
* The amount of time within which we would consider the app to be launching relatively soon
@@ -661,6 +690,18 @@ public class JobSchedulerService extends com.android.server.SystemService
CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_PREFETCH_RELAX_FRAC,
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
CONN_USE_CELL_SIGNAL_STRENGTH = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_USE_CELL_SIGNAL_STRENGTH,
DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH);
CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS,
DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS);
CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = DeviceConfig.getFloat(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
}
private void updatePrefetchConstantsLocked() {
@@ -739,6 +780,11 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
pw.print(KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
.println();
pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
.println();
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();

View File

@@ -35,6 +35,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -60,6 +64,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
/**
@@ -213,9 +218,16 @@ public final class ConnectivityController extends RestrictingController implemen
* is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
*/
private final List<UidStats> mSortedStats = new ArrayList<>();
@GuardedBy("mLock")
private long mLastCallbackAdjustmentTimeElapsed;
@GuardedBy("mLock")
private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
@GuardedBy("mLock")
private long mLastAllJobUpdateTimeElapsed;
private static final int MSG_ADJUST_CALLBACKS = 0;
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
private final Handler mHandler;
@@ -529,11 +541,7 @@ public final class ConnectivityController extends RestrictingController implemen
@GuardedBy("mLock")
public void onBatteryStateChangedLocked() {
// Update job bookkeeping out of band to avoid blocking broadcast progress.
JobSchedulerBackgroundThread.getHandler().post(() -> {
synchronized (mLock) {
updateTrackedJobsLocked(-1, null);
}
});
mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS);
}
private boolean isUsable(NetworkCapabilities capabilities) {
@@ -650,6 +658,82 @@ public final class ConnectivityController extends RestrictingController implemen
}
}
@GuardedBy("mLock")
private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities,
Constants constants) {
final int priority = jobStatus.getEffectivePriority();
if (priority >= JobInfo.PRIORITY_HIGH) {
return true;
}
if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) {
return true;
}
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return true;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
// Exclude VPNs because it's currently not possible to determine the VPN's underlying
// network, and thus the correct signal strength of the VPN's network.
// Transmitting data over a VPN is generally more battery-expensive than on the
// underlying network, so:
// TODO: find a good way to reduce job use of VPN when it'll be very expensive
// For now, we just pretend VPNs are always strong enough
return true;
}
// VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will
// most likely be the default network. We ideally don't want this to restrict jobs when the
// VCN incorrectly declares the CELLULAR transport, but there's currently no way to
// determine if a network is a VCN. When there is:
// TODO(216127782): exclude VCN running over WiFi from this check
int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
// Use the best strength found.
final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
for (int subId : subscriptionIds) {
CellSignalStrengthCallback callback = mSignalStrengths.get(subId);
if (callback != null) {
signalStrength = Math.max(signalStrength, callback.signalStrength);
} else {
Slog.wtf(TAG,
"Subscription ID " + subId + " doesn't have a registered callback");
}
}
if (DEBUG) {
Slog.d(TAG, "Cell signal strength for job=" + signalStrength);
}
// Treat "NONE_OR_UNKNOWN" as "NONE".
if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
// If signal strength is poor, don't run MIN or LOW priority jobs, and only
// run DEFAULT priority jobs if the device is charging or the job has been waiting
// long enough.
if (priority > JobInfo.PRIORITY_DEFAULT) {
return true;
}
if (priority < JobInfo.PRIORITY_DEFAULT) {
return false;
}
// DEFAULT job.
return (mService.isBatteryCharging() && mService.isBatteryNotLow())
|| jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
}
if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) {
// If signal strength is moderate, only run MIN priority jobs when the device
// is charging, or the job is already running.
if (priority >= JobInfo.PRIORITY_LOW) {
return true;
}
// MIN job.
if (mService.isBatteryCharging() && mService.isBatteryNotLow()) {
return true;
}
final UidStats uidStats = getUidStats(
jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
return uidStats.runningJobs.contains(jobStatus);
}
return true;
}
private static NetworkCapabilities.Builder copyCapabilities(
@NonNull final NetworkRequest request) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
@@ -717,10 +801,12 @@ public final class ConnectivityController extends RestrictingController implemen
// Second, is the network congested?
if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
// Third, is the network a strict match?
if (!isStrongEnough(jobStatus, capabilities, constants)) return false;
// Is the network a strict match?
if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
// Third, is the network a relaxed match?
// Is the network a relaxed match?
if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
return false;
@@ -985,6 +1071,24 @@ public final class ConnectivityController extends RestrictingController implemen
return changed;
}
@GuardedBy("mLock")
private void updateAllTrackedJobsLocked(boolean allowThrottle) {
if (allowThrottle) {
final long throttleTimeLeftMs =
(mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
- sElapsedRealtimeClock.millis();
if (throttleTimeLeftMs > 0) {
Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0);
mHandler.sendMessageDelayed(msg, throttleTimeLeftMs);
return;
}
}
mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS);
updateTrackedJobsLocked(-1, null);
mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis();
}
/**
* Update any jobs tracked by this controller that match given filters.
*
@@ -1088,7 +1192,11 @@ public final class ConnectivityController extends RestrictingController implemen
Slog.v(TAG, "onCapabilitiesChanged: " + network);
}
synchronized (mLock) {
mAvailableNetworks.put(network, capabilities);
final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities);
if (oldCaps != null) {
maybeUnregisterSignalStrengthCallbackLocked(oldCaps);
}
maybeRegisterSignalStrengthCallbackLocked(capabilities);
updateTrackedJobsLocked(-1, network);
postAdjustCallbacks();
}
@@ -1100,7 +1208,10 @@ public final class ConnectivityController extends RestrictingController implemen
Slog.v(TAG, "onLost: " + network);
}
synchronized (mLock) {
mAvailableNetworks.remove(network);
final NetworkCapabilities capabilities = mAvailableNetworks.remove(network);
if (capabilities != null) {
maybeUnregisterSignalStrengthCallbackLocked(capabilities);
}
for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
if (Objects.equals(callback.mDefaultNetwork, network)) {
@@ -1111,6 +1222,63 @@ public final class ConnectivityController extends RestrictingController implemen
postAdjustCallbacks();
}
}
@GuardedBy("mLock")
private void maybeRegisterSignalStrengthCallbackLocked(
@NonNull NetworkCapabilities capabilities) {
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return;
}
TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
for (int subId : subscriptionIds) {
if (mSignalStrengths.indexOfKey(subId) >= 0) {
continue;
}
TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
idTm.registerTelephonyCallback(
JobSchedulerBackgroundThread.getExecutor(), callback);
mSignalStrengths.put(subId, callback);
final SignalStrength signalStrength = idTm.getSignalStrength();
if (signalStrength != null) {
callback.signalStrength = signalStrength.getLevel();
}
}
}
@GuardedBy("mLock")
private void maybeUnregisterSignalStrengthCallbackLocked(
@NonNull NetworkCapabilities capabilities) {
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return;
}
ArraySet<Integer> activeIds = new ArraySet<>();
for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) {
NetworkCapabilities nc = mAvailableNetworks.valueAt(i);
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
activeIds.addAll(nc.getSubscriptionIds());
}
}
if (DEBUG) {
Slog.d(TAG, "Active subscription IDs: " + activeIds);
}
TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
for (int subId : subscriptionIds) {
if (activeIds.contains(subId)) {
continue;
}
TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId);
if (callback != null) {
idTm.unregisterTelephonyCallback(callback);
} else {
Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!");
}
}
}
};
private class CcHandler extends Handler {
@@ -1127,6 +1295,13 @@ public final class ConnectivityController extends RestrictingController implemen
maybeAdjustRegisteredCallbacksLocked();
}
break;
case MSG_UPDATE_ALL_TRACKED_JOBS:
synchronized (mLock) {
final boolean allowThrottle = msg.arg1 == 1;
updateAllTrackedJobsLocked(allowThrottle);
}
break;
}
}
}
@@ -1268,6 +1443,33 @@ public final class ConnectivityController extends RestrictingController implemen
}
}
private class CellSignalStrengthCallback extends TelephonyCallback
implements TelephonyCallback.SignalStrengthsListener {
@GuardedBy("mLock")
public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT;
@Override
public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
synchronized (mLock) {
final int newSignalStrength = signalStrength.getLevel();
if (DEBUG) {
Slog.d(TAG, "Signal strength changing from "
+ this.signalStrength + " to " + newSignalStrength);
for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) {
Slog.d(TAG, "CSS: " + css.getLevel() + " " + css);
}
}
if (this.signalStrength == newSignalStrength) {
// This happens a lot.
return;
}
this.signalStrength = newSignalStrength;
// Update job bookkeeping out of band to avoid blocking callback progress.
mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget();
}
}
}
@GuardedBy("mLock")
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
@@ -1299,6 +1501,20 @@ public final class ConnectivityController extends RestrictingController implemen
}
pw.println();
if (mSignalStrengths.size() > 0) {
pw.println("Subscription ID signal strengths:");
pw.increaseIndent();
for (int i = 0; i < mSignalStrengths.size(); ++i) {
pw.print(mSignalStrengths.keyAt(i));
pw.print(": ");
pw.println(mSignalStrengths.valueAt(i).signalStrength);
}
pw.decreaseIndent();
} else {
pw.println("No cached signal strengths");
}
pw.println();
pw.println("Current default network callbacks:");
pw.increaseIndent();
for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {

View File

@@ -1139,11 +1139,12 @@ public final class JobStatus {
*/
public float getFractionRunTime() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME
&& latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
return 1;
} else if (earliestRunTimeElapsedMillis == 0) {
} else if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
return now >= latestRunTimeElapsedMillis ? 1 : 0;
} else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
} else if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
return now >= earliestRunTimeElapsedMillis ? 1 : 0;
} else {
if (now <= earliestRunTimeElapsedMillis) {

View File

@@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@ import android.net.NetworkPolicyManager;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.DataUnit;
import com.android.server.LocalServices;
@@ -76,6 +81,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
public class ConnectivityControllerTest {
@@ -301,6 +307,427 @@ public class ConnectivityControllerTest {
}
}
@Test
public void testStrongEnough_Cellular() {
mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final TelephonyManager telephonyManager = mock(TelephonyManager.class);
when(mContext.getSystemService(TelephonyManager.class))
.thenReturn(telephonyManager);
when(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager);
final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
ArgumentCaptor.forClass(TelephonyCallback.class);
doNothing().when(telephonyManager)
.registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
final ArgumentCaptor<NetworkCallback> callbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
final Network net = mock(Network.class);
final NetworkCapabilities caps = createCapabilitiesBuilder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
.setSubscriptionIds(Set.of(7357))
.build();
final JobInfo.Builder baseJobBuilder = createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
DataUnit.MEBIBYTES.toBytes(1))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
final JobStatus jobHigh = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
final JobStatus jobDefEarly = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 1000, now + 100_000);
final JobStatus jobDefLate = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 100_000, now + 1000);
final JobStatus jobLow = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
final JobStatus jobMin = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final ConnectivityController controller = new ConnectivityController(mService);
final NetworkCallback generalCallback = callbackCaptor.getValue();
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
answerNetwork(generalCallback, null, null, net, caps);
final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
(TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
controller.maybeStartTrackingJobLocked(jobMinRunner, null);
controller.prepareForExecutionLocked(jobMinRunner);
final SignalStrength signalStrength = mock(SignalStrength.class);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_POOR);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(false);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertFalse(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_GOOD);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_GREAT);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
}
@Test
public void testStrongEnough_Cellular_CheckDisabled() {
mConstants.CONN_USE_CELL_SIGNAL_STRENGTH = false;
mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final TelephonyManager telephonyManager = mock(TelephonyManager.class);
when(mContext.getSystemService(TelephonyManager.class))
.thenReturn(telephonyManager);
when(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager);
final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
ArgumentCaptor.forClass(TelephonyCallback.class);
doNothing().when(telephonyManager)
.registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
final ArgumentCaptor<NetworkCallback> callbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
final Network net = mock(Network.class);
final NetworkCapabilities caps = createCapabilitiesBuilder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
.setSubscriptionIds(Set.of(7357))
.build();
final JobInfo.Builder baseJobBuilder = createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
DataUnit.MEBIBYTES.toBytes(1))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
final JobStatus jobHigh = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
final JobStatus jobDefEarly = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 1000, now + 100_000);
final JobStatus jobDefLate = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 100_000, now + 1000);
final JobStatus jobLow = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
final JobStatus jobMin = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final ConnectivityController controller = new ConnectivityController(mService);
final NetworkCallback generalCallback = callbackCaptor.getValue();
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
answerNetwork(generalCallback, null, null, net, caps);
final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
(TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
controller.maybeStartTrackingJobLocked(jobMinRunner, null);
controller.prepareForExecutionLocked(jobMinRunner);
final SignalStrength signalStrength = mock(SignalStrength.class);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_POOR);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(false);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(true);
when(mService.isBatteryNotLow()).thenReturn(true);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_GOOD);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_GREAT);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
}
@Test
public void testStrongEnough_Cellular_VPN() {
mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final TelephonyManager telephonyManager = mock(TelephonyManager.class);
when(mContext.getSystemService(TelephonyManager.class))
.thenReturn(telephonyManager);
when(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager);
final ArgumentCaptor<TelephonyCallback> signalStrengthsCaptor =
ArgumentCaptor.forClass(TelephonyCallback.class);
doNothing().when(telephonyManager)
.registerTelephonyCallback(any(), signalStrengthsCaptor.capture());
final ArgumentCaptor<NetworkCallback> callbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
final Network net = mock(Network.class);
final NetworkCapabilities caps = createCapabilitiesBuilder()
.addTransportType(TRANSPORT_CELLULAR)
.addTransportType(TRANSPORT_VPN)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
.setSubscriptionIds(Set.of(7357))
.build();
final JobInfo.Builder baseJobBuilder = createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
DataUnit.MEBIBYTES.toBytes(1))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
final JobStatus jobExp = createJobStatus(baseJobBuilder.setExpedited(true));
final JobStatus jobHigh = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_HIGH));
final JobStatus jobDefEarly = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 1000, now + 100_000);
final JobStatus jobDefLate = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_DEFAULT),
now - 100_000, now + 1000);
final JobStatus jobLow = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_LOW));
final JobStatus jobMin = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
final ConnectivityController controller = new ConnectivityController(mService);
final NetworkCallback generalCallback = callbackCaptor.getValue();
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
when(mService.isBatteryCharging()).thenReturn(false);
when(mService.isBatteryNotLow()).thenReturn(false);
answerNetwork(generalCallback, null, null, net, caps);
final TelephonyCallback.SignalStrengthsListener signalStrengthsListener =
(TelephonyCallback.SignalStrengthsListener) signalStrengthsCaptor.getValue();
controller.maybeStartTrackingJobLocked(jobMinRunner, null);
controller.prepareForExecutionLocked(jobMinRunner);
final SignalStrength signalStrength = mock(SignalStrength.class);
when(signalStrength.getLevel()).thenReturn(
CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
signalStrengthsListener.onSignalStrengthsChanged(signalStrength);
// We don't restrict data via VPN over cellular.
assertTrue(controller.isSatisfied(jobExp, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobHigh, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefEarly, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobDefLate, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobLow, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMin, net, caps, mConstants));
assertTrue(controller.isSatisfied(jobMinRunner, net, caps, mConstants));
}
@Test
public void testRelaxed() throws Exception {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();