fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -0,0 +1,34 @@
package {
default_team: "trendy_team_android_settings_app",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "packages_apps_Settings_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["packages_apps_Settings_license"],
}
android_test {
name: "AnomalyTester",
certificate: "platform",
libs: ["android.test.runner"],
static_libs: [
"androidx.test.rules",
"mockito-target",
"androidx.test.uiautomator_uiautomator",
"truth",
],
srcs: ["**/*.java"],
optimize: {
enabled: false,
},
platform_apis: true,
instrumentation_for: "Settings",
}

View File

@@ -0,0 +1,54 @@
<?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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settings.anomaly.tester">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<application
android:allowBackup="false"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light.DarkActionBar">
<uses-library android:name="android.test.runner" />
<activity
android:name=".AnomalyActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.AnomalyService"
android:exported="false"/>
</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,59 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:padding="4dp"
android:textSize="16sp">
<TextView
android:id="@+id/displayText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="100000"/>
</ScrollView>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#444444"/>
<ScrollView
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="6"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<include layout="@layout/bluetooth_anomaly"/>
<include layout="@layout/wakelock_anomaly"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="6dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bluetooth Anomaly"
android:textSize="16sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/bluetooth_threshold"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="Threshold(ms)"
android:text="3000"
android:inputType="number"/>
<EditText
android:id="@+id/bluetooth_run_time"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="Run time(ms)"
android:text="6000"
android:inputType="number"/>
<Button
android:id="@+id/bluetooth_button"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="wrap_content"
android:text="START"
android:onClick="startBluetoothAnomaly"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="6dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Wakelock Anomaly"
android:textSize="16sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/wakelock_threshold"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="Threshold(ms)"
android:text="3000"
android:inputType="number"/>
<EditText
android:id="@+id/wakelock_run_time"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="Run time(ms)"
android:text="6000"
android:inputType="number"/>
<Button
android:id="@+id/wakelock_button"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="wrap_content"
android:text="START"
android:onClick="startWakelockAnomaly"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,19 @@
<?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.
-->
<resources>
<string name="app_name" translatable="false">AnomalyTester</string>
</resources>

View File

@@ -0,0 +1,133 @@
/*
* 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.
*/
package com.android.settings.anomaly.tester;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.android.settings.anomaly.tester.service.AnomalyService;
import com.android.settings.anomaly.tester.utils.AnomalyActions;
import com.android.settings.anomaly.tester.utils.AnomalyPolicyBuilder;
/**
* Main activity to control and start anomaly
*/
public class AnomalyActivity extends Activity {
private static final String TAG = AnomalyActivity.class.getSimpleName();
public static final String KEY_TARGET_BUTTON = "target_button";
private AnomalyResultReceiver mResultReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mResultReceiver = new AnomalyResultReceiver(new Handler());
}
public void startBluetoothAnomaly(View view) {
try {
// Enable anomaly detection and change the threshold
final String config = new AnomalyPolicyBuilder()
.addPolicy(AnomalyPolicyBuilder.KEY_ANOMALY_DETECTION_ENABLED, true)
.addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true)
.addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_THRESHOLD,
getValueFromEditText(R.id.bluetooth_threshold))
.build();
Settings.Global.putString(getContentResolver(),
Settings.Global.ANOMALY_DETECTION_CONSTANTS, config);
// Start the anomaly service
Intent intent = new Intent(this, AnomalyService.class);
intent.putExtra(AnomalyActions.KEY_ACTION, AnomalyActions.ACTION_BLE_SCAN_UNOPTIMIZED);
intent.putExtra(AnomalyActions.KEY_DURATION_MS,
getValueFromEditText(R.id.bluetooth_run_time));
intent.putExtra(AnomalyActions.KEY_RESULT_RECEIVER, mResultReceiver);
intent.putExtra(KEY_TARGET_BUTTON, view.getId());
startService(intent);
view.setEnabled(false);
} catch (NumberFormatException e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
}
public void startWakelockAnomaly(View view) {
try {
// Enable anomaly detection and change the threshold
final String config = new AnomalyPolicyBuilder()
.addPolicy(AnomalyPolicyBuilder.KEY_ANOMALY_DETECTION_ENABLED, true)
.addPolicy(AnomalyPolicyBuilder.KEY_WAKELOCK_DETECTION_ENABLED, true)
.addPolicy(AnomalyPolicyBuilder.KEY_WAKELOCK_THRESHOLD,
getValueFromEditText(R.id.wakelock_threshold))
.build();
Settings.Global.putString(getContentResolver(),
Settings.Global.ANOMALY_DETECTION_CONSTANTS,
config);
// Start the anomaly service
Intent intent = new Intent(this, AnomalyService.class);
intent.putExtra(AnomalyActions.KEY_ACTION, AnomalyActions.ACTION_WAKE_LOCK);
intent.putExtra(AnomalyActions.KEY_DURATION_MS,
getValueFromEditText(R.id.wakelock_run_time));
intent.putExtra(AnomalyActions.KEY_RESULT_RECEIVER, mResultReceiver);
intent.putExtra(KEY_TARGET_BUTTON, view.getId());
startService(intent);
view.setEnabled(false);
} catch (NumberFormatException e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
}
private long getValueFromEditText(final int id) throws NumberFormatException {
final EditText editText = findViewById(id);
if (editText != null) {
final long value = Long.parseLong(editText.getText().toString());
if (value > 0) {
return value;
}
}
throw new NumberFormatException("Number should be positive");
}
private class AnomalyResultReceiver extends ResultReceiver {
public AnomalyResultReceiver(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
final Button button = findViewById(resultData.getInt(KEY_TARGET_BUTTON));
if (button != null) {
button.setEnabled(true);
}
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
package com.android.settings.anomaly.tester.service;
import android.annotation.Nullable;
import android.app.IntentService;
import android.content.Intent;
import android.os.ResultReceiver;
import com.android.settings.anomaly.tester.utils.AnomalyActions;
/**
* Service to run the anomaly action
*/
public class AnomalyService extends IntentService {
private static final String TAG = AnomalyService.class.getSimpleName();
public AnomalyService() {
super(AnomalyService.class.getSimpleName());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
final String action = intent.getStringExtra(AnomalyActions.KEY_ACTION);
final long durationMs = intent.getLongExtra(AnomalyActions.KEY_DURATION_MS, 0);
final ResultReceiver resultReceiver = intent.getParcelableExtra(
AnomalyActions.KEY_RESULT_RECEIVER);
AnomalyActions.doAction(this, action, durationMs);
if (resultReceiver != null) {
resultReceiver.send(0 /* resultCode */, intent.getExtras());
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.
*/
package com.android.settings.anomaly.tester.utils;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.os.PowerManager;
import android.util.Log;
import java.util.List;
/**
* Actions to generate anomaly.
*/
public class AnomalyActions {
private static final String TAG = AnomalyActions.class.getSimpleName();
public static final String KEY_ACTION = "action";
public static final String KEY_DURATION_MS = "duration_ms";
public static final String KEY_RESULT_RECEIVER = "result_receiver";
public static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized";
public static final String ACTION_WAKE_LOCK = "action.wake_lock";
public static void doAction(Context ctx, String actionCode, long durationMs) {
if (actionCode == null) {
Log.e(TAG, "Intent was missing action.");
return;
}
switch (actionCode) {
case ACTION_BLE_SCAN_UNOPTIMIZED:
doUnoptimizedBleScan(ctx, durationMs);
break;
case ACTION_WAKE_LOCK:
doHoldWakelock(ctx, durationMs);
default:
Log.e(TAG, "Intent had invalid action");
}
}
private static void doUnoptimizedBleScan(Context ctx, long durationMs) {
ScanSettings scanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
// perform ble scanning
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled() ) {
Log.e(TAG, "Device does not support Bluetooth or Bluetooth not enabled");
return;
}
BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bleScanner == null) {
Log.e(TAG, "Cannot access BLE scanner");
return;
}
ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
Log.v(TAG, "called onScanResult");
}
@Override
public void onScanFailed(int errorCode) {
Log.v(TAG, "called onScanFailed");
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
Log.v(TAG, "called onBatchScanResults");
}
};
bleScanner.startScan(null, scanSettings, scanCallback);
try {
Thread.sleep(durationMs);
} catch (InterruptedException e) {
Log.e(TAG, "Thread couldn't sleep for " + durationMs, e);
}
bleScanner.stopScan(scanCallback);
}
private static void doHoldWakelock(Context ctx, long durationMs) {
PowerManager powerManager = ctx.getSystemService(PowerManager.class);
PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"AnomalyWakeLock");
wl.acquire();
try {
Thread.sleep(durationMs);
} catch (InterruptedException e) {
Log.e(TAG, "Thread couldn't sleep for " + durationMs, e);
}
wl.release();
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.
*/
package com.android.settings.anomaly.tester.utils;
import java.util.HashMap;
import java.util.Map;
/**
* Builder to build the anomaly policy string, used in {@link android.provider.Settings.Global}
*
* @see android.provider.Settings.Global#ANOMALY_DETECTION_CONSTANTS
*/
public class AnomalyPolicyBuilder {
public static final String KEY_ANOMALY_DETECTION_ENABLED = "anomaly_detection_enabled";
public static final String KEY_WAKELOCK_DETECTION_ENABLED = "wakelock_enabled";
public static final String KEY_WAKEUP_ALARM_DETECTION_ENABLED = "wakeup_alarm_enabled";
public static final String KEY_BLUETOOTH_SCAN_DETECTION_ENABLED = "bluetooth_scan_enabled";
public static final String KEY_WAKELOCK_THRESHOLD = "wakelock_threshold";
public static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold";
public static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold";
public static final String DELIM = ",";
private Map<String, String> mValues;
public AnomalyPolicyBuilder() {
mValues = new HashMap<>();
}
public AnomalyPolicyBuilder addPolicy(String key, String value) {
mValues.put(key, value);
return this;
}
public AnomalyPolicyBuilder addPolicy(String key, long value) {
mValues.put(key, Long.toString(value));
return this;
}
public AnomalyPolicyBuilder addPolicy(String key, boolean value) {
mValues.put(key, value ? "true" : "false");
return this;
}
public String build() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : mValues.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue() + DELIM);
}
if (sb.length() != 0) {
return sb.substring(0, sb.length() - 1);
} else {
return "";
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.
*/
package com.android.settings.anomaly.tests;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.text.format.DateUtils;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Functional test for bluetooth unoptimized scanning anomaly detector
*
* @see com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector
*/
@RunWith(AndroidJUnit4.class)
public class BluetoothAnomalyTest {
private static final String BATTERY_INTENT = "android.intent.action.POWER_USAGE_SUMMARY";
private static final String RES_BT_EDITTEXT =
"com.android.settings.anomaly.tester:id/bluetooth_run_time";
private static final String RES_BT_BUTTON =
"com.android.settings.anomaly.tester:id/bluetooth_button";
private static final long TIME_OUT = 3000;
private UiDevice mDevice;
@Before
public void setUp() {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
// setup environment
TestUtils.setUp(instrumentation);
// start anomaly-tester app
TestUtils.startAnomalyApp(context, mDevice);
}
@After
public void tearDown() {
TestUtils.tearDown(InstrumentationRegistry.getInstrumentation());
}
@Test
public void testBluetoothAnomaly_longScanningTime_reportAnomaly() throws InterruptedException {
// Set running time
final long durationMs = DateUtils.SECOND_IN_MILLIS * 15;
TestUtils.setEditTextWithValue(mDevice, RES_BT_EDITTEXT, durationMs);
// Click start button
TestUtils.clickButton(mDevice, RES_BT_BUTTON);
// Wait for its running
mDevice.pressHome();
TestUtils.wait(mDevice, durationMs);
// Check it in battery main page
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
assertWithMessage("Doesn't have bluetooth anomaly").that(
mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
TIME_OUT)).isNotNull();
}
@Test
public void testBluetoothAnomaly_shortScanningTime_notReport() throws InterruptedException {
// Set running time
final long durationMs = DateUtils.SECOND_IN_MILLIS;
TestUtils.setEditTextWithValue(mDevice, RES_BT_EDITTEXT, durationMs);
// Click start button
TestUtils.clickButton(mDevice, RES_BT_BUTTON);
// Wait for its running
mDevice.pressHome();
TestUtils.wait(mDevice, durationMs);
// Check it in battery main page
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
assertWithMessage("Shouldn't have bluetooth anomaly").that(
mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
TIME_OUT)).isNull();
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.
*/
package com.android.settings.anomaly.tests;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
public class TestUtils {
private static final String PACKAGE_NAME = "com.android.settings.anomaly.tester";
private static final long TIME_OUT = 3000;
/**
* This method set up the environment for anomaly test
*
* @param instrumentation to execute command
*/
public static void setUp(Instrumentation instrumentation) {
final UiAutomation uiAutomation = instrumentation.getUiAutomation();
// pretend unplug and screen off, also reset the battery stats
uiAutomation.executeShellCommand("dumpsys battery unplug");
uiAutomation.executeShellCommand("dumpsys batterystats enable pretend-screen-off");
uiAutomation.executeShellCommand("dumpsys batterystats --reset");
}
/**
* This method cleans up all the commands in {@link #setUp(Instrumentation)}
*
* @param instrumentation to execute command
*/
public static void tearDown(Instrumentation instrumentation) {
final UiAutomation uiAutomation = instrumentation.getUiAutomation();
// reset unplug and screen-off
uiAutomation.executeShellCommand("dumpsys battery reset");
uiAutomation.executeShellCommand("dumpsys batterystats disable pretend-screen-off");
}
public static void startAnomalyApp(Context context, UiDevice uiDevice) {
final Intent intent = context.getPackageManager().getLaunchIntentForPackage(PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
uiDevice.wait(Until.hasObject(By.pkg(PACKAGE_NAME).depth(0)), TIME_OUT);
}
/**
* Find {@link android.widget.EditText} with {@code res} and set its {@code value}
*/
public static void setEditTextWithValue(UiDevice uiDevice, String res, long value) {
final UiObject2 editText = uiDevice.findObject(By.res(res));
assertWithMessage("Cannot find editText with res: " + res).that(editText).isNotNull();
editText.setText(String.valueOf(value));
}
/**
* Find {@link android.widget.Button} with {@code res} and click it
*/
public static void clickButton(UiDevice uiDevice, String res) {
final UiObject2 button = uiDevice.findObject(By.res(res));
assertWithMessage("Cannot find button with res: " + res).that(button).isNotNull();
button.click();
}
/**
* Make {@link UiDevice} wait for {@code timeMs}
*
* @see Thread#sleep(long)
*/
public static void wait(UiDevice uiDevice, long timeMs) throws InterruptedException {
uiDevice.waitForIdle();
Thread.sleep(timeMs);
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.
*/
package com.android.settings.anomaly.tests;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.text.format.DateUtils;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Functional test for bluetooth unoptimized scanning anomaly detector
*
* @see com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector
*/
@RunWith(AndroidJUnit4.class)
public class WakelockAnomalyTest {
private static final String BATTERY_INTENT = "android.intent.action.POWER_USAGE_SUMMARY";
private static final String RES_WAKELOCK_EDITTEXT =
"com.android.settings.anomaly.tester:id/wakelock_run_time";
private static final String RES_WAKELOCK_BUTTON =
"com.android.settings.anomaly.tester:id/wakelock_button";
private static final long TIME_OUT = 3000;
private UiDevice mDevice;
@Before
public void setUp() {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
// setup environment
TestUtils.setUp(instrumentation);
// start anomaly-tester app
TestUtils.startAnomalyApp(context, mDevice);
}
@After
public void tearDown() {
TestUtils.tearDown(InstrumentationRegistry.getInstrumentation());
}
@Test
public void testWakelockAnomaly_longTimeWhileRunning_report() throws InterruptedException {
// Set running time
final long durationMs = DateUtils.SECOND_IN_MILLIS * 15;
TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
// Click start button
TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
// Wait for its running
mDevice.pressHome();
// Sleeping time less than running time, so the app still holding wakelock when we check
TestUtils.wait(mDevice, durationMs - TIME_OUT);
// Check it in battery main page
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
assertWithMessage("Doesn't have wakelock anomaly").that(
mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
TIME_OUT)).isNotNull();
}
@Test
public void testWakelockAnomaly_shortTime_notReport() throws InterruptedException {
// Set running time
final long durationMs = DateUtils.SECOND_IN_MILLIS;
TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
// Click start button
TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
// Wait for its running
mDevice.pressHome();
TestUtils.wait(mDevice, durationMs);
// Check it in battery main page
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
assertWithMessage("Shouldn't have wakelock anomaly").that(
mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
TIME_OUT)).isNull();
}
@Test
public void testWakelockAnomaly_longTimeWhileNotRunning_notReport()
throws InterruptedException {
// Set running time
final long durationMs = DateUtils.SECOND_IN_MILLIS * 10;
TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
// Click start button
TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
// Wait for its running
mDevice.pressHome();
// Wait more time for releasing the wakelock
TestUtils.wait(mDevice, durationMs + TIME_OUT);
// Check it in battery main page
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
assertWithMessage("Shouldn't have wakelock anomaly").that(
mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
TIME_OUT)).isNull();
}
}