1 /*
2  * Copyright (C) 2019 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.cts.net.hostside;
18 
19 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
20 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
21 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
24 
25 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
26 import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertNotEquals;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assert.fail;
33 
34 import android.app.ActivityManager;
35 import android.app.Instrumentation;
36 import android.content.Context;
37 import android.location.LocationManager;
38 import android.net.ConnectivityManager;
39 import android.net.ConnectivityManager.NetworkCallback;
40 import android.net.Network;
41 import android.net.NetworkCapabilities;
42 import android.net.wifi.WifiManager;
43 import android.os.Process;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.util.Pair;
47 
48 import com.android.compatibility.common.util.AppStandbyUtils;
49 import com.android.compatibility.common.util.BatteryUtils;
50 
51 import java.util.concurrent.CountDownLatch;
52 import java.util.concurrent.TimeUnit;
53 
54 import androidx.test.platform.app.InstrumentationRegistry;
55 
56 public class NetworkPolicyTestUtils {
57 
58     private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
59 
60     private static ConnectivityManager mCm;
61     private static WifiManager mWm;
62 
63     private static Boolean mBatterySaverSupported;
64     private static Boolean mDataSaverSupported;
65     private static Boolean mDozeModeSupported;
66     private static Boolean mAppStandbySupported;
67 
NetworkPolicyTestUtils()68     private NetworkPolicyTestUtils() {}
69 
isBatterySaverSupported()70     public static boolean isBatterySaverSupported() {
71         if (mBatterySaverSupported == null) {
72             mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
73         }
74         return mBatterySaverSupported;
75     }
76 
77     /**
78      * As per CDD requirements, if the device doesn't support data saver mode then
79      * ConnectivityManager.getRestrictBackgroundStatus() will always return
80      * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
81      * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
82      * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
83      */
isDataSaverSupported()84     public static boolean isDataSaverSupported() {
85         if (mDataSaverSupported == null) {
86             assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
87             try {
88                 setRestrictBackground(true);
89                 mDataSaverSupported = !isMyRestrictBackgroundStatus(
90                         RESTRICT_BACKGROUND_STATUS_DISABLED);
91             } finally {
92                 setRestrictBackground(false);
93             }
94         }
95         return mDataSaverSupported;
96     }
97 
isDozeModeSupported()98     public static boolean isDozeModeSupported() {
99         if (mDozeModeSupported == null) {
100             final String result = executeShellCommand("cmd deviceidle enabled deep");
101             mDozeModeSupported = result.equals("1");
102         }
103         return mDozeModeSupported;
104     }
105 
isAppStandbySupported()106     public static boolean isAppStandbySupported() {
107         if (mAppStandbySupported == null) {
108             mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
109         }
110         return mAppStandbySupported;
111     }
112 
isLowRamDevice()113     public static boolean isLowRamDevice() {
114         final ActivityManager am = (ActivityManager) getContext().getSystemService(
115                 Context.ACTIVITY_SERVICE);
116         return am.isLowRamDevice();
117     }
118 
isLocationEnabled()119     public static boolean isLocationEnabled() {
120         final LocationManager lm = (LocationManager) getContext().getSystemService(
121                 Context.LOCATION_SERVICE);
122         return lm.isLocationEnabled();
123     }
124 
setLocationEnabled(boolean enabled)125     public static void setLocationEnabled(boolean enabled) {
126         final LocationManager lm = (LocationManager) getContext().getSystemService(
127                 Context.LOCATION_SERVICE);
128         lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
129         assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
130         Log.d(TAG, "Changed location enabled state to " + enabled);
131     }
132 
isActiveNetworkMetered(boolean metered)133     public static boolean isActiveNetworkMetered(boolean metered) {
134         return getConnectivityManager().isActiveNetworkMetered() == metered;
135     }
136 
canChangeActiveNetworkMeteredness()137     public static boolean canChangeActiveNetworkMeteredness() {
138         final Network activeNetwork = getConnectivityManager().getActiveNetwork();
139         final NetworkCapabilities networkCapabilities
140                 = getConnectivityManager().getNetworkCapabilities(activeNetwork);
141         return networkCapabilities.hasTransport(TRANSPORT_WIFI);
142     }
143 
setupMeteredNetwork(boolean metered)144     public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
145         if (isActiveNetworkMetered(metered)) {
146             return null;
147         }
148         final boolean isLocationEnabled = isLocationEnabled();
149         try {
150             if (!isLocationEnabled) {
151                 setLocationEnabled(true);
152             }
153             final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
154             assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
155             setWifiMeteredStatus(ssid, metered);
156             return Pair.create(ssid, !metered);
157         } finally {
158             // Reset the location enabled state
159             if (!isLocationEnabled) {
160                 setLocationEnabled(false);
161             }
162         }
163     }
164 
resetMeteredNetwork(String ssid, boolean metered)165     public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
166         setWifiMeteredStatus(ssid, metered);
167     }
168 
setWifiMeteredStatus(String ssid, boolean metered)169     public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
170         assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
171         final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
172         executeShellCommand(cmd);
173         assertWifiMeteredStatus(ssid, metered);
174         assertActiveNetworkMetered(metered);
175     }
176 
assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus)177     public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
178         final String result = executeShellCommand("cmd netpolicy list wifi-networks");
179         final String expectedLine = ssid + ";" + expectedMeteredStatus;
180         assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
181                 result.contains(expectedLine));
182     }
183 
184     // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
assertActiveNetworkMetered(boolean expectedMeteredStatus)185     public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
186         final CountDownLatch latch = new CountDownLatch(1);
187         final NetworkCallback networkCallback = new NetworkCallback() {
188             @Override
189             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
190                 final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
191                 if (metered == expectedMeteredStatus) {
192                     latch.countDown();
193                 }
194             }
195         };
196         // Registering a callback here guarantees onCapabilitiesChanged is called immediately
197         // with the current setting. Therefore, if the setting has already been changed,
198         // this method will return right away, and if not it will wait for the setting to change.
199         getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
200         if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
201             fail("Timed out waiting for active network metered status to change to "
202                     + expectedMeteredStatus + " ; network = "
203                     + getConnectivityManager().getActiveNetwork());
204         }
205         getConnectivityManager().unregisterNetworkCallback(networkCallback);
206     }
207 
setRestrictBackground(boolean enabled)208     public static void setRestrictBackground(boolean enabled) {
209         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
210         final String output = executeShellCommand("cmd netpolicy get restrict-background");
211         final String expectedSuffix = enabled ? "enabled" : "disabled";
212         assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
213                 output.endsWith(expectedSuffix));
214     }
215 
isMyRestrictBackgroundStatus(int expectedStatus)216     public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
217         final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
218         if (expectedStatus != actualStatus) {
219             Log.d(TAG, "MyRestrictBackgroundStatus: "
220                     + "Expected: " + restrictBackgroundValueToString(expectedStatus)
221                     + "; Actual: " + restrictBackgroundValueToString(actualStatus));
222             return false;
223         }
224         return true;
225     }
226 
227     // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
unquoteSSID(String ssid)228     private static String unquoteSSID(String ssid) {
229         // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
230         // Otherwise it's guaranteed not to start with a quote.
231         if (ssid.charAt(0) == '"') {
232             return ssid.substring(1, ssid.length() - 1);
233         } else {
234             return ssid;
235         }
236     }
237 
restrictBackgroundValueToString(int status)238     public static String restrictBackgroundValueToString(int status) {
239         switch (status) {
240             case RESTRICT_BACKGROUND_STATUS_DISABLED:
241                 return "DISABLED";
242             case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
243                 return "WHITELISTED";
244             case RESTRICT_BACKGROUND_STATUS_ENABLED:
245                 return "ENABLED";
246             default:
247                 return "UNKNOWN_STATUS_" + status;
248         }
249     }
250 
executeShellCommand(String command)251     public static String executeShellCommand(String command) {
252         final String result = runShellCommand(command).trim();
253         Log.d(TAG, "Output of '" + command + "': '" + result + "'");
254         return result;
255     }
256 
assertMyRestrictBackgroundStatus(int expectedStatus)257     public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
258         final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
259         assertEquals(restrictBackgroundValueToString(expectedStatus),
260                 restrictBackgroundValueToString(actualStatus));
261     }
262 
getConnectivityManager()263     public static ConnectivityManager getConnectivityManager() {
264         if (mCm == null) {
265             mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
266         }
267         return mCm;
268     }
269 
getWifiManager()270     public static WifiManager getWifiManager() {
271         if (mWm == null) {
272             mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
273         }
274         return mWm;
275     }
276 
getContext()277     public static Context getContext() {
278         return getInstrumentation().getContext();
279     }
280 
getInstrumentation()281     public static Instrumentation getInstrumentation() {
282         return InstrumentationRegistry.getInstrumentation();
283     }
284 }
285