Initial commit for Settings component test

Test: Tested on local device
Bug: 178765084
Change-Id: I3b8f36daa31b0a44e788fe4c84f94d48653ee037
This commit is contained in:
Jiun-Yang Hsu
2020-12-31 14:18:01 +08:00
parent 1e1c813354
commit 7a3635e7f4
12 changed files with 673 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
//############################################################
// Settings Component test target. #
//############################################################
android_test {
name: "SettingsComponentTests",
certificate: "platform",
privileged: true,
srcs: [
"src/**/*.java",
],
static_libs: [
"truth-prebuilt",
"androidx.test.core",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
],
test_suites: ["device-tests"],
instrumentation_for: "Settings",
}

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.settings.tests.component">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application/>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.settings"
android:label="Settings Test Cases">
</instrumentation>
</manifest>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration description="Runs Settings Test Cases.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="SettingsComponentTests.apk" />
</target_preparer>
<option name="test-tag" value="SettingsComponentTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.settings.tests.component" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
</test>
</configuration>

View File

@@ -0,0 +1,3 @@
# People who can approve changes for submission
jyhsu@google.com
syaoranx@google.com

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterysaver;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.widget.Button;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.settings.R;
import com.android.settings.Settings.BatterySaverSettingsActivity;
import com.android.settings.testutils.AdbUtils;
import com.android.settings.testutils.UiUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BatterySaverButtonPreferenceControllerComponentTest {
private static final String TAG =
BatterySaverButtonPreferenceControllerComponentTest.class.getSimpleName();
private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private PowerManager mManager =
(PowerManager) mInstrumentation.getTargetContext().getSystemService(
Context.POWER_SERVICE);
@Rule
public ActivityScenarioRule<BatterySaverSettingsActivity> rule = new ActivityScenarioRule<>(
new Intent(
Settings.ACTION_BATTERY_SAVER_SETTINGS).setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK));
@Before
public void setUp() throws Exception {
mInstrumentation.getUiAutomation().executeShellCommand("dumpsys battery unplug");
mInstrumentation.getUiAutomation().executeShellCommand("settings get global low_power 0");
}
@Test
public void test_check_battery_saver_button() throws Exception {
ActivityScenario scenario = rule.getScenario();
scenario.onActivity(activity -> {
final Button button = activity.findViewById(R.id.state_on_button);
UiUtils.waitUntilCondition(3000, () -> button.isEnabled());
button.callOnClick();
checkPowerSaverMode(true);
Button offButton = activity.findViewById(R.id.state_off_button);
offButton.callOnClick();
checkPowerSaverMode(false);
});
//Ideally, we should be able to also create BatteryTipPreferenceController and verify that
//it is showing battery saver on. Unfortunately, that part of code is tightly coupled with
//UI, and it's not possible to retrieve that string without reaching very deep into the
//codes and become very tightly coupled with any future changes. That is not what component
//tests should do, so either we'll need to do this through UI with another ActivityScenario,
//or the code needs to be refactored to be less coupled with UI.
}
@Test
public void test_battery_saver_button_changes_when_framework_setting_change() throws Exception {
ActivityScenario scenario = rule.getScenario();
scenario.onActivity(activity -> {
Button buttonOn = activity.findViewById(R.id.state_on_button);
Button buttonOff = activity.findViewById(R.id.state_off_button);
assertThat(buttonOn.isVisibleToUser()).isEqualTo(true);
assertThat(buttonOff.isVisibleToUser()).isEqualTo(false);
});
mManager.setPowerSaveModeEnabled(true);
scenario.recreate();
scenario.onActivity(activity -> {
Button buttonOn = activity.findViewById(R.id.state_on_button);
Button buttonOff = activity.findViewById(R.id.state_off_button);
assertThat(buttonOn.isVisibleToUser()).isEqualTo(false);
assertThat(buttonOff.isVisibleToUser()).isEqualTo(true);
});
mManager.setPowerSaveModeEnabled(false);
scenario.recreate();
scenario.onActivity(activity -> {
Button buttonOn = activity.findViewById(R.id.state_on_button);
Button buttonOff = activity.findViewById(R.id.state_off_button);
assertThat(buttonOn.isVisibleToUser()).isEqualTo(true);
assertThat(buttonOff.isVisibleToUser()).isEqualTo(false);
});
}
@After
public void tearDown() {
mInstrumentation.getUiAutomation().executeShellCommand("settings get global low_power 0");
mInstrumentation.getUiAutomation().executeShellCommand("dumpsys battery reset");
}
private void checkPowerSaverMode(boolean enabled) {
//Check through adb. Note that this needs to be done first, or a wait and poll needs to be
//done to the manager.isPowerSaveMode(), because calling isPowerSaveMode immediately after
//setting it does not return true. It takes a while for isPowerSaveMode() to return the
//up-to-date value.
try {
assertThat(
AdbUtils.checkStringInAdbCommandOutput(TAG, "settings get global low_power",
null, enabled ? "1" : "0", 1000)).isTrue();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
assert_().fail();
}
//Check through manager
assertThat(mManager.isPowerSaveMode() == enabled).isTrue();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.privacy;
import static com.google.common.truth.Truth.assertThat;
import android.app.Instrumentation;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.settings.testutils.AdbUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class EnabledContentCapturePreferenceControllerComponentTest {
private Instrumentation mInstrumentation;
private static final String TAG =
EnabledContentCapturePreferenceControllerComponentTest.class.getSimpleName();
@Before
public void setUp() {
if (null == mInstrumentation) {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
}
@Test
public void test_uncheck_content_capture() throws Exception {
content_capture_checkbox_test_helper(false);
}
@Test
public void test_check_content_capture() throws Exception {
content_capture_checkbox_test_helper(true);
}
private void content_capture_checkbox_test_helper(boolean check) throws Exception {
EnableContentCapturePreferenceController enableContentCapturePreferenceController =
new EnableContentCapturePreferenceController(
ApplicationProvider.getApplicationContext(),
"Test_key");
enableContentCapturePreferenceController.setChecked(check);
//Check through adb command
assertThat(AdbUtils.checkStringInAdbCommandOutput(TAG, "dumpsys content_capture",
"Users disabled by Settings: ", check ? "{}" : "{0=true}", 1000)).isTrue();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.testutils;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class AdbUtils {
public static boolean checkStringInAdbCommandOutput(String logTag, String command,
String prefix, String target, int timeoutInMillis) throws Exception {
long start = System.nanoTime();
//Sometimes the change do no reflect in adn output immediately, so need a wait and poll here
while (System.nanoTime() - start < (timeoutInMillis * 1000000)) {
try (ParcelFileDescriptor.AutoCloseInputStream in =
new ParcelFileDescriptor.AutoCloseInputStream(
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.executeShellCommand(command))) {
try (BufferedReader br =
new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8))) {
Optional<String> resultOptional = br.lines().filter(line -> {
Log.d(logTag, line);
return TextUtils.isEmpty(prefix) || line.contains(prefix);
}).findFirst();
String result = resultOptional.get();
if (result.contains(target)) {
return true;
} else {
Thread.sleep(100);
}
}
} catch (Exception e) {
throw e;
}
}
return false;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.testutils;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class CommonUtils {
private static final String TAG = CommonUtils.class.getSimpleName();
public static void takeScreenshot(Activity activity) {
long now = System.currentTimeMillis();
try {
// image naming and path to include sd card appending name you choose for file
String mPath =
Environment.getExternalStorageDirectory().toString() + "/" + now + ".jpg";
Log.d(TAG, "screenshot path is " + mPath);
// create bitmap screen capture
View v1 = activity.getWindow().getDecorView().getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
File imageFile = new File(mPath);
FileOutputStream outputStream = new FileOutputStream(imageFile);
int quality = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
outputStream.flush();
outputStream.close();
} catch (Throwable e) {
// Several error may come out with file handling or DOM
e.printStackTrace();
}
}
public static boolean connectToURL(URL url) {
HttpURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.connect();
if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) {
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while (null != (line = reader.readLine())) {
response.append(line);
}
return true;
}
} catch (Exception e) {
Log.d(TAG, e.getMessage());
return false;
} finally {
if (null != connection) {
connection.disconnect();
}
}
return false;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.testutils;
public class Constants {
public static final long ACTIVITY_LAUNCH_WAIT_TIMEOUT = 5000;
public static final long VIEW_APPEAR_WAIT_MEDIUM_TIMEOUT = 5000;
public static final long WIFI_CONNECT_WAIT_TIMEOUT = 15000;
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.testutils;
import android.app.Activity;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Supplier;
public class UiUtils {
public static void waitUntilCondition(long timeoutInMillis, Supplier<Boolean> condition) {
long start = System.nanoTime();
while (System.nanoTime() - start < (timeoutInMillis * 1000000)) {
try {
//Eat NPE from condition because there's a concurrency issue that when calling
//findViewById when the view hierarchy is still rendering, it sometimes encounter
//null views that may exist few milliseconds before, and causes a NPE.
if (condition.get()) {
return;
}
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}
public static boolean waitForActivitiesInStage(long timeoutInMillis, Stage stage) {
final Collection<Activity> activities = new ArrayList<>();
waitUntilCondition(Constants.ACTIVITY_LAUNCH_WAIT_TIMEOUT, () -> {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activities.addAll(
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED)));
return activities.size() > 0;
});
return activities.size() > 0;
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.wifi;
import static com.google.common.truth.Truth.assertThat;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import com.android.settings.R;
import com.android.settings.Settings.NetworkProviderSettingsActivity;
import com.android.settings.fuelgauge.batterysaver.BatterySaverButtonPreferenceControllerComponentTest;
import com.android.settings.network.NetworkProviderSettings;
import com.android.settings.testutils.CommonUtils;
import com.android.settings.testutils.Constants;
import com.android.settings.testutils.UiUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.URL;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@MediumTest
/*
This test is just for demonstration purpose. For component tests, this approach is not recommended.
The reason why it is written this way is because the current Settings app wifi codes have tight
coupling with UI, so it's not easy to drive from API without binding the test deeply with the code.
*/
public class WifiSettings2ActivityTest {
private static final String TAG =
BatterySaverButtonPreferenceControllerComponentTest.class.getSimpleName();
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
@Test
public void test_connect_to_wifi() throws Exception {
//For some reason the ActivityScenario gets null activity here
mInstrumentation.getTargetContext().startActivity(
new Intent(Settings.ACTION_WIFI_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
UiUtils.waitForActivitiesInStage(Constants.ACTIVITY_LAUNCH_WAIT_TIMEOUT, Stage.RESUMED);
final NetworkProviderSettings[] settings = new NetworkProviderSettings[1];
mInstrumentation.runOnMainSync(() -> {
NetworkProviderSettingsActivity activity = (NetworkProviderSettingsActivity)
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED).iterator().next();
settings[0] =
(NetworkProviderSettings) activity.getSupportFragmentManager().getFragments()
.get(0);
});
//For some reason this view does not appear immediately after the fragment is resumed.
View root = settings[0].getView();
UiUtils.waitUntilCondition(Constants.VIEW_APPEAR_WAIT_MEDIUM_TIMEOUT,
() -> root.findViewById(R.id.settings_button) != null);
View view = root.findViewById(R.id.settings_button);
view.callOnClick();
UiUtils.waitForActivitiesInStage(Constants.ACTIVITY_LAUNCH_WAIT_TIMEOUT, Stage.RESUMED);
Button[] button = new Button[1];
mInstrumentation.runOnMainSync(() -> {
FragmentActivity activity =
(FragmentActivity) ActivityLifecycleMonitorRegistry.getInstance()
.getActivitiesInStage(Stage.RESUMED).iterator().next();
List<Fragment> fragments = activity.getSupportFragmentManager().getFragments();
Log.d(TAG, "fragment class is " + fragments.get(0).getClass());
button[0] = fragments.get(0).getView().findViewById(R.id.button3);
});
//HttpURLConnection needs to run outside of main thread, so running it in the test thread
final URL url = new URL("https://www.google.net/");
//Make sure the connectivity is available before disconnecting from wifi
assertThat(CommonUtils.connectToURL(url)).isTrue();
//Disconnect from wifi
button[0].callOnClick();
//Make sure the Internet connectivity is gone
assertThat(CommonUtils.connectToURL(url)).isFalse();
//Connect to wifi
button[0].callOnClick();
ConnectivityManager manager =
(ConnectivityManager) mInstrumentation.getTargetContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
//For some reason I can't find a way to tell the time that the internet connectivity is
//actually available with the new, non-deprecated ways, so I still need to use this.
UiUtils.waitUntilCondition(Constants.WIFI_CONNECT_WAIT_TIMEOUT,
() -> manager.getActiveNetworkInfo().isConnected());
//Make sure the connectivity is back again
assertThat(CommonUtils.connectToURL(url)).isTrue();
}
}

View File

@@ -1,4 +1,4 @@
//############################################################
//############################################################
// Build SettingsRoboTestStub.apk which includes test-only resources.#
//############################################################