Disconnect a network if it's suspended for 5 minutes for wear
The suspension state is designed to be a transient state. Implement an enforcement to disconnect the network after staying at this state for 5 minutes. This can avoid apps unnecessarily waking up the device for network activities even when there's no network actually connected. Test: unit tests. Locally tested with a signal blocking bag. Bug: 404570281 Flag: EXEMPT mainline Change-Id: I1427a72402f7ec64eda537a89c86768466a72328
This commit is contained in:
parent
1cdb0a5609
commit
3dcb3c5890
3 changed files with 157 additions and 0 deletions
|
|
@ -507,6 +507,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
private static final int ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS = 20 * 1000;
|
||||
// Timeout in case the "actively prefer bad wifi" feature is off
|
||||
private static final int DEFAULT_EVALUATION_TIMEOUT_MS = 8 * 1000;
|
||||
// Timeout threshold on how long a network is allowed to stay at SUSPENDED state. When the
|
||||
// timeout is reached, the network will be torn down.
|
||||
private static final int DEFAULT_NETWORK_SUSPENDED_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
// Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
|
||||
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
|
||||
|
|
@ -978,6 +981,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
*/
|
||||
private static final int EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS = 63;
|
||||
|
||||
/**
|
||||
* indicates a timeout period to wait for the network leaving suspended state is over.
|
||||
* The network should be torn down now.
|
||||
*/
|
||||
private static final int EVENT_TIMEOUT_NETWORK_SUSPENDED = 64;
|
||||
|
||||
/**
|
||||
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
|
||||
* should be shown.
|
||||
|
|
@ -1760,6 +1769,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
WIFI_DATA_INACTIVITY_TIMEOUT, 15);
|
||||
}
|
||||
|
||||
/** Returns the timeout duration for a network to stay in SUSPENDED state before being torn
|
||||
* down.
|
||||
*/
|
||||
public int getNetworkSuspendedTimeoutMs() {
|
||||
return DEFAULT_NETWORK_SUSPENDED_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the enforcement of short network suspension duration should be enabled.
|
||||
*/
|
||||
public boolean isShortNetworkSuspensionEnforced(Context context) {
|
||||
// The feature is only enabled on watch for now. See b/422119228 for details.
|
||||
return context.getPackageManager().hasSystemFeature(FEATURE_WATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DeviceConfigUtils#isTetheringFeatureEnabled
|
||||
*/
|
||||
|
|
@ -3757,6 +3781,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
}
|
||||
|
||||
private void handleNetwokSuspendedTimeout(NetworkAgentInfo nai) {
|
||||
// Tear down the network if it has stayed in SUSPENDED state for longer than intended.
|
||||
disconnectAndDestroyNetwork(nai);
|
||||
}
|
||||
|
||||
private void handleFreezeNetworkCallbacks(int[] uids, int[] frozenStates) {
|
||||
if (!mQueueCallbacksForFrozenApps) {
|
||||
return;
|
||||
|
|
@ -5833,6 +5862,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
nai.networkMonitor().notifyNetworkDisconnected();
|
||||
mNetworkAgentInfos.remove(nai);
|
||||
if (mDeps.isShortNetworkSuspensionEnforced(mContext)) {
|
||||
// Remove the message to disconnect the network if it's in suspended state for too long,
|
||||
// this is to allow the system to reclaim the memory associated with this NAI earlier,
|
||||
// rather than unnecessarily waiting for the timeout period.
|
||||
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_SUSPENDED, nai);
|
||||
}
|
||||
nai.clatd.update();
|
||||
synchronized (mNetworkForNetId) {
|
||||
// Remove the NetworkAgent, but don't mark the netId as
|
||||
|
|
@ -7347,6 +7382,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
case EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS:
|
||||
handleClearFirewallDestroySocketReasons(msg.arg1);
|
||||
break;
|
||||
case EVENT_TIMEOUT_NETWORK_SUSPENDED:
|
||||
handleNetwokSuspendedTimeout((NetworkAgentInfo) msg.obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10912,6 +10950,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
// TODO (b/73132094) : remove this call once the few users of onSuspended and
|
||||
// onResumed have been removed.
|
||||
notifyNetworkCallbacks(nai, suspended ? CALLBACK_SUSPENDED : CALLBACK_RESUMED);
|
||||
if (mDeps.isShortNetworkSuspensionEnforced(mContext)) {
|
||||
if (suspended) {
|
||||
mHandler.sendMessageDelayed(
|
||||
mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_SUSPENDED,
|
||||
nai), mDeps.getNetworkSuspendedTimeoutMs());
|
||||
} else {
|
||||
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_SUSPENDED, nai);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prevSuspended != suspended || prevRoaming != roaming) {
|
||||
// updateNetworkInfo will mix in the suspended info from the capabilities and
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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
|
||||
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import com.android.testutils.ConnectivityModuleTest
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.TestableNetworkCallback
|
||||
import com.android.testutils.TestableNetworkCallback.Event.Lost
|
||||
import kotlin.test.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
private const val NETWORK_SUSPENDED_TIMEOUT_SHORT = 150
|
||||
|
||||
@DevSdkIgnoreRunner.MonitorThreadLeak
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
|
||||
class CSNetworkSuspendedTest : CSTest() {
|
||||
private val cb = TestableNetworkCallback(timeoutMs = 30 * 1000)
|
||||
private val cellNc = defaultNc().addTransportType(TRANSPORT_CELLULAR).addCapability(
|
||||
NET_CAPABILITY_INTERNET
|
||||
)
|
||||
private val cellNcSuspended = defaultNc().removeCapability(
|
||||
NET_CAPABILITY_NOT_SUSPENDED
|
||||
).addCapability(
|
||||
NET_CAPABILITY_INTERNET
|
||||
).addTransportType(TRANSPORT_CELLULAR)
|
||||
|
||||
@ConnectivityModuleTest
|
||||
@Test
|
||||
fun testNetworkSuspendedTimeoutDisconnected() {
|
||||
// Set a short timeout to make the test faster.
|
||||
deps.networkSuspendedTimeoutForTestMs = NETWORK_SUSPENDED_TIMEOUT_SHORT
|
||||
val agent = Agent(cellNc)
|
||||
agent.connect()
|
||||
val networkInfo = cm.activeNetworkInfo
|
||||
assertNotNull(networkInfo)
|
||||
cm.registerNetworkCallback(
|
||||
NetworkRequest.Builder().setCapabilities(
|
||||
cellNc
|
||||
).removeCapability(NET_CAPABILITY_NOT_SUSPENDED).build(),
|
||||
cb
|
||||
)
|
||||
// Update the network to suspended.
|
||||
agent.sendNetworkCapabilities(cellNcSuspended)
|
||||
|
||||
// Verify that the suspended network eventually disconnects.
|
||||
cb.assertNoCallback(
|
||||
NETWORK_SUSPENDED_TIMEOUT_SHORT.toLong()
|
||||
) { it is Lost && agent.network == it.network }
|
||||
cb.eventuallyExpect<Lost> { it.network == agent.network }
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
}
|
||||
|
||||
@ConnectivityModuleTest
|
||||
@Test
|
||||
fun testNetworkExitsSuspendedRemainsConnected() {
|
||||
// Set a slightly longer(0.5s) timeout to test (SUSPENDED -> NOT_SUSPENDED)
|
||||
deps.networkSuspendedTimeoutForTestMs = 500
|
||||
val agent = Agent(cellNc)
|
||||
agent.connect()
|
||||
val networkInfo = cm.activeNetworkInfo
|
||||
assertNotNull(networkInfo)
|
||||
cm.registerNetworkCallback(
|
||||
NetworkRequest.Builder().setCapabilities(
|
||||
cellNc
|
||||
).removeCapability(NET_CAPABILITY_NOT_SUSPENDED).build(),
|
||||
cb
|
||||
)
|
||||
// Update the network to suspended.
|
||||
agent.sendNetworkCapabilities(cellNcSuspended)
|
||||
// Update the network back to not suspended.
|
||||
agent.sendNetworkCapabilities(cellNc)
|
||||
|
||||
// Verify that the network is not disconnected after the suspended state timeout,
|
||||
// since the network returned to NOT_SUSPENDED state before the timeout.
|
||||
cb.assertNoCallback(750) { it is Lost && agent.network == it.network }
|
||||
cm.unregisterNetworkCallback(cb)
|
||||
}
|
||||
}
|
||||
|
|
@ -423,6 +423,16 @@ open class CSTest {
|
|||
return defaultWifiDataInactivityTimeoutForTest
|
||||
}
|
||||
|
||||
var networkSuspendedTimeoutForTestMs: Int = 10
|
||||
override fun getNetworkSuspendedTimeoutMs(): Int {
|
||||
return networkSuspendedTimeoutForTestMs
|
||||
}
|
||||
|
||||
// Enable the feature for testing.
|
||||
override fun isShortNetworkSuspensionEnforced(context: Context?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isChangeEnabled(changeId: Long, pkg: String, user: UserHandle) =
|
||||
changeId in enabledChangeIds
|
||||
override fun isChangeEnabled(changeId: Long, uid: Int) =
|
||||
|
|
|
|||
Loading…
Reference in a new issue