ConnOnActivityStartTest.java revision cb2b2ef73cd245b61292d2f8b524dc2da40df99e
1/*
2 * Copyright (C) 2017 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
17package com.android.server.net;
18
19import static android.util.DebugUtils.valueToString;
20
21import static org.junit.Assert.assertEquals;
22import static org.junit.Assert.assertFalse;
23import static org.junit.Assert.assertNotNull;
24import static org.junit.Assert.assertTrue;
25import static org.junit.Assert.fail;
26
27import com.android.frameworks.servicestests.R;
28import com.android.servicestests.aidl.INetworkStateObserver;
29
30import android.app.PendingIntent;
31import android.content.BroadcastReceiver;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.IntentSender;
37import android.content.pm.IPackageDeleteObserver;
38import android.content.pm.PackageInstaller;
39import android.content.pm.PackageManager;
40import android.net.ConnectivityManager;
41import android.net.NetworkInfo;
42import android.net.Uri;
43import android.os.BatteryManager;
44import android.os.Bundle;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.support.test.InstrumentationRegistry;
48import android.support.test.filters.LargeTest;
49import android.support.test.runner.AndroidJUnit4;
50import android.support.test.uiautomator.UiDevice;
51import android.util.Log;
52
53import libcore.io.IoUtils;
54
55import org.junit.AfterClass;
56import org.junit.BeforeClass;
57import org.junit.Ignore;
58import org.junit.Test;
59import org.junit.runner.RunWith;
60
61import java.io.IOException;
62import java.io.InputStream;
63import java.io.OutputStream;
64import java.util.concurrent.CountDownLatch;
65import java.util.concurrent.TimeUnit;
66
67/**
68 * Tests for verifying network availability on activity start.
69 *
70 * To run the tests, use
71 *
72 * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services
73 *
74 * or the following steps:
75 *
76 * Build: m FrameworksServicesTests
77 * Install: adb install -r \
78 *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
79 * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \
80 *     com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
81 */
82@Ignore
83@LargeTest
84@RunWith(AndroidJUnit4.class)
85public class ConnOnActivityStartTest {
86    private static final String TAG = ConnOnActivityStartTest.class.getSimpleName();
87
88    private static final String ACTION_INSTALL_COMPLETE = "com.android.server.net.INSTALL_COMPLETE";
89
90    private static final String TEST_APP_URI =
91            "android.resource://com.android.frameworks.servicestests/raw/conntestapp";
92    private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp";
93    private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity";
94
95    private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH";
96
97    private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
98
99    private static final long BATTERY_OFF_TIMEOUT_MS = 2000; // 2 sec
100    private static final long BATTERY_OFF_CHECK_INTERVAL_MS = 200; // 0.2 sec
101
102    private static final long WAIT_FOR_INSTALL_TIMEOUT_MS = 2000; // 2 sec
103
104    private static final long NETWORK_CHECK_TIMEOUT_MS = 6000; // 6 sec
105
106    private static final long SCREEN_ON_DELAY_MS = 500; // 0.5 sec
107
108    private static final String NETWORK_STATUS_SEPARATOR = "\\|";
109
110    private static final int REPEAT_TEST_COUNT = 5;
111
112    private static Context mContext;
113    private static UiDevice mUiDevice;
114    private static int mTestPkgUid;
115    private static BatteryManager mBatteryManager;
116    private static ConnectivityManager mConnectivityManager;
117
118    @BeforeClass
119    public static void setUpOnce() throws Exception {
120        mContext = InstrumentationRegistry.getContext();
121        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
122
123        installAppAndAssertInstalled();
124        mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
125                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
126        mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0);
127
128        mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
129        mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
130                Context.CONNECTIVITY_SERVICE);
131    }
132
133    @AfterClass
134    public static void tearDownOnce() {
135        mContext.getPackageManager().deletePackage(TEST_PKG,
136                new IPackageDeleteObserver.Stub() {
137                    @Override
138                    public void packageDeleted(String packageName, int returnCode)
139                            throws RemoteException {
140                        Log.e(TAG, packageName + " deleted, returnCode: " + returnCode);
141                    }
142                }, 0);
143    }
144
145    @Test
146    public void testStartActivity_batterySaver() throws Exception {
147        if (!isNetworkAvailable()) {
148            fail("Device doesn't have network connectivity");
149        }
150        setBatterySaverMode(true);
151        try {
152            testConnOnActivityStart("testStartActivity_batterySaver");
153        } finally {
154            setBatterySaverMode(false);
155        }
156    }
157
158    @Test
159    public void testStartActivity_dataSaver() throws Exception {
160        if (!isNetworkAvailable()) {
161            fail("Device doesn't have network connectivity");
162        }
163        setDataSaverMode(true);
164        try {
165            testConnOnActivityStart("testStartActivity_dataSaver");
166        } finally {
167            setDataSaverMode(false);
168        }
169    }
170
171    @Test
172    public void testStartActivity_dozeMode() throws Exception {
173        if (!isNetworkAvailable()) {
174            fail("Device doesn't have network connectivity");
175        }
176        setDozeMode(true);
177        try {
178            testConnOnActivityStart("testStartActivity_dozeMode");
179        } finally {
180            setDozeMode(false);
181        }
182    }
183
184    @Test
185    public void testStartActivity_appStandby() throws Exception {
186        if (!isNetworkAvailable()) {
187            fail("Device doesn't have network connectivity");
188        }
189        try{
190            turnBatteryOff();
191            setAppIdle(true);
192            SystemClock.sleep(30000);
193            turnScreenOn();
194            startActivityAndCheckNetworkAccess();
195        } finally {
196            turnBatteryOn();
197            setAppIdle(false);
198        }
199    }
200
201    @Test
202    public void testStartActivity_backgroundRestrict() throws Exception {
203        if (!isNetworkAvailable()) {
204            fail("Device doesn't have network connectivity");
205        }
206        updateRestrictBackgroundBlacklist(true);
207        try {
208            testConnOnActivityStart("testStartActivity_backgroundRestrict");
209        } finally {
210            updateRestrictBackgroundBlacklist(false);
211        }
212    }
213
214    private void testConnOnActivityStart(String testName) throws Exception {
215        for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) {
216            try {
217                Log.d(TAG, testName + " Start #" + i);
218                turnScreenOn();
219                SystemClock.sleep(SCREEN_ON_DELAY_MS);
220                startActivityAndCheckNetworkAccess();
221                Log.d(TAG, testName + " end #" + i);
222            } finally {
223                finishActivity();
224            }
225        }
226    }
227
228    // TODO: Some of these methods are also used in CTS, so instead of duplicating code,
229    // create a static library which can be used by both servicestests and cts.
230    private void setBatterySaverMode(boolean enabled) throws Exception {
231        if (enabled) {
232            turnBatteryOff();
233            executeCommand("settings put global low_power 1");
234        } else {
235            executeCommand("settings put global low_power 0");
236            turnBatteryOn();
237        }
238        final String result = executeCommand("settings get global low_power");
239        assertEquals(enabled ? "1" : "0", result);
240    }
241
242    private void setDataSaverMode(boolean enabled) throws Exception {
243        executeCommand("cmd netpolicy set restrict-background " + enabled);
244        final String output = executeCommand("cmd netpolicy get restrict-background");
245        final String expectedSuffix = enabled ? "enabled" : "disabled";
246        assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
247                output.endsWith(expectedSuffix));
248    }
249
250    private void setDozeMode(boolean enabled) throws Exception {
251        if (enabled) {
252            turnBatteryOff();
253            turnScreenOff();
254            executeCommand("dumpsys deviceidle force-idle deep");
255        } else {
256            turnScreenOn();
257            turnBatteryOn();
258            executeCommand("dumpsys deviceidle unforce");
259        }
260        assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE",
261                5 /* maxTries */, 500 /* napTimeMs */);
262    }
263
264    private void setAppIdle(boolean enabled) throws Exception {
265        executeCommand("am set-inactive " + TEST_PKG + " " + enabled);
266        assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled,
267                10 /* maxTries */, 2000 /* napTimeMs */);
268    }
269
270    private void updateRestrictBackgroundBlacklist(boolean add) throws Exception {
271        if (add) {
272            executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid);
273        } else {
274            executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid);
275        }
276        assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add);
277    }
278
279    private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
280        final int maxTries = 5;
281        boolean actual = false;
282        final String expectedUid = Integer.toString(uid);
283        String uids = "";
284        for (int i = 1; i <= maxTries; i++) {
285            final String output = executeCommand("cmd netpolicy list " + list);
286            uids = output.split(":")[1];
287            for (String candidate : uids.split(" ")) {
288                actual = candidate.trim().equals(expectedUid);
289                if (expected == actual) {
290                    return;
291                }
292            }
293            Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
294                    + expected + ", got " + actual + "); sleeping 1s before polling again");
295            SystemClock.sleep(1000);
296        }
297        fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
298                + ". Full list: " + uids);
299    }
300
301    private void turnBatteryOff() throws Exception {
302        executeCommand("cmd battery unplug");
303        assertBatteryOff();
304    }
305
306    private void assertBatteryOff() throws Exception {
307        final long endTime = SystemClock.uptimeMillis() + BATTERY_OFF_TIMEOUT_MS;
308        while (mBatteryManager.isCharging() && SystemClock.uptimeMillis() < endTime) {
309            SystemClock.sleep(BATTERY_OFF_CHECK_INTERVAL_MS);
310        }
311        assertFalse("Power should be disconnected", mBatteryManager.isCharging());
312    }
313
314    private void turnBatteryOn() throws Exception {
315        executeCommand("cmd battery reset");
316    }
317
318    private void turnScreenOff() throws Exception {
319        executeCommand("input keyevent KEYCODE_SLEEP");
320    }
321
322    private void turnScreenOn() throws Exception {
323        executeCommand("input keyevent KEYCODE_WAKEUP");
324        executeCommand("wm dismiss-keyguard");
325    }
326
327    private String executeCommand(String cmd) throws IOException {
328        final String result = mUiDevice.executeShellCommand(cmd).trim();
329        Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
330        return result;
331    }
332
333    private void assertDelayedCommandResult(String cmd, String expectedResult,
334            int maxTries, int napTimeMs) throws IOException {
335        String result = "";
336        for (int i = 1; i <= maxTries; ++i) {
337            result = executeCommand(cmd);
338            if (expectedResult.equals(result)) {
339                return;
340            }
341            Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '"
342                    + expectedResult + "' on attempt #" + i
343                    + "; sleeping " + napTimeMs + "ms before trying again");
344            SystemClock.sleep(napTimeMs);
345        }
346        fail("Command '" + cmd + "' did not return '" + expectedResult + "' after "
347                + maxTries + " attempts. Last result: '" + result + "'");
348    }
349
350    private boolean isNetworkAvailable() throws Exception {
351        final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
352        return networkInfo != null && networkInfo.isConnected();
353    }
354
355    private void startActivityAndCheckNetworkAccess() throws Exception {
356        final CountDownLatch latch = new CountDownLatch(1);
357        final Intent launchIntent = new Intent().setComponent(
358                new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS));
359        final Bundle extras = new Bundle();
360        final String[] errors = new String[] {null};
361        extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() {
362            @Override
363            public void onNetworkStateChecked(String resultData) {
364                errors[0] = checkForAvailability(resultData);
365                latch.countDown();
366            }
367        });
368        launchIntent.putExtras(extras);
369        mContext.startActivity(launchIntent);
370        if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
371            if (!errors[0].isEmpty()) {
372                fail("Network not available for test app " + mTestPkgUid);
373            }
374        } else {
375            fail("Timed out waiting for network availability status from test app " + mTestPkgUid);
376        }
377    }
378
379    private void finishActivity() {
380        final Intent finishIntent = new Intent(ACTION_FINISH_ACTIVITY)
381                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
382        mContext.sendBroadcast(finishIntent);
383    }
384
385    private String checkForAvailability(String resultData) {
386        if (resultData == null) {
387            assertNotNull("Network status from app2 is null, Uid: " + mTestPkgUid, resultData);
388        }
389        // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
390        final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
391        assertEquals("Wrong network status: " + resultData + ", Uid: " + mTestPkgUid,
392                5, parts.length); // Sanity check
393        final NetworkInfo.State state = parts[0].equals("null")
394                ? null : NetworkInfo.State.valueOf(parts[0]);
395        final NetworkInfo.DetailedState detailedState = parts[1].equals("null")
396                ? null : NetworkInfo.DetailedState.valueOf(parts[1]);
397        final boolean connected = Boolean.valueOf(parts[2]);
398        final String connectionCheckDetails = parts[3];
399        final String networkInfo = parts[4];
400
401        final StringBuilder errors = new StringBuilder();
402        final NetworkInfo.State expectedState = NetworkInfo.State.CONNECTED;
403        final NetworkInfo.DetailedState expectedDetailedState = NetworkInfo.DetailedState.CONNECTED;
404
405        if (true != connected) {
406            errors.append(String.format("External site connection failed: expected %s, got %s\n",
407                    true, connected));
408        }
409        if (expectedState != state || expectedDetailedState != detailedState) {
410            errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
411                    expectedState, expectedDetailedState, state, detailedState));
412        }
413
414        if (errors.length() > 0) {
415            errors.append("\tnetworkInfo: " + networkInfo + "\n");
416            errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
417        }
418        return errors.toString();
419    }
420
421    private static void installAppAndAssertInstalled() throws Exception {
422        final CountDownLatch latch = new CountDownLatch(1);
423        final int[] result = {PackageInstaller.STATUS_SUCCESS};
424        final BroadcastReceiver installStatusReceiver = new BroadcastReceiver() {
425            @Override
426            public void onReceive(Context context, Intent intent) {
427                final String pkgName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
428                if (!TEST_PKG.equals(pkgName)) {
429                    return;
430                }
431                result[0] = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
432                        PackageInstaller.STATUS_FAILURE);
433                latch.countDown();
434            }
435        };
436        mContext.registerReceiver(installStatusReceiver, new IntentFilter(ACTION_INSTALL_COMPLETE));
437        try {
438            installApp();
439            if (latch.await(WAIT_FOR_INSTALL_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
440                if (result[0] != PackageInstaller.STATUS_SUCCESS) {
441                    fail("Couldn't install test app, result: "
442                            + valueToString(PackageInstaller.class, "STATUS_", result[0]));
443                }
444            } else {
445                fail("Timed out waiting for the test app to install");
446            }
447        } finally {
448            mContext.unregisterReceiver(installStatusReceiver);
449        }
450    }
451
452    private static void installApp() throws Exception {
453        final Uri packageUri = Uri.parse(TEST_APP_URI);
454        final InputStream in = mContext.getContentResolver().openInputStream(packageUri);
455
456        final PackageInstaller packageInstaller
457                = mContext.getPackageManager().getPackageInstaller();
458        final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
459                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
460        params.setAppPackageName(TEST_PKG);
461
462        final int sessionId = packageInstaller.createSession(params);
463        final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
464
465        OutputStream out = null;
466        try {
467            out = session.openWrite(TAG, 0, -1);
468            final byte[] buffer = new byte[65536];
469            int c;
470            while ((c = in.read(buffer)) != -1) {
471                out.write(buffer, 0, c);
472            }
473            session.fsync(out);
474        } finally {
475            IoUtils.closeQuietly(in);
476            IoUtils.closeQuietly(out);
477        }
478        session.commit(createIntentSender(mContext, sessionId));
479    }
480
481    private static IntentSender createIntentSender(Context context, int sessionId) {
482        PendingIntent pendingIntent = PendingIntent.getBroadcast(
483                context, sessionId, new Intent(ACTION_INSTALL_COMPLETE), 0);
484        return pendingIntent.getIntentSender();
485    }
486}