Merge "Use data plans for better job scheduling."
This commit is contained in:
@@ -6992,6 +6992,7 @@ package android.app.job {
|
||||
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
|
||||
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
|
||||
method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
|
||||
method public android.app.job.JobInfo.Builder setIsPrefetch(boolean);
|
||||
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
|
||||
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
|
||||
method public android.app.job.JobInfo.Builder setPeriodic(long);
|
||||
|
||||
@@ -4381,6 +4381,8 @@ package android.telephony {
|
||||
|
||||
public class SubscriptionManager {
|
||||
method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
|
||||
method public void setSubscriptionOverrideCongested(int, boolean, long);
|
||||
method public void setSubscriptionOverrideUnmetered(int, boolean, long);
|
||||
method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
|
||||
field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
|
||||
field public static final java.lang.String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
|
||||
|
||||
@@ -250,6 +250,11 @@ public class JobInfo implements Parcelable {
|
||||
*/
|
||||
public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_IS_PREFETCH = 1 << 2;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -1363,6 +1368,28 @@ public class JobInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting this to true indicates that this job is designed to prefetch
|
||||
* content that will make a material improvement to the experience of
|
||||
* the specific user of this device. For example, fetching top headlines
|
||||
* of interest to the current user.
|
||||
* <p>
|
||||
* The system may use this signal to relax the network constraints you
|
||||
* originally requested, such as allowing a
|
||||
* {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
|
||||
* network when there is a surplus of metered data available. The system
|
||||
* may also use this signal in combination with end user usage patterns
|
||||
* to ensure data is prefetched before the user launches your app.
|
||||
*/
|
||||
public Builder setIsPrefetch(boolean isPrefetch) {
|
||||
if (isPrefetch) {
|
||||
mFlags |= FLAG_IS_PREFETCH;
|
||||
} else {
|
||||
mFlags &= (~FLAG_IS_PREFETCH);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to persist this job across device reboots.
|
||||
*
|
||||
|
||||
@@ -436,6 +436,11 @@ public abstract class PackageManagerInternal {
|
||||
*/
|
||||
public abstract int getUidTargetSdkVersion(int uid);
|
||||
|
||||
/**
|
||||
* Return the taget SDK version for the app with the given package name.
|
||||
*/
|
||||
public abstract int getPackageTargetSdkVersion(String packageName);
|
||||
|
||||
/** Whether the binder caller can access instant apps. */
|
||||
public abstract boolean canAccessInstantApps(int callingUid, int userId);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ interface INetworkPolicyManager {
|
||||
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
|
||||
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
|
||||
String getSubscriptionPlansOwner(int subId);
|
||||
void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
|
||||
|
||||
void factoryReset(String subscriber);
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.server.job.controllers;
|
||||
|
||||
import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
@@ -35,6 +39,7 @@ import android.util.Slog;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobServiceContext;
|
||||
import com.android.server.job.StateChangedListener;
|
||||
@@ -62,15 +67,15 @@ public final class ConnectivityController extends StateController implements
|
||||
private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
|
||||
|
||||
/** Singleton. */
|
||||
private static ConnectivityController mSingleton;
|
||||
private static ConnectivityController sSingleton;
|
||||
private static Object sCreationLock = new Object();
|
||||
|
||||
public static ConnectivityController get(JobSchedulerService jms) {
|
||||
synchronized (sCreationLock) {
|
||||
if (mSingleton == null) {
|
||||
mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
|
||||
if (sSingleton == null) {
|
||||
sSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
|
||||
}
|
||||
return mSingleton;
|
||||
return sSingleton;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,37 +110,29 @@ public final class ConnectivityController extends StateController implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if running the given job on the given network is sane.
|
||||
* Test to see if running the given job on the given network is insane.
|
||||
* <p>
|
||||
* For example, if a job is trying to send 10MB over a 128Kbps EDGE
|
||||
* connection, it would take 10.4 minutes, and has no chance of succeeding
|
||||
* before the job times out, so we'd be insane to try running it.
|
||||
*/
|
||||
private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean isInsane(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities) {
|
||||
final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
|
||||
if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
|
||||
// We don't know how large the job is; cross our fingers!
|
||||
return true;
|
||||
}
|
||||
if (capabilities == null) {
|
||||
// We don't know what the network is like; cross our fingers!
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't ask developers to differentiate between upstream/downstream
|
||||
// in their size estimates, so test against the slowest link direction.
|
||||
final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
|
||||
final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
|
||||
final long slowest;
|
||||
if (downstream > 0 && upstream > 0) {
|
||||
slowest = Math.min(downstream, upstream);
|
||||
} else if (downstream > 0) {
|
||||
slowest = downstream;
|
||||
} else if (upstream > 0) {
|
||||
slowest = upstream;
|
||||
} else {
|
||||
final long slowest = NetworkCapabilities.minBandwidth(
|
||||
capabilities.getLinkDownstreamBandwidthKbps(),
|
||||
capabilities.getLinkUpstreamBandwidthKbps());
|
||||
if (slowest == LINK_BANDWIDTH_UNSPECIFIED) {
|
||||
// We don't know what the network is like; cross our fingers!
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
|
||||
@@ -144,28 +141,87 @@ public final class ConnectivityController extends StateController implements
|
||||
// If we'd never finish before the timeout, we'd be insane!
|
||||
Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
|
||||
+ " kbps network would take " + estimatedMillis + "ms; that's insane!");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities) {
|
||||
// If network is congested, and job is less than 50% through the
|
||||
// developer-requested window, then we're okay delaying the job.
|
||||
if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
|
||||
return jobStatus.getFractionRunTime() < 0.5;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities) {
|
||||
return jobStatus.getJob().getRequiredNetwork().networkCapabilities
|
||||
.satisfiedByNetworkCapabilities(capabilities);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities) {
|
||||
// Only consider doing this for prefetching jobs
|
||||
if ((jobStatus.getJob().getFlags() & JobInfo.FLAG_IS_PREFETCH) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See if we match after relaxing any unmetered request
|
||||
final NetworkCapabilities relaxed = new NetworkCapabilities(
|
||||
jobStatus.getJob().getRequiredNetwork().networkCapabilities)
|
||||
.removeCapability(NET_CAPABILITY_NOT_METERED);
|
||||
if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
|
||||
// TODO: treat this as "maybe" response; need to check quotas
|
||||
return jobStatus.getFractionRunTime() > 0.5;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean isSatisfied(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities) {
|
||||
// Zeroth, we gotta have a network to think about being satisfied
|
||||
if (network == null || capabilities == null) return false;
|
||||
|
||||
// First, are we insane?
|
||||
if (isInsane(jobStatus, network, capabilities)) return false;
|
||||
|
||||
// Second, is the network congested?
|
||||
if (isCongestionDelayed(jobStatus, network, capabilities)) return false;
|
||||
|
||||
// Third, is the network a strict match?
|
||||
if (isStrictSatisfied(jobStatus, network, capabilities)) return true;
|
||||
|
||||
// Third, is the network a relaxed match?
|
||||
if (isRelaxedSatisfied(jobStatus, network, capabilities)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
|
||||
// TODO: consider matching against non-active networks
|
||||
|
||||
final int jobUid = jobStatus.getSourceUid();
|
||||
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
|
||||
|
||||
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
|
||||
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
|
||||
final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
|
||||
|
||||
final boolean connected = (info != null) && info.isConnected();
|
||||
final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
|
||||
.satisfiedByNetworkCapabilities(capabilities);
|
||||
final boolean sane = isSane(jobStatus, capabilities);
|
||||
final boolean satisfied = isSatisfied(jobStatus, network, capabilities);
|
||||
|
||||
final boolean changed = jobStatus
|
||||
.setConnectivityConstraintSatisfied(connected && satisfied && sane);
|
||||
.setConnectivityConstraintSatisfied(connected && satisfied);
|
||||
|
||||
// Pass along the evaluated network for job to use; prevents race
|
||||
// conditions as default routes change over time, and opens the door to
|
||||
@@ -181,8 +237,7 @@ public final class ConnectivityController extends StateController implements
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
|
||||
+ " for " + jobStatus + ": connected=" + connected
|
||||
+ " satisfied=" + satisfied
|
||||
+ " sane=" + sane);
|
||||
+ " satisfied=" + satisfied);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.app.job.JobInfo;
|
||||
import android.app.job.JobWorkItem;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.net.Network;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
@@ -96,6 +97,7 @@ public final class JobStatus {
|
||||
final JobInfo job;
|
||||
/** Uid of the package requesting this job. */
|
||||
final int callingUid;
|
||||
final int targetSdkVersion;
|
||||
final String batteryName;
|
||||
|
||||
final String sourcePackageName;
|
||||
@@ -243,12 +245,13 @@ public final class JobStatus {
|
||||
return callingUid;
|
||||
}
|
||||
|
||||
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
|
||||
private JobStatus(JobInfo job, int callingUid, int targetSdkVersion, String sourcePackageName,
|
||||
int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
|
||||
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime) {
|
||||
this.job = job;
|
||||
this.callingUid = callingUid;
|
||||
this.targetSdkVersion = targetSdkVersion;
|
||||
this.standbyBucket = standbyBucket;
|
||||
this.baseHeartbeat = heartbeat;
|
||||
|
||||
@@ -307,7 +310,7 @@ public final class JobStatus {
|
||||
/** Copy constructor: used specifically when cloning JobStatus objects for persistence,
|
||||
* so we preserve RTC window bounds if the source object has them. */
|
||||
public JobStatus(JobStatus jobStatus) {
|
||||
this(jobStatus.getJob(), jobStatus.getUid(),
|
||||
this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.targetSdkVersion,
|
||||
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
|
||||
jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
|
||||
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
|
||||
@@ -334,7 +337,7 @@ public final class JobStatus {
|
||||
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime,
|
||||
Pair<Long, Long> persistedExecutionTimesUTC) {
|
||||
this(job, callingUid, sourcePkgName, sourceUserId,
|
||||
this(job, callingUid, resolveTargetSdkVersion(job), sourcePkgName, sourceUserId,
|
||||
standbyBucket, baseHeartbeat,
|
||||
sourceTag, 0,
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
|
||||
@@ -357,7 +360,7 @@ public final class JobStatus {
|
||||
long newEarliestRuntimeElapsedMillis,
|
||||
long newLatestRuntimeElapsedMillis, int backoffAttempt,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime) {
|
||||
this(rescheduling.job, rescheduling.getUid(),
|
||||
this(rescheduling.job, rescheduling.getUid(), resolveTargetSdkVersion(rescheduling.job),
|
||||
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
|
||||
rescheduling.getStandbyBucket(), newBaseHeartbeat,
|
||||
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
|
||||
@@ -394,7 +397,7 @@ public final class JobStatus {
|
||||
long currentHeartbeat = js != null
|
||||
? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
|
||||
: 0;
|
||||
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
|
||||
return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId,
|
||||
standbyBucket, currentHeartbeat, tag, 0,
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
|
||||
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
|
||||
@@ -541,6 +544,10 @@ public final class JobStatus {
|
||||
return job.getId();
|
||||
}
|
||||
|
||||
public int getTargetSdkVersion() {
|
||||
return targetSdkVersion;
|
||||
}
|
||||
|
||||
public void printUniqueId(PrintWriter pw) {
|
||||
UserHandle.formatUid(pw, callingUid);
|
||||
pw.print("/");
|
||||
@@ -715,6 +722,37 @@ public final class JobStatus {
|
||||
return latestRunTimeElapsedMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fractional position of "now" within the "run time" window of
|
||||
* this job.
|
||||
* <p>
|
||||
* For example, if the earliest run time was 10 minutes ago, and the latest
|
||||
* run time is 30 minutes from now, this would return 0.25.
|
||||
* <p>
|
||||
* If the job has no window defined, returns 1. When only an earliest or
|
||||
* latest time is defined, it's treated as an infinitely small window at
|
||||
* that time.
|
||||
*/
|
||||
public float getFractionRunTime() {
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
|
||||
return 1;
|
||||
} else if (earliestRunTimeElapsedMillis == 0) {
|
||||
return now >= latestRunTimeElapsedMillis ? 1 : 0;
|
||||
} else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
|
||||
return now >= earliestRunTimeElapsedMillis ? 1 : 0;
|
||||
} else {
|
||||
if (now <= earliestRunTimeElapsedMillis) {
|
||||
return 0;
|
||||
} else if (now >= latestRunTimeElapsedMillis) {
|
||||
return 1;
|
||||
} else {
|
||||
return (float) (now - earliestRunTimeElapsedMillis)
|
||||
/ (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<Long, Long> getPersistedUtcTimes() {
|
||||
return mPersistedUtcTimes;
|
||||
}
|
||||
@@ -1093,6 +1131,11 @@ public final class JobStatus {
|
||||
}
|
||||
}
|
||||
|
||||
private static int resolveTargetSdkVersion(JobInfo job) {
|
||||
return LocalServices.getService(PackageManagerInternal.class)
|
||||
.getPackageTargetSdkVersion(job.getService().getPackageName());
|
||||
}
|
||||
|
||||
// Dumpsys infrastructure
|
||||
public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
|
||||
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.server.net;
|
||||
|
||||
import android.net.Network;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
|
||||
/**
|
||||
* Network Policy Manager local system service interface.
|
||||
*
|
||||
@@ -47,4 +50,20 @@ public abstract class NetworkPolicyManagerInternal {
|
||||
* @param added Denotes whether the {@param appId} has been added or removed from the whitelist.
|
||||
*/
|
||||
public abstract void onTempPowerSaveWhitelistChange(int appId, boolean added);
|
||||
|
||||
/**
|
||||
* Return the active {@link SubscriptionPlan} for the given network.
|
||||
*/
|
||||
public abstract SubscriptionPlan getSubscriptionPlan(Network network);
|
||||
|
||||
public static final int QUOTA_TYPE_JOBS = 1;
|
||||
public static final int QUOTA_TYPE_MULTIPATH = 2;
|
||||
|
||||
/**
|
||||
* Return the daily quota (in bytes) that can be opportunistically used on
|
||||
* the given network to improve the end user experience. It's called
|
||||
* "opportunistic" because it's traffic that would typically not use the
|
||||
* given network.
|
||||
*/
|
||||
public abstract long getSubscriptionOpportunisticQuota(Network network, int quotaType);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.net.NetworkPolicy.LIMIT_DISABLED;
|
||||
import static android.net.NetworkPolicy.SNOOZE_NEVER;
|
||||
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
||||
@@ -69,6 +70,7 @@ import static android.net.TrafficStats.MB_IN_BYTES;
|
||||
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
|
||||
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
|
||||
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
|
||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
import static com.android.internal.util.ArrayUtils.appendInt;
|
||||
@@ -134,8 +136,10 @@ import android.net.NetworkPolicy;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.net.NetworkQuotaInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkSpecifier;
|
||||
import android.net.NetworkState;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.StringNetworkSpecifier;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
@@ -174,6 +178,7 @@ import android.text.format.Formatter;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.DataUnit;
|
||||
import android.util.Log;
|
||||
import android.util.NtpTrustedTime;
|
||||
import android.util.Pair;
|
||||
@@ -182,6 +187,7 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.util.SparseLongArray;
|
||||
import android.util.TrustedTime;
|
||||
import android.util.Xml;
|
||||
|
||||
@@ -219,7 +225,6 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -332,6 +337,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
|
||||
private static final int MSG_POLICIES_CHANGED = 13;
|
||||
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
|
||||
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
|
||||
|
||||
private static final int UID_MSG_STATE_CHANGED = 100;
|
||||
private static final int UID_MSG_GONE = 101;
|
||||
@@ -384,6 +390,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
@GuardedBy("mNetworkPoliciesSecondLock")
|
||||
final SparseArray<String> mSubscriptionPlansOwner = new SparseArray<>();
|
||||
|
||||
/** Map from subId to daily opportunistic quota. */
|
||||
@GuardedBy("mNetworkPoliciesSecondLock")
|
||||
final SparseLongArray mSubscriptionOpportunisticQuota = new SparseLongArray();
|
||||
|
||||
/** Defined UID policies. */
|
||||
@GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
|
||||
/** Currently derived rules for each UID. */
|
||||
@@ -453,6 +463,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
@GuardedBy("mNetworkPoliciesSecondLock")
|
||||
private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
|
||||
|
||||
/** Map from netId to subId as of last update */
|
||||
@GuardedBy("mNetworkPoliciesSecondLock")
|
||||
private final SparseIntArray mNetIdToSubId = new SparseIntArray();
|
||||
|
||||
private final RemoteCallbackList<INetworkPolicyListener>
|
||||
mListeners = new RemoteCallbackList<>();
|
||||
|
||||
@@ -1504,8 +1518,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
|
||||
// First, generate identities of all connected networks so we can
|
||||
// quickly compare them against all defined policies below.
|
||||
mNetIdToSubId.clear();
|
||||
final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
|
||||
for (NetworkState state : states) {
|
||||
mNetIdToSubId.put(state.network.netId, parseSubId(state));
|
||||
if (state.networkInfo != null && state.networkInfo.isConnected()) {
|
||||
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
|
||||
identified.put(state, ident);
|
||||
@@ -1607,6 +1623,42 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
}
|
||||
mMeteredIfaces = newMeteredIfaces;
|
||||
|
||||
// Finally, calculate our opportunistic quotas
|
||||
// TODO: add experiments support to disable or tweak ratios
|
||||
mSubscriptionOpportunisticQuota.clear();
|
||||
for (NetworkState state : states) {
|
||||
final int subId = getSubIdLocked(state.network);
|
||||
final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
|
||||
final SubscriptionPlan plan = ArrayUtils.isEmpty(plans) ? null : plans[0];
|
||||
if (plan == null) continue;
|
||||
|
||||
// By default assume we have no quota
|
||||
long limitBytes = plan.getDataLimitBytes();
|
||||
long quotaBytes = 0;
|
||||
|
||||
if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
|
||||
// Ignore missing limits
|
||||
} else if (plan.getDataLimitBytes() == SubscriptionPlan.BYTES_UNLIMITED) {
|
||||
// Unlimited data; let's use 20MiB/day (600MiB/month)
|
||||
quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
|
||||
} else {
|
||||
// Limited data; let's only use 10% of remaining budget
|
||||
final Pair<ZonedDateTime, ZonedDateTime> cycle = plans[0].cycleIterator().next();
|
||||
final long start = cycle.first.toInstant().toEpochMilli();
|
||||
final long end = cycle.second.toInstant().toEpochMilli();
|
||||
final long totalBytes = getTotalBytes(
|
||||
NetworkTemplate.buildTemplateMobileAll(state.subscriberId), start, end);
|
||||
final long remainingBytes = limitBytes - totalBytes;
|
||||
final long remainingDays = Math.min(1, (end - RecurrenceRule.sClock.millis())
|
||||
/ TimeUnit.DAYS.toMillis(1));
|
||||
if (remainingBytes > 0) {
|
||||
quotaBytes = (remainingBytes / remainingDays) / 10;
|
||||
}
|
||||
}
|
||||
|
||||
mSubscriptionOpportunisticQuota.put(subId, quotaBytes);
|
||||
}
|
||||
|
||||
final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
|
||||
mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
|
||||
|
||||
@@ -2814,6 +2866,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
|
||||
long timeoutMillis, String callingPackage) {
|
||||
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
|
||||
|
||||
// We can only override when carrier told us about plans
|
||||
synchronized (mNetworkPoliciesSecondLock) {
|
||||
if (ArrayUtils.isEmpty(mSubscriptionPlans.get(subId))) {
|
||||
throw new IllegalStateException(
|
||||
"Must provide SubscriptionPlan information before overriding");
|
||||
}
|
||||
}
|
||||
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
|
||||
overrideMask, overrideValue, subId));
|
||||
if (timeoutMillis > 0) {
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
|
||||
overrideMask, 0, subId), timeoutMillis);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
|
||||
@@ -3819,6 +3892,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
|
||||
int overrideMask, int overrideValue) {
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
@@ -3922,6 +4005,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
resetUidFirewallRules(msg.arg1);
|
||||
return true;
|
||||
}
|
||||
case MSG_SUBSCRIPTION_OVERRIDE: {
|
||||
final int overrideMask = msg.arg1;
|
||||
final int overrideValue = msg.arg2;
|
||||
final int subId = (int) msg.obj;
|
||||
final int length = mListeners.beginBroadcast();
|
||||
for (int i = 0; i < length; i++) {
|
||||
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
|
||||
dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
@@ -4404,6 +4499,42 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
updateRulesForTempWhitelistChangeUL(appId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionPlan getSubscriptionPlan(Network network) {
|
||||
synchronized (mNetworkPoliciesSecondLock) {
|
||||
final SubscriptionPlan[] plans = mSubscriptionPlans.get(getSubIdLocked(network));
|
||||
return ArrayUtils.isEmpty(plans) ? null : plans[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
|
||||
synchronized (mNetworkPoliciesSecondLock) {
|
||||
// TODO: handle splitting quota between use-cases
|
||||
return mSubscriptionOpportunisticQuota.get(getSubIdLocked(network));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int parseSubId(NetworkState state) {
|
||||
// TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
|
||||
int subId = INVALID_SUBSCRIPTION_ID;
|
||||
if (state != null && state.networkCapabilities != null
|
||||
&& state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
|
||||
NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
|
||||
if (spec instanceof StringNetworkSpecifier) {
|
||||
try {
|
||||
subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return subId;
|
||||
}
|
||||
|
||||
private int getSubIdLocked(Network network) {
|
||||
return mNetIdToSubId.get(network.netId, INVALID_SUBSCRIPTION_ID);
|
||||
}
|
||||
|
||||
private static boolean hasRule(int uidRules, int rule) {
|
||||
|
||||
@@ -18900,6 +18900,14 @@ Slog.e("TODD",
|
||||
return Build.VERSION_CODES.CUR_DEVELOPMENT;
|
||||
}
|
||||
|
||||
private int getPackageTargetSdkVersionLockedLPr(String packageName) {
|
||||
final PackageParser.Package p = mPackages.get(packageName);
|
||||
if (p != null) {
|
||||
return p.applicationInfo.targetSdkVersion;
|
||||
}
|
||||
return Build.VERSION_CODES.CUR_DEVELOPMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPreferredActivity(IntentFilter filter, int match,
|
||||
ComponentName[] set, ComponentName activity, int userId) {
|
||||
@@ -23418,6 +23426,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPackageTargetSdkVersion(String packageName) {
|
||||
synchronized (mPackages) {
|
||||
return getPackageTargetSdkVersionLockedLPr(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccessInstantApps(int callingUid, int userId) {
|
||||
return PackageManagerService.this.canViewInstantApps(callingUid, userId);
|
||||
|
||||
@@ -6,12 +6,17 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobInfo.Builder;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -24,6 +29,7 @@ import android.util.Pair;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
import com.android.server.IoThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobStore.JobSet;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
|
||||
@@ -65,6 +71,13 @@ public class JobStoreTest {
|
||||
JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
|
||||
mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
|
||||
|
||||
// Assume all packages are current SDK
|
||||
final PackageManagerInternal pm = mock(PackageManagerInternal.class);
|
||||
when(pm.getPackageTargetSdkVersion(anyString()))
|
||||
.thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, pm);
|
||||
|
||||
// Freeze the clocks at this moment in time
|
||||
JobSchedulerService.sSystemClock =
|
||||
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.android.server.job.controllers;
|
||||
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.DataUnit;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ConnectivityControllerTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// Assume all packages are current SDK
|
||||
final PackageManagerInternal pm = mock(PackageManagerInternal.class);
|
||||
when(pm.getPackageTargetSdkVersion(anyString()))
|
||||
.thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, pm);
|
||||
|
||||
// Freeze the clocks at this moment in time
|
||||
JobSchedulerService.sSystemClock =
|
||||
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sUptimeMillisClock =
|
||||
Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sElapsedRealtimeClock =
|
||||
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsane() throws Exception {
|
||||
final Network network = new Network(101);
|
||||
final JobInfo.Builder job = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
|
||||
// Slow network is too slow
|
||||
assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), network,
|
||||
createCapabilities().setLinkUpstreamBandwidthKbps(1)
|
||||
.setLinkDownstreamBandwidthKbps(1)));
|
||||
// Fast network looks great
|
||||
assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), network,
|
||||
createCapabilities().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1024)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCongestion() throws Exception {
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
final JobInfo.Builder job = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
|
||||
final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
|
||||
|
||||
// Uncongested network is whenever
|
||||
{
|
||||
final Network network = new Network(101);
|
||||
final NetworkCapabilities capabilities = createCapabilities()
|
||||
.addCapability(NET_CAPABILITY_NOT_CONGESTED);
|
||||
assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
|
||||
}
|
||||
|
||||
// Congested network is more selective
|
||||
{
|
||||
final Network network = new Network(101);
|
||||
final NetworkCapabilities capabilities = createCapabilities();
|
||||
assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelaxed() throws Exception {
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
final JobInfo.Builder job = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
|
||||
final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
|
||||
final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
|
||||
|
||||
job.setIsPrefetch(true);
|
||||
final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
|
||||
final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
|
||||
|
||||
// Unmetered network is whenever
|
||||
{
|
||||
final Network network = new Network(101);
|
||||
final NetworkCapabilities capabilities = createCapabilities()
|
||||
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
|
||||
.addCapability(NET_CAPABILITY_NOT_METERED);
|
||||
assertTrue(ConnectivityController.isSatisfied(early, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(late, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
|
||||
}
|
||||
|
||||
// Metered network is only when prefetching and late
|
||||
{
|
||||
final Network network = new Network(101);
|
||||
final NetworkCapabilities capabilities = createCapabilities()
|
||||
.addCapability(NET_CAPABILITY_NOT_CONGESTED);
|
||||
assertFalse(ConnectivityController.isSatisfied(early, network, capabilities));
|
||||
assertFalse(ConnectivityController.isSatisfied(late, network, capabilities));
|
||||
assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, network, capabilities));
|
||||
assertTrue(ConnectivityController.isSatisfied(latePrefetch, network, capabilities));
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createCapabilities() {
|
||||
return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NET_CAPABILITY_VALIDATED);
|
||||
}
|
||||
|
||||
private static JobInfo.Builder createJob() {
|
||||
return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
|
||||
}
|
||||
|
||||
private static JobStatus createJobStatus(JobInfo.Builder job) {
|
||||
return createJobStatus(job, 0, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
private static JobStatus createJobStatus(JobInfo.Builder job, long earliestRunTimeElapsedMillis,
|
||||
long latestRunTimeElapsedMillis) {
|
||||
return new JobStatus(job.build(), 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
|
||||
latestRunTimeElapsedMillis, 0, 0, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.android.server.job.controllers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class JobStatusTest {
|
||||
private static final double DELTA = 0.00001;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// Freeze the clocks at this moment in time
|
||||
JobSchedulerService.sSystemClock =
|
||||
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sUptimeMillisClock =
|
||||
Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
|
||||
JobSchedulerService.sElapsedRealtimeClock =
|
||||
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFraction() throws Exception {
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
|
||||
assertEquals(1, createJobStatus(0, Long.MAX_VALUE).getFractionRunTime(), DELTA);
|
||||
|
||||
assertEquals(1, createJobStatus(0, now - 1000).getFractionRunTime(), DELTA);
|
||||
assertEquals(0, createJobStatus(0, now + 1000).getFractionRunTime(), DELTA);
|
||||
|
||||
assertEquals(1, createJobStatus(now - 1000, Long.MAX_VALUE).getFractionRunTime(), DELTA);
|
||||
assertEquals(0, createJobStatus(now + 1000, Long.MAX_VALUE).getFractionRunTime(), DELTA);
|
||||
|
||||
assertEquals(0, createJobStatus(now, now + 2000).getFractionRunTime(), DELTA);
|
||||
assertEquals(0.25, createJobStatus(now - 500, now + 1500).getFractionRunTime(), DELTA);
|
||||
assertEquals(0.5, createJobStatus(now - 1000, now + 1000).getFractionRunTime(), DELTA);
|
||||
assertEquals(0.75, createJobStatus(now - 1500, now + 500).getFractionRunTime(), DELTA);
|
||||
assertEquals(1, createJobStatus(now - 2000, now).getFractionRunTime(), DELTA);
|
||||
}
|
||||
|
||||
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
|
||||
long latestRunTimeElapsedMillis) {
|
||||
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
|
||||
return new JobStatus(job, 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
|
||||
latestRunTimeElapsedMillis, 0, 0, null);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package android.telephony;
|
||||
|
||||
import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
|
||||
import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
|
||||
|
||||
import android.annotation.DurationMillisLong;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
@@ -30,6 +34,7 @@ import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.net.INetworkPolicyManager;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -38,7 +43,6 @@ import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
|
||||
import com.android.internal.telephony.ISub;
|
||||
@@ -1737,6 +1741,75 @@ public class SubscriptionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily override the billing relationship plan between a carrier and
|
||||
* a specific subscriber to be considered unmetered. This will be reflected
|
||||
* to apps via {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
|
||||
* <p>
|
||||
* This method is only accessible to the following narrow set of apps:
|
||||
* <ul>
|
||||
* <li>The carrier app for this subscriberId, as determined by
|
||||
* {@link TelephonyManager#hasCarrierPrivileges()}.
|
||||
* <li>The carrier app explicitly delegated access through
|
||||
* {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
|
||||
* </ul>
|
||||
*
|
||||
* @param subId the subscriber this override applies to.
|
||||
* @param overrideUnmetered set if the billing relationship should be
|
||||
* considered unmetered.
|
||||
* @param timeoutMillis the timeout after which the requested override will
|
||||
* be automatically cleared, or {@code 0} to leave in the
|
||||
* requested state until explicitly cleared, or the next reboot,
|
||||
* whichever happens first.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
|
||||
@DurationMillisLong long timeoutMillis) {
|
||||
try {
|
||||
final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
|
||||
mNetworkPolicy.setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
|
||||
timeoutMillis, mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily override the billing relationship plan between a carrier and
|
||||
* a specific subscriber to be considered congested. This will cause the
|
||||
* device to delay certain network requests when possible, such as developer
|
||||
* jobs that are willing to run in a flexible time window.
|
||||
* <p>
|
||||
* This method is only accessible to the following narrow set of apps:
|
||||
* <ul>
|
||||
* <li>The carrier app for this subscriberId, as determined by
|
||||
* {@link TelephonyManager#hasCarrierPrivileges()}.
|
||||
* <li>The carrier app explicitly delegated access through
|
||||
* {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
|
||||
* </ul>
|
||||
*
|
||||
* @param subId the subscriber this override applies to.
|
||||
* @param overrideCongested set if the subscription should be considered
|
||||
* congested.
|
||||
* @param timeoutMillis the timeout after which the requested override will
|
||||
* be automatically cleared, or {@code 0} to leave in the
|
||||
* requested state until explicitly cleared, or the next reboot,
|
||||
* whichever happens first.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
|
||||
@DurationMillisLong long timeoutMillis) {
|
||||
try {
|
||||
final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
|
||||
mNetworkPolicy.setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
|
||||
timeoutMillis, mContext.getOpPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link Intent} that can be launched towards the carrier app
|
||||
* that is currently defining the billing relationship plan through
|
||||
|
||||
Reference in New Issue
Block a user