1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.job.controllers; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; 21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; 22 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 23 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.anyBoolean; 28 import static org.mockito.ArgumentMatchers.anyString; 29 import static org.mockito.ArgumentMatchers.eq; 30 import static org.mockito.Mockito.doNothing; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.reset; 33 import static org.mockito.Mockito.when; 34 35 import android.app.job.JobInfo; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.pm.PackageManagerInternal; 39 import android.net.ConnectivityManager; 40 import android.net.ConnectivityManager.NetworkCallback; 41 import android.net.Network; 42 import android.net.NetworkCapabilities; 43 import android.net.NetworkInfo; 44 import android.net.NetworkInfo.DetailedState; 45 import android.net.NetworkPolicyManager; 46 import android.os.Build; 47 import android.os.SystemClock; 48 import android.util.DataUnit; 49 50 import com.android.server.LocalServices; 51 import com.android.server.job.JobSchedulerService; 52 import com.android.server.job.JobSchedulerService.Constants; 53 54 import org.junit.Before; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.ArgumentCaptor; 58 import org.mockito.Mock; 59 import org.mockito.junit.MockitoJUnitRunner; 60 61 import java.time.Clock; 62 import java.time.ZoneOffset; 63 64 @RunWith(MockitoJUnitRunner.class) 65 public class ConnectivityControllerTest { 66 67 @Mock private Context mContext; 68 @Mock private ConnectivityManager mConnManager; 69 @Mock private NetworkPolicyManager mNetPolicyManager; 70 @Mock private JobSchedulerService mService; 71 72 private Constants mConstants; 73 74 private static final int UID_RED = 10001; 75 private static final int UID_BLUE = 10002; 76 77 @Before 78 public void setUp() throws Exception { 79 // Assume all packages are current SDK 80 final PackageManagerInternal pm = mock(PackageManagerInternal.class); 81 when(pm.getPackageTargetSdkVersion(anyString())) 82 .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT); 83 LocalServices.removeServiceForTest(PackageManagerInternal.class); 84 LocalServices.addService(PackageManagerInternal.class, pm); 85 86 // Freeze the clocks at this moment in time 87 JobSchedulerService.sSystemClock = 88 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC); 89 JobSchedulerService.sUptimeMillisClock = 90 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC); 91 JobSchedulerService.sElapsedRealtimeClock = 92 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC); 93 94 // Assume default constants for now 95 mConstants = new Constants(); 96 97 // Get our mocks ready 98 when(mContext.getSystemServiceName(ConnectivityManager.class)) 99 .thenReturn(Context.CONNECTIVITY_SERVICE); 100 when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) 101 .thenReturn(mConnManager); 102 when(mContext.getSystemServiceName(NetworkPolicyManager.class)) 103 .thenReturn(Context.NETWORK_POLICY_SERVICE); 104 when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE)) 105 .thenReturn(mNetPolicyManager); 106 when(mService.getTestableContext()).thenReturn(mContext); 107 when(mService.getLock()).thenReturn(mService); 108 when(mService.getConstants()).thenReturn(mConstants); 109 } 110 111 @Test 112 public void testInsane() throws Exception { 113 final Network net = new Network(101); 114 final JobInfo.Builder job = createJob() 115 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) 116 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 117 118 // Slow network is too slow 119 assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net, 120 createCapabilities().setLinkUpstreamBandwidthKbps(1) 121 .setLinkDownstreamBandwidthKbps(1), mConstants)); 122 // Fast network looks great 123 assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net, 124 createCapabilities().setLinkUpstreamBandwidthKbps(1024) 125 .setLinkDownstreamBandwidthKbps(1024), mConstants)); 126 } 127 128 @Test 129 public void testCongestion() throws Exception { 130 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 131 final JobInfo.Builder job = createJob() 132 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) 133 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 134 final JobStatus early = createJobStatus(job, now - 1000, now + 2000); 135 final JobStatus late = createJobStatus(job, now - 2000, now + 1000); 136 137 // Uncongested network is whenever 138 { 139 final Network net = new Network(101); 140 final NetworkCapabilities caps = createCapabilities() 141 .addCapability(NET_CAPABILITY_NOT_CONGESTED); 142 assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); 143 assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); 144 } 145 146 // Congested network is more selective 147 { 148 final Network net = new Network(101); 149 final NetworkCapabilities caps = createCapabilities(); 150 assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); 151 assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); 152 } 153 } 154 155 @Test 156 public void testRelaxed() throws Exception { 157 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 158 final JobInfo.Builder job = createJob() 159 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1)) 160 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 161 final JobStatus early = createJobStatus(job, now - 1000, now + 2000); 162 final JobStatus late = createJobStatus(job, now - 2000, now + 1000); 163 164 job.setIsPrefetch(true); 165 final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000); 166 final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000); 167 168 // Unmetered network is whenever 169 { 170 final Network net = new Network(101); 171 final NetworkCapabilities caps = createCapabilities() 172 .addCapability(NET_CAPABILITY_NOT_CONGESTED) 173 .addCapability(NET_CAPABILITY_NOT_METERED); 174 assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants)); 175 assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants)); 176 assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); 177 assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); 178 } 179 180 // Metered network is only when prefetching and late 181 { 182 final Network net = new Network(101); 183 final NetworkCapabilities caps = createCapabilities() 184 .addCapability(NET_CAPABILITY_NOT_CONGESTED); 185 assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants)); 186 assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants)); 187 assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants)); 188 assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants)); 189 } 190 } 191 192 @Test 193 public void testUpdates() throws Exception { 194 final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor 195 .forClass(NetworkCallback.class); 196 doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture()); 197 198 final ConnectivityController controller = new ConnectivityController(mService); 199 200 final Network meteredNet = new Network(101); 201 final NetworkCapabilities meteredCaps = createCapabilities(); 202 final Network unmeteredNet = new Network(202); 203 final NetworkCapabilities unmeteredCaps = createCapabilities() 204 .addCapability(NET_CAPABILITY_NOT_METERED); 205 206 final JobStatus red = createJobStatus(createJob() 207 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) 208 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); 209 final JobStatus blue = createJobStatus(createJob() 210 .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) 211 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); 212 213 // Pretend we're offline when job is added 214 { 215 reset(mConnManager); 216 answerNetwork(UID_RED, null, null); 217 answerNetwork(UID_BLUE, null, null); 218 219 controller.maybeStartTrackingJobLocked(red, null); 220 controller.maybeStartTrackingJobLocked(blue, null); 221 222 assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 223 assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 224 } 225 226 // Metered network 227 { 228 reset(mConnManager); 229 answerNetwork(UID_RED, meteredNet, meteredCaps); 230 answerNetwork(UID_BLUE, meteredNet, meteredCaps); 231 232 callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps); 233 234 assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 235 assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 236 } 237 238 // Unmetered network background 239 { 240 reset(mConnManager); 241 answerNetwork(UID_RED, meteredNet, meteredCaps); 242 answerNetwork(UID_BLUE, meteredNet, meteredCaps); 243 244 callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); 245 246 assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 247 assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 248 } 249 250 // Lost metered network 251 { 252 reset(mConnManager); 253 answerNetwork(UID_RED, unmeteredNet, unmeteredCaps); 254 answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); 255 256 callback.getValue().onLost(meteredNet); 257 258 assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 259 assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 260 } 261 262 // Specific UID was blocked 263 { 264 reset(mConnManager); 265 answerNetwork(UID_RED, null, null); 266 answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps); 267 268 callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps); 269 270 assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 271 assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); 272 } 273 } 274 275 private void answerNetwork(int uid, Network net, NetworkCapabilities caps) { 276 when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net); 277 when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps); 278 if (net != null) { 279 final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null); 280 ni.setDetailedState(DetailedState.CONNECTED, null, null); 281 when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni); 282 } 283 } 284 285 private static NetworkCapabilities createCapabilities() { 286 return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET) 287 .addCapability(NET_CAPABILITY_VALIDATED); 288 } 289 290 private static JobInfo.Builder createJob() { 291 return new JobInfo.Builder(101, new ComponentName("foo", "bar")); 292 } 293 294 private static JobStatus createJobStatus(JobInfo.Builder job) { 295 return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE); 296 } 297 298 private static JobStatus createJobStatus(JobInfo.Builder job, int uid) { 299 return createJobStatus(job, uid, 0, Long.MAX_VALUE); 300 } 301 302 private static JobStatus createJobStatus(JobInfo.Builder job, 303 long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { 304 return createJobStatus(job, android.os.Process.NOBODY_UID, 305 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); 306 } 307 308 private static JobStatus createJobStatus(JobInfo.Builder job, int uid, 309 long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { 310 return new JobStatus(job.build(), uid, null, -1, 0, 0, null, 311 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0); 312 } 313 } 314