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.cts.deviceandprofileowner;
18 
19 import static com.android.compatibility.common.util.ShellIdentityUtils.invokeStaticMethodWithShellPermissions;
20 
21 import static org.junit.Assert.assertNotEquals;
22 
23 import android.content.Intent;
24 import android.net.ConnectivityManager;
25 import android.net.Network;
26 import android.net.NetworkInfo;
27 import android.net.NetworkInfo.DetailedState;
28 import android.net.NetworkInfo.State;
29 import android.net.wifi.WifiInfo;
30 import android.net.wifi.WifiManager;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.Messenger;
36 import android.os.SystemClock;
37 import android.util.Log;
38 
39 import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
40 import com.android.compatibility.common.util.SystemUtil;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.concurrent.BlockingQueue;
48 import java.util.concurrent.LinkedBlockingQueue;
49 import java.util.concurrent.TimeUnit;
50 
51 public class MeteredDataRestrictionTest extends BaseDeviceAdminTest {
52     private static final String TAG = MeteredDataRestrictionTest.class.getSimpleName();
53 
54     private static final String METERED_DATA_APP_PKG =
55             "com.android.cts.devicepolicy.metereddatatestapp";
56     private static final String METERED_DATA_APP_MAIN_ACTIVITY = METERED_DATA_APP_PKG
57             + ".MainActivity";
58 
59     private static final long WAIT_FOR_NETWORK_RECONNECTION_TIMEOUT_SEC = 10;
60     private static final long WAIT_FOR_NETWORK_INFO_TIMEOUT_SEC = 8;
61 
62     private static final int NUM_TRIES_METERED_STATUS_CHECK = 20;
63     private static final long INTERVAL_METERED_STATUS_CHECK_MS = 500;
64 
65     private static final String EXTRA_MESSENGER = "messenger";
66     private static final int MSG_NOTIFY_NETWORK_STATE = 1;
67 
68     private final Messenger mCallbackMessenger = new Messenger(new CallbackHandler());
69     private final BlockingQueue<NetworkInfo> mNetworkInfos = new LinkedBlockingQueue<>(1);
70 
71     private ConnectivityManager mCm;
72     private WifiManager mWm;
73     private String mMeteredWifi;
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         super.setUp();
78 
79         mCm = mContext.getSystemService(ConnectivityManager.class);
80         mWm = TestAppSystemServiceFactory.getWifiManager(mContext, BasicAdminReceiver.class);
81         setMeteredNetwork();
82     }
83 
84     @After
tearDown()85     public void tearDown() throws Exception {
86         super.tearDown();
87         resetMeteredNetwork();
88     }
89 
testSetMeteredDataDisabledPackages()90     public void testSetMeteredDataDisabledPackages() {
91         final List<String> restrictedPkgs = new ArrayList<>();
92         restrictedPkgs.add(METERED_DATA_APP_PKG);
93         final List<String> excludedPkgs = mDevicePolicyManager.setMeteredDataDisabledPackages(
94                 ADMIN_RECEIVER_COMPONENT, restrictedPkgs);
95         assertTrue("Packages not restricted: " + excludedPkgs, excludedPkgs.isEmpty());
96 
97         List<String> actualRestrictedPkgs = mDevicePolicyManager.getMeteredDataDisabledPackages(
98                 ADMIN_RECEIVER_COMPONENT);
99         assertEquals("Actual restricted pkgs: " + actualRestrictedPkgs,
100                 1, actualRestrictedPkgs.size());
101         assertTrue("Actual restricted pkgs: " + actualRestrictedPkgs,
102                 actualRestrictedPkgs.contains(METERED_DATA_APP_PKG));
103         verifyAppNetworkState(true);
104 
105         restrictedPkgs.clear();
106         mDevicePolicyManager.setMeteredDataDisabledPackages(ADMIN_RECEIVER_COMPONENT,
107                 restrictedPkgs);
108         actualRestrictedPkgs = mDevicePolicyManager.getMeteredDataDisabledPackages(
109                 ADMIN_RECEIVER_COMPONENT);
110         assertTrue("Actual restricted pkgs: " + actualRestrictedPkgs,
111                 actualRestrictedPkgs.isEmpty());
112         verifyAppNetworkState(false);
113     }
114 
verifyAppNetworkState(boolean blocked)115     private void verifyAppNetworkState(boolean blocked) {
116         final Bundle extras = new Bundle();
117         extras.putBinder(EXTRA_MESSENGER, mCallbackMessenger.getBinder());
118         mNetworkInfos.clear();
119         final Intent launchIntent = new Intent()
120                 .setClassName(METERED_DATA_APP_PKG, METERED_DATA_APP_MAIN_ACTIVITY)
121                 .putExtras(extras)
122                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
123         mContext.startActivity(launchIntent);
124 
125         try {
126             final NetworkInfo networkInfo = mNetworkInfos.poll(WAIT_FOR_NETWORK_INFO_TIMEOUT_SEC,
127                     TimeUnit.SECONDS);
128             if (networkInfo == null) {
129                 fail("Timed out waiting for the network info");
130             }
131 
132             final String expectedState = (blocked ? State.DISCONNECTED : State.CONNECTED).name();
133             final String expectedDetailedState = (blocked ? DetailedState.BLOCKED
134                     : DetailedState.CONNECTED).name();
135             assertEquals("Wrong state: " + networkInfo,
136                     expectedState, networkInfo.getState().name());
137             assertEquals("Wrong detailed state: " + networkInfo,
138                     expectedDetailedState, networkInfo.getDetailedState().name());
139         } catch (InterruptedException e) {
140             fail("Waiting for networkinfo got interrupted: " + e);
141         }
142     }
143 
144     private class CallbackHandler extends Handler {
CallbackHandler()145         public CallbackHandler() {
146             super(Looper.getMainLooper());
147         }
148 
149         @Override
handleMessage(Message msg)150         public void handleMessage(Message msg) {
151             if (msg.what == MSG_NOTIFY_NETWORK_STATE) {
152                 final NetworkInfo networkInfo = (NetworkInfo) msg.obj;
153                 if (!mNetworkInfos.offer(networkInfo)) {
154                     Log.e(TAG, "Error while adding networkinfo");
155                 }
156             } else {
157                 Log.e(TAG, "Unknown msg type: " + msg.what);
158             }
159         }
160     }
161 
setMeteredNetwork()162     private void setMeteredNetwork() throws Exception {
163         final int oldNetId = getActiveNetworkNetId();
164         final boolean oldMeteredState = mCm.isActiveNetworkMetered();
165         final NetworkInfo networkInfo = mCm.getActiveNetworkInfo();
166         Log.d(TAG, "setMeteredNetwork(): oldNetId=" + oldNetId
167                 + ", oldMeteredState=" + oldMeteredState + ", activeNetworkInfo=" + networkInfo);
168         if (networkInfo == null) {
169             fail("Active network is not available");
170         } else if (networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
171             fail("Active network doesn't support setting metered status: " + networkInfo);
172         }
173         final String ssid = setWifiMeteredStatus(true);
174 
175         // Set flag so status is reverted on resetMeteredNetwork();
176         mMeteredWifi = ssid;
177 
178         // When transitioning from unmetered to metered, the network stack will discconect
179         // the current WiFi connection and reconnect it. In this case we need to wait for
180         // the new network to come up.
181         if (!oldMeteredState) {
182             waitForReconnection(oldNetId);
183         }
184         assertWifiMeteredStatus(ssid, true);
185         assertActiveNetworkMetered(true);
186     }
187 
resetMeteredNetwork()188     private void resetMeteredNetwork() throws Exception {
189         if (mMeteredWifi != null) {
190             Log.i(TAG, "Resetting metered status for netId=" + mMeteredWifi);
191             setWifiMeteredStatus(mMeteredWifi, /* metered= */ null);
192             assertWifiMeteredStatus(mMeteredWifi, /* metered= */ null);
193             assertActiveNetworkMetered(false);
194         }
195     }
196 
setWifiMeteredStatus(Boolean metered)197     private String setWifiMeteredStatus(Boolean metered) throws Exception {
198         // Must use Shell permissions to get the connection info because on headless system user
199         // mode the method would be called by the device owner on system user, which have location
200         // disabled (and hence the returned connectionInfo would have the SSID redacted).
201         WifiInfo connectionInfo = invokeStaticMethodWithShellPermissions(
202                 () -> mWm.getConnectionInfo());
203 
204         String ssid = connectionInfo.getSSID();
205         assertNotNull("null SSID", ssid);
206         assertNotEquals("unknown SSID", WifiManager.UNKNOWN_SSID, ssid);
207 
208         final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
209         assertFalse("empty SSID", ssid.isEmpty());
210 
211         Log.d(TAG, "setWifiMeteredStatus(" + metered + "): setting " + connectionInfo);
212         setWifiMeteredStatus(netId, metered);
213         return netId;
214     }
215 
setWifiMeteredStatus(String ssid, Boolean metered)216     private void setWifiMeteredStatus(String ssid, Boolean metered) throws Exception {
217         Log.i(TAG, "Setting wi-fi network " + ssid + " metered status to " + metered);
218         executeCmd("cmd netpolicy set metered-network " + ssid + " " +
219                 (metered != null ? metered.toString() : "undefined"));
220     }
221 
assertWifiMeteredStatus(String ssid, Boolean metered)222     private void assertWifiMeteredStatus(String ssid, Boolean metered) throws Exception {
223         final String cmd = "cmd netpolicy list wifi-networks";
224         final String expectedResult = ssid + ";" + (metered != null ? metered.toString() : "none");
225         String cmdResult = null;
226         for (int i = 0; i < NUM_TRIES_METERED_STATUS_CHECK; ++i) {
227             cmdResult = executeCmd(cmd);
228             if (cmdResult.contains(expectedResult)) {
229                 return;
230             }
231             SystemClock.sleep(INTERVAL_METERED_STATUS_CHECK_MS);
232         }
233         fail("Timed out waiting for wifi metered status to change. expected=" + expectedResult
234                 + ", actual status=" + cmdResult);
235     }
236 
assertActiveNetworkMetered(boolean metered)237     private void assertActiveNetworkMetered(boolean metered) {
238         boolean actualMeteredStatus = !metered;
239         for (int i = 0; i < NUM_TRIES_METERED_STATUS_CHECK; ++i) {
240             actualMeteredStatus = mCm.isActiveNetworkMetered();
241             if (actualMeteredStatus == metered) {
242                 return;
243             }
244             SystemClock.sleep(INTERVAL_METERED_STATUS_CHECK_MS);
245         }
246         fail("Timed out waiting for active network metered status to change. expected="
247                 + metered + "; actual=" + actualMeteredStatus
248                 + "; networkInfo=" + mCm.getActiveNetwork());
249     }
250 
executeCmd(String cmd)251     private String executeCmd(String cmd) throws Exception {
252         final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
253         Log.i(TAG, "Cmd '" + cmd + "' result: " + result);
254         return result;
255     }
256 
getActiveNetworkNetId()257     private int getActiveNetworkNetId() {
258         Network network = mCm.getActiveNetwork();
259         if (network == null) {
260             return 0;
261         }
262         return network.getNetId();
263     }
264 
waitForReconnection(int oldNetId)265     private void waitForReconnection(int oldNetId) throws InterruptedException {
266         long pollingDeadline = System.currentTimeMillis()
267                 + WAIT_FOR_NETWORK_RECONNECTION_TIMEOUT_SEC * 1000;
268         int latestNetId;
269         do {
270             Thread.sleep(1000);
271             if (System.currentTimeMillis() >= pollingDeadline) {
272                 fail("Timeout waiting for network reconnection");
273             }
274             latestNetId = getActiveNetworkNetId();
275             // NetId will be 0 while old network is disconnected but new network
276             // has not come up yet.
277         } while (latestNetId == 0 || latestNetId == oldNetId);
278     }
279 }
280