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