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