fix: 首次提交
This commit is contained in:
73
SettingsLib/tests/integ/Android.bp
Normal file
73
SettingsLib/tests/integ/Android.bp
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2015 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 {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
android_test {
|
||||
name: "SettingsLibTests",
|
||||
use_resource_processor: true,
|
||||
defaults: [
|
||||
"SettingsLibDefaults",
|
||||
"framework-wifi-test-defaults",
|
||||
],
|
||||
|
||||
certificate: "platform",
|
||||
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
],
|
||||
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"telephony-common",
|
||||
"android.test.base",
|
||||
"android.test.mock",
|
||||
"truth",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
test_suites: ["device-tests"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.test.core",
|
||||
"androidx.test.rules",
|
||||
"androidx.test.espresso.core",
|
||||
"androidx.test.ext.junit",
|
||||
"flag-junit",
|
||||
"kotlinx_coroutines_test",
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"platform-test-annotations",
|
||||
"truth",
|
||||
"SettingsLibDeviceStateRotationLock",
|
||||
"SettingsLibSettingsSpinner",
|
||||
"SettingsLibUsageProgressBarPreference",
|
||||
"settingslib_media_flags_lib",
|
||||
],
|
||||
|
||||
jni_libs: [
|
||||
"libdexmakerjvmtiagent",
|
||||
"libmultiplejvmtiagentsinterferenceagent",
|
||||
"libstaticjvmtiagent",
|
||||
],
|
||||
dxflags: ["--multi-dex"],
|
||||
manifest: "AndroidManifest.xml",
|
||||
}
|
||||
47
SettingsLib/tests/integ/AndroidManifest.xml
Normal file
47
SettingsLib/tests/integ/AndroidManifest.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 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.settingslib.tests.integ">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
<uses-permission android:name="android.permission.MANAGE_USERS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
|
||||
<uses-permission android:name="android.permission.SET_TIME_ZONE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
|
||||
<application android:debuggable="true" android:testOnly="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.android.settingslib.test"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.settingslib.tests.integ"
|
||||
android:label="Tests for SettingsLib">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
29
SettingsLib/tests/integ/AndroidTest.xml
Normal file
29
SettingsLib/tests/integ/AndroidTest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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 Tests for SettingsLib.">
|
||||
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
|
||||
<option name="test-file-name" value="SettingsLibTests.apk" />
|
||||
<option name="install-arg" value="-t" />
|
||||
</target_preparer>
|
||||
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
<option name="test-tag" value="SettingsLibTests" />
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="com.android.settingslib.tests.integ" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
<option name="hidden-api-checks" value="false"/>
|
||||
</test>
|
||||
</configuration>
|
||||
20
SettingsLib/tests/integ/res/xml/file_paths.xml
Normal file
20
SettingsLib/tests/integ/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 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.
|
||||
-->
|
||||
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Offer access to files under Context.getCacheDir() -->
|
||||
<cache-path name="my_cache" />
|
||||
</paths>
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.settingslib;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class BaseTest extends AndroidTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
// Mockito stuff.
|
||||
System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
|
||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Flags;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class ApplicationsStateTest {
|
||||
private static final int APP_ENTRY_ID = 1;
|
||||
private ApplicationsState.AppEntry mEntry;
|
||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mEntry = new ApplicationsState.AppEntry(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
mock(ApplicationInfo.class),
|
||||
APP_ENTRY_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGamesFilterAcceptsGameDeprecated() {
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameFilterAcceptsCategorizedGame() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameFilterAcceptsCategorizedGameAndDeprecatedIsGame() {
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGamesFilterRejectsNotGame() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_GAMES.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudioFilterAcceptsCategorizedAudio() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_AUDIO.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudiosFilterRejectsNotAudio() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_AUDIO.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudiosFilterRejectsDefaultCategory() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_AUDIO.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAppsRejectsAudio() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAppsRejectsGame() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAppsRejectsImageApp() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAppsAcceptsDefaultCategory() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPhotosFilterAcceptsFilter() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPhotosFilterRejectsNotPhotos() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPhotosFilterRejectsDefaultCategory() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_PHOTOS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppsExceptGamesFilterRejectsGame() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppsExceptGamesFilterAcceptsImage() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppsExceptGamesFilterAcceptsVideo() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppsExceptGamesFilterAcceptsAudio() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadAndLauncherAndInstantAcceptsCorrectApps() {
|
||||
// should include instant apps
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = false;
|
||||
when(mEntry.info.isInstantApp()).thenReturn(true);
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(mEntry))
|
||||
.isTrue();
|
||||
|
||||
// should included updated system apps
|
||||
when(mEntry.info.isInstantApp()).thenReturn(false);
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(mEntry))
|
||||
.isTrue();
|
||||
|
||||
// should not include system apps other than the home app
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = false;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(mEntry))
|
||||
.isFalse();
|
||||
|
||||
// should include the home app
|
||||
mEntry.isHomeApp = true;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(mEntry))
|
||||
.isTrue();
|
||||
|
||||
// should include any System app with a launcher entry
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = true;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(mEntry))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDownloadAndLauncherAcceptsCorrectApps() {
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = false;
|
||||
|
||||
// should included updated system apps
|
||||
when(mEntry.info.isInstantApp()).thenReturn(false);
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(mEntry))
|
||||
.isTrue();
|
||||
|
||||
// should not include system apps other than the home app
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = false;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(mEntry))
|
||||
.isFalse();
|
||||
|
||||
// should include the home app
|
||||
mEntry.isHomeApp = true;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(mEntry))
|
||||
.isTrue();
|
||||
|
||||
// should include any System app with a launcher entry
|
||||
mEntry.isHomeApp = false;
|
||||
mEntry.hasLauncherEntry = true;
|
||||
assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(mEntry))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAppsRejectsLegacyGame() {
|
||||
mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_OTHER_APPS.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstantFilterAcceptsInstantApp() {
|
||||
when(mEntry.info.isInstantApp()).thenReturn(true);
|
||||
assertThat(ApplicationsState.FILTER_INSTANT.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstantFilterRejectsNonInstantApp() {
|
||||
when(mEntry.info.isInstantApp()).thenReturn(false);
|
||||
assertThat(ApplicationsState.FILTER_INSTANT.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledFilterRejectsInstantApp() {
|
||||
mEntry.info.enabled = true;
|
||||
assertThat(ApplicationsState.FILTER_ALL_ENABLED.filterApp(mEntry)).isTrue();
|
||||
when(mEntry.info.isInstantApp()).thenReturn(true);
|
||||
assertThat(ApplicationsState.FILTER_ALL_ENABLED.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterWithDomainUrls() {
|
||||
mEntry.info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
|
||||
// should included updated system apps
|
||||
when(mEntry.info.isInstantApp()).thenReturn(false);
|
||||
assertThat(ApplicationsState.FILTER_WITH_DOMAIN_URLS.filterApp(mEntry))
|
||||
.isTrue();
|
||||
mEntry.info.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
|
||||
assertThat(ApplicationsState.FILTER_WITH_DOMAIN_URLS.filterApp(mEntry))
|
||||
.isFalse();
|
||||
mEntry.info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
|
||||
when(mEntry.info.isInstantApp()).thenReturn(true);
|
||||
assertThat(ApplicationsState.FILTER_WITH_DOMAIN_URLS.filterApp(mEntry))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabledFilterRejectsInstantApp() {
|
||||
mEntry.info.enabled = false;
|
||||
assertThat(ApplicationsState.FILTER_DISABLED.filterApp(mEntry)).isTrue();
|
||||
when(mEntry.info.isInstantApp()).thenReturn(true);
|
||||
assertThat(ApplicationsState.FILTER_DISABLED.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVideoFilterAcceptsCategorizedVideo() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVideosFilterRejectsNotVideo() {
|
||||
mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
|
||||
|
||||
assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
|
||||
mEntry.showInPersonalTab = true;
|
||||
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
|
||||
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
|
||||
assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
|
||||
|
||||
mEntry.showInPersonalTab = false;
|
||||
mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_MANAGED;
|
||||
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
|
||||
assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateProfileFilterDisplaysCorrectApps() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
|
||||
|
||||
mEntry.showInPersonalTab = true;
|
||||
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
|
||||
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
|
||||
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
|
||||
|
||||
mEntry.showInPersonalTab = false;
|
||||
mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
|
||||
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
|
||||
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
|
||||
|
||||
mEntry.showInPersonalTab = false;
|
||||
mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
|
||||
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
|
||||
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.usage.StorageStats;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class StorageStatsSourceTest {
|
||||
@Test
|
||||
public void AppStorageStatsImpl_totalCorrectly() {
|
||||
StorageStats storageStats = new StorageStats();
|
||||
storageStats.cacheBytes = 1;
|
||||
storageStats.codeBytes = 10;
|
||||
storageStats.dataBytes = 100;
|
||||
StorageStatsSource.AppStorageStatsImpl stats = new StorageStatsSource.AppStorageStatsImpl(
|
||||
storageStats);
|
||||
|
||||
// Note that this does not double add the cache (111).
|
||||
assertThat(stats.getTotalBytes()).isEqualTo(110);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* Test that verifies that BluetoothEventManager can receive broadcasts for non-current
|
||||
* users for all bluetooth events.
|
||||
*
|
||||
* <p>Creation and deletion of users takes a long time, so marking this as a LargeTest.
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BluetoothEventManagerIntegTest {
|
||||
private static final int LATCH_TIMEOUT = 4;
|
||||
|
||||
private Context mContext;
|
||||
private UserManager mUserManager;
|
||||
private BluetoothEventManager mBluetoothEventManager;
|
||||
|
||||
private UserInfo mOtherUser;
|
||||
private final Intent mTestIntent = new Intent("Test intent");
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mUserManager = UserManager.get(mContext);
|
||||
|
||||
mBluetoothEventManager = new BluetoothEventManager(
|
||||
mock(LocalBluetoothAdapter.class), mock(CachedBluetoothDeviceManager.class),
|
||||
mContext, /* handler= */ null, UserHandle.ALL);
|
||||
|
||||
// Create and start another user in the background.
|
||||
mOtherUser = mUserManager.createUser("TestUser", /* flags= */ 0);
|
||||
try {
|
||||
ActivityManager.getService().startUserInBackground(mOtherUser.id);
|
||||
} catch (RemoteException e) {
|
||||
fail("Count't create an additional user.");
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mOtherUser != null) {
|
||||
mUserManager.removeUser(mOtherUser.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that MultiUserAwareBluetoothEventManager's adapter receiver handles events coming from
|
||||
* users other than current user.
|
||||
*/
|
||||
@Test
|
||||
public void registerAdapterReceiver_ifIntentFromAnotherUser_broadcastIsReceived()
|
||||
throws Exception {
|
||||
// Create a latch to listen for the intent.
|
||||
final CountDownLatch broadcastLatch = new CountDownLatch(1);
|
||||
|
||||
// Register adapter receiver.
|
||||
mBluetoothEventManager.addHandler(mTestIntent.getAction(),
|
||||
(context, intent, device) -> broadcastLatch.countDown());
|
||||
mBluetoothEventManager.registerAdapterIntentReceiver();
|
||||
|
||||
// Send broadcast from another user.
|
||||
mContext.sendBroadcastAsUser(mTestIntent, mOtherUser.getUserHandle());
|
||||
|
||||
// Wait to receive it.
|
||||
assertTrue(broadcastLatch.await(LATCH_TIMEOUT, SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that MultiUserAwareBluetoothEventManager's profile receiver handles events coming from
|
||||
* users other than current user.
|
||||
*/
|
||||
@Test
|
||||
public void registerProfileReceiver_ifIntentFromAnotherUser_broadcastIsReceived()
|
||||
throws Exception {
|
||||
// Create a latch to listen for the intent.
|
||||
final CountDownLatch broadcastLatch = new CountDownLatch(1);
|
||||
|
||||
// Register profile receiver.
|
||||
mBluetoothEventManager.addProfileHandler(mTestIntent.getAction(),
|
||||
(context, intent, device) -> broadcastLatch.countDown());
|
||||
mBluetoothEventManager.registerProfileIntentReceiver();
|
||||
|
||||
// Send broadcast from another user.
|
||||
mContext.sendBroadcastAsUser(mTestIntent, mOtherUser.getUserHandle());
|
||||
|
||||
// Wait to receive it.
|
||||
assertTrue(broadcastLatch.await(LATCH_TIMEOUT, SECONDS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.devicestate;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
|
||||
|
||||
import com.google.common.truth.Expect;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DeviceStateRotationLockSettingsManagerTest {
|
||||
|
||||
@Rule public Expect mExpect = Expect.create();
|
||||
|
||||
@Mock private Context mMockContext;
|
||||
@Mock private Resources mMockResources;
|
||||
|
||||
private DeviceStateRotationLockSettingsManager mManager;
|
||||
private int mNumSettingsChanges = 0;
|
||||
private final ContentObserver mContentObserver = new ContentObserver(null) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
mNumSettingsChanges++;
|
||||
}
|
||||
};
|
||||
private final FakeSecureSettings mFakeSecureSettings = new FakeSecureSettings();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
|
||||
when(mMockContext.getResources()).thenReturn(mMockResources);
|
||||
when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
|
||||
when(mMockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
|
||||
.thenReturn(new String[]{"0:1", "1:0:2", "2:2"});
|
||||
when(mMockResources.getIntArray(R.array.config_foldedDeviceStates))
|
||||
.thenReturn(new int[]{0});
|
||||
when(mMockResources.getIntArray(R.array.config_halfFoldedDeviceStates))
|
||||
.thenReturn(new int[]{1});
|
||||
when(mMockResources.getIntArray(R.array.config_openDeviceStates))
|
||||
.thenReturn(new int[]{2});
|
||||
mFakeSecureSettings.registerContentObserver(
|
||||
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
|
||||
/* notifyForDescendents= */ false, //NOTYPO
|
||||
mContentObserver,
|
||||
UserHandle.USER_CURRENT);
|
||||
mManager = new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialization_settingsAreChangedOnce() {
|
||||
assertThat(mNumSettingsChanges).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSetting_multipleTimes_sameValue_settingsAreChangedOnlyOnce() {
|
||||
mNumSettingsChanges = 0;
|
||||
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
|
||||
assertThat(mNumSettingsChanges).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSetting_multipleTimes_differentValues_settingsAreChangedMultipleTimes() {
|
||||
mNumSettingsChanges = 0;
|
||||
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ false);
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
|
||||
assertThat(mNumSettingsChanges).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSetting_twiceWithSameValue_persistedValueDifferent_persistsAgain() {
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
// This persists a different setting than what was set above. It simulates the persisted
|
||||
// setting being changed from a different process.
|
||||
persistSettings("0:1:1:2:2:2");
|
||||
mNumSettingsChanges = 0;
|
||||
|
||||
// Updating again with the same value as in the first line of the test should persist the
|
||||
// setting, as it is different to what is actually persisted.
|
||||
mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
|
||||
|
||||
assertThat(mNumSettingsChanges).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
|
||||
when(mMockResources.getStringArray(
|
||||
R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
|
||||
new String[]{"2:1", "1:0:1", "0:2"});
|
||||
|
||||
List<SettableDeviceState> settableDeviceStates =
|
||||
DeviceStateRotationLockSettingsManager.getInstance(
|
||||
mMockContext).getSettableDeviceStates();
|
||||
|
||||
assertThat(settableDeviceStates).containsExactly(
|
||||
new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
|
||||
new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ false),
|
||||
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ true)
|
||||
).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistedInvalidIgnoredState_returnsDefaults() {
|
||||
when(mMockResources.getStringArray(
|
||||
R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
|
||||
new String[]{"0:1", "1:0:2", "2:2"});
|
||||
// Here 2 has IGNORED, and in the defaults 1 has IGNORED.
|
||||
persistSettings("0:2:2:0:1:2");
|
||||
DeviceStateRotationLockSettingsManager manager =
|
||||
new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
|
||||
|
||||
mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1);
|
||||
mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2);
|
||||
mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistedValidValues_returnsPersistedValues() {
|
||||
when(mMockResources.getStringArray(
|
||||
R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
|
||||
new String[]{"0:1", "1:0:2", "2:2"});
|
||||
persistSettings("0:2:1:0:2:1");
|
||||
DeviceStateRotationLockSettingsManager manager =
|
||||
new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
|
||||
|
||||
mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2);
|
||||
mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1);
|
||||
mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1);
|
||||
}
|
||||
|
||||
private void persistSettings(String value) {
|
||||
mFakeSecureSettings.putStringForUser(
|
||||
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
|
||||
value,
|
||||
UserHandle.USER_CURRENT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.devicestate;
|
||||
|
||||
import android.database.ContentObserver;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Fake implementation of {@link SecureSettings} that stores everything in memory. */
|
||||
class FakeSecureSettings implements SecureSettings {
|
||||
|
||||
private final Map<SettingsKey, String> mValues = new HashMap<>();
|
||||
private final Multimap<SettingsKey, ContentObserver> mContentObservers = HashMultimap.create();
|
||||
|
||||
@Override
|
||||
public void putStringForUser(String name, String value, int userHandle) {
|
||||
SettingsKey settingsKey = new SettingsKey(userHandle, name);
|
||||
mValues.put(settingsKey, value);
|
||||
for (ContentObserver observer : mContentObservers.get(settingsKey)) {
|
||||
observer.onChange(/* selfChange= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStringForUser(String name, int userHandle) {
|
||||
return mValues.getOrDefault(new SettingsKey(userHandle, name), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerContentObserver(String name, boolean notifyForDescendants,
|
||||
ContentObserver settingsObserver, int userHandle) {
|
||||
mContentObservers.put(new SettingsKey(userHandle, name), settingsObserver);
|
||||
}
|
||||
|
||||
private static class SettingsKey extends Pair<Integer, String> {
|
||||
|
||||
SettingsKey(Integer userHandle, String settingName) {
|
||||
super(userHandle, settingName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Default reviewers for this and subdirectories.
|
||||
alexflo@google.com
|
||||
chrisgollner@google.com
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.settingslib.devicestate
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED
|
||||
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
|
||||
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY
|
||||
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED
|
||||
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNKNOWN
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.internal.R
|
||||
import com.google.common.truth.Expect
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
private const val DEVICE_STATE_UNKNOWN = 0
|
||||
private const val DEVICE_STATE_CLOSED = 1
|
||||
private const val DEVICE_STATE_HALF_FOLDED = 2
|
||||
private const val DEVICE_STATE_OPEN = 3
|
||||
private const val DEVICE_STATE_REAR_DISPLAY = 4
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PosturesHelperTest {
|
||||
|
||||
@get:Rule val expect: Expect = Expect.create()
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
|
||||
@Mock private lateinit var resources: Resources
|
||||
|
||||
private lateinit var posturesHelper: PosturesHelper
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
whenever(context.resources).thenReturn(resources)
|
||||
whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
|
||||
.thenReturn(intArrayOf(DEVICE_STATE_CLOSED))
|
||||
whenever(resources.getIntArray(R.array.config_halfFoldedDeviceStates))
|
||||
.thenReturn(intArrayOf(DEVICE_STATE_HALF_FOLDED))
|
||||
whenever(resources.getIntArray(R.array.config_openDeviceStates))
|
||||
.thenReturn(intArrayOf(DEVICE_STATE_OPEN))
|
||||
whenever(resources.getIntArray(R.array.config_rearDisplayDeviceStates))
|
||||
.thenReturn(intArrayOf(DEVICE_STATE_REAR_DISPLAY))
|
||||
|
||||
posturesHelper = PosturesHelper(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceStateToPosture_mapsCorrectly() {
|
||||
expect
|
||||
.that(posturesHelper.deviceStateToPosture(DEVICE_STATE_CLOSED))
|
||||
.isEqualTo(DEVICE_STATE_ROTATION_KEY_FOLDED)
|
||||
expect
|
||||
.that(posturesHelper.deviceStateToPosture(DEVICE_STATE_HALF_FOLDED))
|
||||
.isEqualTo(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED)
|
||||
expect
|
||||
.that(posturesHelper.deviceStateToPosture(DEVICE_STATE_OPEN))
|
||||
.isEqualTo(DEVICE_STATE_ROTATION_KEY_UNFOLDED)
|
||||
expect
|
||||
.that(posturesHelper.deviceStateToPosture(DEVICE_STATE_REAR_DISPLAY))
|
||||
.isEqualTo(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY)
|
||||
expect
|
||||
.that(posturesHelper.deviceStateToPosture(DEVICE_STATE_UNKNOWN))
|
||||
.isEqualTo(DEVICE_STATE_ROTATION_KEY_UNKNOWN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun postureToDeviceState_mapsCorrectly() {
|
||||
expect
|
||||
.that(posturesHelper.postureToDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED))
|
||||
.isEqualTo(DEVICE_STATE_CLOSED)
|
||||
expect
|
||||
.that(posturesHelper.postureToDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED))
|
||||
.isEqualTo(DEVICE_STATE_HALF_FOLDED)
|
||||
expect
|
||||
.that(posturesHelper.postureToDeviceState(DEVICE_STATE_ROTATION_KEY_UNFOLDED))
|
||||
.isEqualTo(DEVICE_STATE_OPEN)
|
||||
expect
|
||||
.that(posturesHelper.postureToDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY))
|
||||
.isEqualTo(DEVICE_STATE_REAR_DISPLAY)
|
||||
expect.that(posturesHelper.postureToDeviceState(DEVICE_STATE_ROTATION_KEY_UNKNOWN)).isNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.settingslib.drawable;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UserIconDrawableTest {
|
||||
|
||||
private UserIconDrawable mDrawable;
|
||||
|
||||
@Test
|
||||
public void getConstantState_shouldNotBeNull() {
|
||||
final Bitmap b = BitmapFactory.decodeResource(
|
||||
InstrumentationRegistry.getTargetContext().getResources(),
|
||||
com.android.internal.R.drawable.ic_mode_edit);
|
||||
mDrawable = new UserIconDrawable(100 /* size */).setIcon(b).bake();
|
||||
assertThat(mDrawable.getConstantState()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTintList_shouldBeApplied() {
|
||||
@ColorInt final int targetColor = Color.BLUE;
|
||||
final PorterDuff.Mode mode = Mode.SRC_OVER;
|
||||
|
||||
final Bitmap b = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
|
||||
UserIconDrawable drawable = new UserIconDrawable().setIcon(b);
|
||||
drawable.setBounds(0, 0, 100, 100);
|
||||
|
||||
int[][] stateSet = new int[][] { {} };
|
||||
int[] colors = new int[] { targetColor };
|
||||
drawable.setTintList(new ColorStateList(stateSet, colors));
|
||||
drawable.setTintMode(mode);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.draw(canvas);
|
||||
|
||||
assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.BLUE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.android.settingslib.graph;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyFloat;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BatteryMeterDrawableBaseTest {
|
||||
private Context mContext;
|
||||
private Resources mResources;
|
||||
private BatteryMeterDrawableBase mBatteryDrawable;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mResources = mContext.getResources();
|
||||
mBatteryDrawable = new BatteryMeterDrawableBase(mContext, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntrinsicSize() {
|
||||
assertThat(mBatteryDrawable.getIntrinsicWidth()).
|
||||
isEqualTo(mResources.getDimensionPixelSize(R.dimen.battery_width));
|
||||
assertThat(mBatteryDrawable.getIntrinsicHeight()).
|
||||
isEqualTo(mResources.getDimensionPixelSize(R.dimen.battery_height));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDrawNothingBeforeOnBatteryLevelChanged() {
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
mBatteryDrawable.draw(canvas);
|
||||
verify(canvas, never()).drawPath(any(), any());
|
||||
verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDrawingForTypicalValues() {
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
final int levels[] = { 0, 1, 5, 10, 25, 50, 75, 90, 95, 99, 100 };
|
||||
final boolean bools[] = { false, true };
|
||||
for (int l : levels) {
|
||||
for (boolean charging : bools) {
|
||||
for (boolean saver : bools) {
|
||||
for (boolean percent : bools) {
|
||||
mBatteryDrawable.setBatteryLevel(l);
|
||||
mBatteryDrawable.setPowerSave(saver);
|
||||
mBatteryDrawable.setCharging(charging);
|
||||
mBatteryDrawable.setShowPercent(percent);
|
||||
mBatteryDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPadding_returnsCorrectValues() {
|
||||
// different pads on each side to differentiate
|
||||
final int left = 1;
|
||||
final int top = 2;
|
||||
final int right = 3;
|
||||
final int bottom = 4;
|
||||
|
||||
final Rect expected = new Rect(left, top, right, bottom);
|
||||
final Rect padding = new Rect();
|
||||
|
||||
mBatteryDrawable.setPadding(left, top, right, bottom);
|
||||
|
||||
assertThat(mBatteryDrawable.getPadding(padding)).isEqualTo(true);
|
||||
assertThat(padding).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPadding_falseIfUnsetOrZero() {
|
||||
final Rect padding = new Rect();
|
||||
assertThat(mBatteryDrawable.getPadding(padding)).isEqualTo(false);
|
||||
assertThat(isRectZero(padding)).isEqualTo(true);
|
||||
|
||||
mBatteryDrawable.setPadding(0, 0, 0, 0);
|
||||
assertThat(mBatteryDrawable.getPadding(padding)).isEqualTo(false);
|
||||
assertThat(isRectZero(padding)).isEqualTo(true);
|
||||
}
|
||||
|
||||
private boolean isRectZero(Rect r) {
|
||||
return r.left == 0 && r.top == 0 && r.right == 0 && r.bottom == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.settingslib.inputmethod;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntBiFunction;
|
||||
|
||||
/**
|
||||
* Utility class to assert {@link Comparable} objects.
|
||||
*/
|
||||
final class ComparableUtils {
|
||||
|
||||
private ComparableUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether specified items have ascending ordering.
|
||||
*
|
||||
* @param items objects to be checked Comparable contracts.
|
||||
* @param compareTo function to compare two objects as {@link Comparable#compareTo(Object)}.
|
||||
* @param name function to extract name of an object.
|
||||
* @param <T> type that implements {@link Comparable}.
|
||||
*/
|
||||
static <T> void assertAscendingOrdering(final List<T> items,
|
||||
final ToIntBiFunction<T, T> compareTo, final Function<T, String> name) {
|
||||
for (int i = 1; i < items.size(); i++) {
|
||||
final T x = items.get(i - 1);
|
||||
final T y = items.get(i);
|
||||
assertTrue(name.apply(x) + " is less than " + name.apply(y),
|
||||
compareTo.applyAsInt(x, y) < 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether specified items have the same ordering.
|
||||
*
|
||||
* @param items objects to be checked equality.
|
||||
* @param compareTo function to compare two objects as {@link Comparable#compareTo(Object)}.
|
||||
* @param name function to extract name of an object.
|
||||
* @param <T> type that implements {@link Comparable}.
|
||||
*/
|
||||
static <T> void assertSameOrdering(final Collection<T> items,
|
||||
final ToIntBiFunction<T, T> compareTo, final Function<T, String> name) {
|
||||
for (final T x : items) {
|
||||
for (final T y : items) {
|
||||
assertTrue(name.apply(x) + " is equal to " + name.apply(y),
|
||||
compareTo.applyAsInt(x, y) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a {@link Comparable} type complies with Comparable contracts.
|
||||
* <ul>
|
||||
* <li>Ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.</li>
|
||||
* <li>Ensure that the relation is transitive:
|
||||
* (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.</li>
|
||||
* <li>Ensure that x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)),
|
||||
* for all z.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param items objects to be checked Comparable contracts.
|
||||
* @param compareTo function to compare two objects as {@link Comparable#compareTo(Object)}.
|
||||
* @param name function to extract name of an object.
|
||||
* @param <T> type that implements {@link Comparable}.
|
||||
*/
|
||||
static <T> void assertComparableContracts(final Collection<T> items,
|
||||
final ToIntBiFunction<T, T> compareTo, final Function<T, String> name) {
|
||||
for (final T x : items) {
|
||||
final String nameX = name.apply(x);
|
||||
assertTrue("Reflective: " + nameX + " is equal to itself",
|
||||
compareTo.applyAsInt(x, x) == 0);
|
||||
for (final T y : items) {
|
||||
final String nameY = name.apply(y);
|
||||
assertEquals("Asymmetric: " + nameX + " and " + nameY,
|
||||
Integer.signum(compareTo.applyAsInt(x, y)),
|
||||
-Integer.signum(compareTo.applyAsInt(y, x)));
|
||||
for (final T z : items) {
|
||||
final String nameZ = name.apply(z);
|
||||
if (compareTo.applyAsInt(x, y) > 0 && compareTo.applyAsInt(y, z) > 0) {
|
||||
assertTrue("Transitive: " + nameX + " is greater than " + nameY
|
||||
+ " and " + nameY + " is greater than " + nameZ
|
||||
+ " then " + nameX + " is greater than " + nameZ,
|
||||
compareTo.applyAsInt(x, z) > 0);
|
||||
}
|
||||
if (compareTo.applyAsInt(x, y) == 0) {
|
||||
assertEquals("Transitive: " + nameX + " and " + nameY + " is same "
|
||||
+ " then both return the same result for " + nameZ,
|
||||
Integer.signum(compareTo.applyAsInt(x, z)),
|
||||
Integer.signum(compareTo.applyAsInt(y, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.settingslib.inputmethod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class InputMethodPreferenceTest {
|
||||
|
||||
private static final Collator COLLATOR = Collator.getInstance(Locale.US);
|
||||
|
||||
@Test
|
||||
public void testComparableOrdering() throws Exception {
|
||||
final List<InputMethodPreference> itemsInAscendingOrder = Arrays.asList(
|
||||
createPreference("", true, "no_title-system"),
|
||||
createPreference("E", true, "E-system"),
|
||||
createPreference("Z", true, "Z-system"),
|
||||
createPreference("", false, "no_title-non_system"),
|
||||
createPreference("E", false, "E-non_system"),
|
||||
createPreference("Z", false, "Z-non_system")
|
||||
);
|
||||
ComparableUtils.assertAscendingOrdering(
|
||||
itemsInAscendingOrder,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
x -> x.getInputMethodInfo().getServiceName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparableEquality() {
|
||||
final List<InputMethodPreference> itemsInSameOrder1 = Arrays.asList(
|
||||
createPreference("", true, "no_title-system-1"),
|
||||
createPreference("", true, "no_title-system-2")
|
||||
);
|
||||
ComparableUtils.assertSameOrdering(
|
||||
itemsInSameOrder1,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
x -> x.getInputMethodInfo().getServiceName());
|
||||
|
||||
final List<InputMethodPreference> itemsInSameOrder2 = Arrays.asList(
|
||||
createPreference("A", false, "A-non_system-1"),
|
||||
createPreference("A", false, "A-non_system-2")
|
||||
);
|
||||
ComparableUtils.assertSameOrdering(
|
||||
itemsInSameOrder2,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
x -> x.getInputMethodInfo().getServiceName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparableContracts() {
|
||||
final List<InputMethodPreference> items = Arrays.asList(
|
||||
// itemsInAscendingOrder.
|
||||
createPreference("", true, "no_title-system"),
|
||||
createPreference("E", true, "E-system"),
|
||||
createPreference("Z", true, "Z-system"),
|
||||
createPreference("", false, "no_title-non_system"),
|
||||
createPreference("E", false, "E-non_system"),
|
||||
createPreference("Z", false, "Z-non_system"),
|
||||
// itemsInSameOrder1.
|
||||
createPreference("", true, "no_title-system-1"),
|
||||
createPreference("", true, "no_title-system-2"),
|
||||
// itemsInSameOrder2.
|
||||
createPreference("A", false, "A-non_system-1"),
|
||||
createPreference("A", false, "A-non_system-2")
|
||||
);
|
||||
|
||||
ComparableUtils.assertComparableContracts(
|
||||
items,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
x -> x.getInputMethodInfo().getServiceName());
|
||||
}
|
||||
|
||||
private static InputMethodPreference createPreference(
|
||||
final CharSequence title,
|
||||
final boolean systemIme,
|
||||
final String name) {
|
||||
return new InputMethodPreference(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
createInputMethodInfo(systemIme, name),
|
||||
title,
|
||||
true /* isAllowedByOrganization */,
|
||||
p -> {} /* onSavePreferenceListener */,
|
||||
UserHandle.myUserId());
|
||||
}
|
||||
|
||||
private static InputMethodInfo createInputMethodInfo(
|
||||
final boolean systemIme, final String name) {
|
||||
final Context targetContext = InstrumentationRegistry.getTargetContext();
|
||||
final Locale systemLocale = targetContext
|
||||
.getResources()
|
||||
.getConfiguration()
|
||||
.getLocales()
|
||||
.get(0);
|
||||
final InputMethodSubtype systemLocaleSubtype =
|
||||
new InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
.setIsAsciiCapable(true)
|
||||
.setSubtypeMode("keyboard")
|
||||
.setSubtypeLocale(systemLocale.getLanguage())
|
||||
.build();
|
||||
|
||||
final ResolveInfo resolveInfo = new ResolveInfo();
|
||||
resolveInfo.serviceInfo = new ServiceInfo();
|
||||
resolveInfo.serviceInfo.packageName = "com.android.ime";
|
||||
resolveInfo.serviceInfo.name = name;
|
||||
resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo();
|
||||
resolveInfo.serviceInfo.applicationInfo.enabled = true;
|
||||
if (systemIme) {
|
||||
resolveInfo.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
} else {
|
||||
resolveInfo.serviceInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
|
||||
}
|
||||
return new InputMethodInfo(
|
||||
resolveInfo,
|
||||
false /* isAuxIme */,
|
||||
"SettingsActivity",
|
||||
Collections.singletonList(systemLocaleSubtype),
|
||||
0 /* isDefaultResId */,
|
||||
true /* forceDefault */);
|
||||
}
|
||||
}
|
||||
@@ -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.settingslib.inputmethod;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class InputMethodSubtypePreferenceTest {
|
||||
|
||||
private static final List<InputMethodSubtypePreference> ITEMS_IN_ASCENDING = Arrays.asList(
|
||||
// Subtypes that has the same locale of the system's.
|
||||
createPreference("", "en_US", Locale.US),
|
||||
createPreference("E", "en_US", Locale.US),
|
||||
createPreference("Z", "en_US", Locale.US),
|
||||
// Subtypes that has the same language of the system's.
|
||||
createPreference("", "en", Locale.US),
|
||||
createPreference("E", "en", Locale.US),
|
||||
createPreference("Z", "en", Locale.US),
|
||||
// Subtypes that has different language than the system's.
|
||||
createPreference("", "ja", Locale.US),
|
||||
createPreference("A", "hi_IN", Locale.US),
|
||||
createPreference("B", "", Locale.US),
|
||||
createPreference("E", "ja", Locale.US),
|
||||
createPreference("Z", "ja", Locale.US)
|
||||
);
|
||||
private static final List<InputMethodSubtypePreference> SAME_ORDER_ITEMS = Arrays.asList(
|
||||
// Subtypes that has different language than the system's.
|
||||
createPreference("A", "ja_JP", Locale.US),
|
||||
createPreference("A", "hi_IN", Locale.US),
|
||||
// Subtypes that has an empty subtype locale string.
|
||||
createPreference("A", "", Locale.US)
|
||||
);
|
||||
private static final Collator COLLATOR = Collator.getInstance(Locale.US);
|
||||
|
||||
@Test
|
||||
public void testComparableOrdering() throws Exception {
|
||||
ComparableUtils.assertAscendingOrdering(
|
||||
ITEMS_IN_ASCENDING,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
InputMethodSubtypePreference::getKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparableEquality() {
|
||||
ComparableUtils.assertSameOrdering(
|
||||
SAME_ORDER_ITEMS,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
InputMethodSubtypePreference::getKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparableContracts() {
|
||||
final Collection<InputMethodSubtypePreference> items = new ArrayList<>();
|
||||
items.addAll(ITEMS_IN_ASCENDING);
|
||||
items.addAll(SAME_ORDER_ITEMS);
|
||||
items.add(createPreference("", "", Locale.US));
|
||||
items.add(createPreference("A", "en", Locale.US));
|
||||
items.add(createPreference("A", "en_US", Locale.US));
|
||||
items.add(createPreference("E", "hi_IN", Locale.US));
|
||||
items.add(createPreference("E", "en", Locale.US));
|
||||
items.add(createPreference("Z", "en_US", Locale.US));
|
||||
|
||||
ComparableUtils.assertComparableContracts(
|
||||
items,
|
||||
(x, y) -> x.compareTo(y, COLLATOR),
|
||||
InputMethodSubtypePreference::getKey);
|
||||
}
|
||||
|
||||
private static InputMethodSubtypePreference createPreference(
|
||||
final String subtypeName,
|
||||
final String subtypeLocaleString,
|
||||
final Locale systemLocale) {
|
||||
final String key = subtypeName + "-" + subtypeLocaleString + "-" + systemLocale;
|
||||
final String subtypeLanguageTag = subtypeLocaleString.replace('_', '-');
|
||||
final Locale subtypeLocale = TextUtils.isEmpty(subtypeLanguageTag)
|
||||
? null : Locale.forLanguageTag(subtypeLanguageTag);
|
||||
return new InputMethodSubtypePreference(
|
||||
InstrumentationRegistry.getTargetContext(),
|
||||
key,
|
||||
subtypeName,
|
||||
subtypeLocale,
|
||||
systemLocale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.settingslib.media;
|
||||
|
||||
import static com.android.settingslib.media.flags.Flags.FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.UiAutomation;
|
||||
import android.content.Context;
|
||||
import android.platform.test.annotations.RequiresFlagsDisabled;
|
||||
import android.platform.test.annotations.RequiresFlagsEnabled;
|
||||
import android.platform.test.flag.junit.CheckFlagsRule;
|
||||
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class InfoMediaManagerIntegTest {
|
||||
|
||||
private static final String FAKE_PACKAGE = "FAKE_PACKAGE";
|
||||
|
||||
private Context mContext;
|
||||
private UiAutomation mUiAutomation;
|
||||
|
||||
@Rule
|
||||
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
|
||||
mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mUiAutomation.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
|
||||
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
|
||||
InfoMediaManager manager =
|
||||
InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
|
||||
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
|
||||
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
|
||||
InfoMediaManager manager =
|
||||
InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
|
||||
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
|
||||
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
|
||||
InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
|
||||
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
|
||||
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
|
||||
InfoMediaManager manager =
|
||||
InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
|
||||
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.statusbar.notification.data.repository
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings.Global
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.statusbar.notification.data.model.ZenMode
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class NotificationsSoundPolicyRepositoryTest {
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
@Mock private lateinit var notificationManager: NotificationManager
|
||||
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
|
||||
private lateinit var underTest: NotificationsSoundPolicyRepository
|
||||
|
||||
private val testScope: TestScope = TestScope()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
underTest =
|
||||
NotificationsSoundPolicyRepositoryImpl(
|
||||
context,
|
||||
notificationManager,
|
||||
testScope.backgroundScope,
|
||||
testScope.testScheduler,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun policyChanges_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
val values = mutableListOf<NotificationManager.Policy?>()
|
||||
`when`(notificationManager.notificationPolicy).thenReturn(testPolicy1)
|
||||
underTest.notificationPolicy.onEach { values.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
`when`(notificationManager.notificationPolicy).thenReturn(testPolicy2)
|
||||
triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
|
||||
runCurrent()
|
||||
|
||||
assertThat(values)
|
||||
.containsExactlyElementsIn(listOf(null, testPolicy1, testPolicy2))
|
||||
.inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun zenModeChanges_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
val values = mutableListOf<ZenMode?>()
|
||||
`when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_OFF)
|
||||
underTest.zenMode.onEach { values.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
`when`(notificationManager.zenMode).thenReturn(Global.ZEN_MODE_ALARMS)
|
||||
triggerIntent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
|
||||
runCurrent()
|
||||
|
||||
assertThat(values)
|
||||
.containsExactlyElementsIn(
|
||||
listOf(null, ZenMode(Global.ZEN_MODE_OFF), ZenMode(Global.ZEN_MODE_ALARMS))
|
||||
)
|
||||
.inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerIntent(action: String) {
|
||||
verify(context).registerReceiver(receiverCaptor.capture(), any())
|
||||
receiverCaptor.value.onReceive(context, Intent(action))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val testPolicy1 =
|
||||
NotificationManager.Policy(
|
||||
/* priorityCategories = */ 1,
|
||||
/* priorityCallSenders =*/ 1,
|
||||
/* priorityMessageSenders = */ 1,
|
||||
)
|
||||
val testPolicy2 =
|
||||
NotificationManager.Policy(
|
||||
/* priorityCategories = */ 2,
|
||||
/* priorityCallSenders =*/ 2,
|
||||
/* priorityMessageSenders = */ 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.users;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.settingslib.BaseTest;
|
||||
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for AppCopyHelper.
|
||||
*/
|
||||
@SmallTest
|
||||
public class AppCopyingHelperTest extends BaseTest {
|
||||
private @Mock Context mContext;
|
||||
private @Mock PackageManager mPm;
|
||||
private @Mock IPackageManager mIpm;
|
||||
|
||||
private final UserHandle mTestUser = UserHandle.of(1111);
|
||||
private AppCopyHelper mHelper;
|
||||
|
||||
private final ArrayList<ApplicationInfo> mCurrUserInstalledAppInfos = new ArrayList<>();
|
||||
private final ArrayList<ApplicationInfo> mTestUserInstalledAppInfos = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mHelper = new AppCopyHelper(new TestInjector());
|
||||
}
|
||||
|
||||
public void testFetchAndMergeApps() throws Exception {
|
||||
// Apps on the current user.
|
||||
final String[] sysInapplicables = new String[] {"sys.no0, sys.no1"};
|
||||
final String[] sysLaunchables = new String[] {"sys1", "sys2", "sys3"};
|
||||
final String[] sysWidgets = new String[] {"sys1", "sys4"};
|
||||
final String[] downloadeds = new String[] {"app1", "app2"};
|
||||
|
||||
addInapplicableSystemApps(sysInapplicables);
|
||||
addSystemAppsForIntent(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER),
|
||||
sysLaunchables);
|
||||
addSystemAppsForIntent(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
|
||||
sysWidgets);
|
||||
addDownloadedApps(downloadeds);
|
||||
when(mPm.getInstalledApplications(anyInt())).thenReturn(mCurrUserInstalledAppInfos);
|
||||
|
||||
// Apps on the test user.
|
||||
final String[] testUserApps =
|
||||
new String[]{"sys.no0", "sys2", "sys4", "app2", "sys999", "app999"};
|
||||
addAppsToTestUser(testUserApps);
|
||||
when(mPm.getInstalledApplicationsAsUser(anyInt(), eq(mTestUser.getIdentifier())))
|
||||
.thenReturn(mTestUserInstalledAppInfos);
|
||||
|
||||
mHelper.fetchAndMergeApps();
|
||||
|
||||
final ArraySet<String> notExpectedInVisibleApps = new ArraySet<>();
|
||||
Collections.addAll(notExpectedInVisibleApps, sysInapplicables);
|
||||
Collections.addAll(notExpectedInVisibleApps, testUserApps);
|
||||
|
||||
final ArraySet<String> expectedInVisibleApps = new ArraySet<>();
|
||||
Collections.addAll(expectedInVisibleApps, sysLaunchables);
|
||||
Collections.addAll(expectedInVisibleApps, sysWidgets);
|
||||
Collections.addAll(expectedInVisibleApps, downloadeds);
|
||||
expectedInVisibleApps.removeAll(notExpectedInVisibleApps);
|
||||
|
||||
for (AppCopyHelper.SelectableAppInfo info : mHelper.getVisibleApps()) {
|
||||
if (expectedInVisibleApps.contains(info.packageName)) {
|
||||
expectedInVisibleApps.remove(info.packageName);
|
||||
} else if (notExpectedInVisibleApps.contains(info.packageName)) {
|
||||
fail("Package: " + info.packageName + " should not be included in visibleApps");
|
||||
} else {
|
||||
fail("Unknown package: " + info.packageName);
|
||||
}
|
||||
}
|
||||
assertEquals("Some expected apps are not included in visibleApps: " + expectedInVisibleApps,
|
||||
0, expectedInVisibleApps.size());
|
||||
}
|
||||
|
||||
public void testInstallSelectedApps() throws Exception {
|
||||
final int testUserId = mTestUser.getIdentifier();
|
||||
|
||||
mHelper.setPackageSelected("app1", true); // Ultimately true
|
||||
mHelper.setPackageSelected("app2", true); // Ultimately false
|
||||
mHelper.setPackageSelected("app3", true); // Ultimately true
|
||||
mHelper.setPackageSelected("app4", true); // Ultimately true
|
||||
|
||||
mHelper.setPackageSelected("app2", false);
|
||||
mHelper.setPackageSelected("app1", false);
|
||||
mHelper.setPackageSelected("app1", true);
|
||||
|
||||
|
||||
// app3 is installed but hidden
|
||||
ApplicationInfo info = new ApplicationInfo();
|
||||
info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
|
||||
info.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
when(mIpm.getApplicationInfo(eq("app3"), anyLong(), eq(testUserId)))
|
||||
.thenReturn(info);
|
||||
|
||||
info = new ApplicationInfo();
|
||||
when(mIpm.getApplicationInfo(eq("app4"), anyLong(), eq(testUserId)))
|
||||
.thenReturn(info);
|
||||
|
||||
mHelper.installSelectedApps();
|
||||
|
||||
verify(mIpm, times(1)).installExistingPackageAsUser(
|
||||
"app1", testUserId,
|
||||
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
|
||||
PackageManager.INSTALL_REASON_UNKNOWN, null);
|
||||
verify(mIpm, times(0)).installExistingPackageAsUser(eq(
|
||||
"app2"), eq(testUserId),
|
||||
anyInt(), anyInt(), any());
|
||||
verify(mIpm, times(0)).installExistingPackageAsUser(eq(
|
||||
"app3"), eq(testUserId),
|
||||
anyInt(), anyInt(), any());
|
||||
verify(mIpm, times(1)).installExistingPackageAsUser(
|
||||
"app4", testUserId,
|
||||
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
|
||||
PackageManager.INSTALL_REASON_UNKNOWN, null);
|
||||
|
||||
verify(mIpm, times(0)).setApplicationHiddenSettingAsUser(
|
||||
eq("app1"), anyBoolean(), eq(testUserId));
|
||||
verify(mIpm, times(0)).setApplicationHiddenSettingAsUser(
|
||||
eq("app2"), anyBoolean(), eq(testUserId));
|
||||
verify(mIpm, times(1)).setApplicationHiddenSettingAsUser(
|
||||
eq("app3"), eq(false), eq(testUserId));
|
||||
verify(mIpm, times(0)).setApplicationHiddenSettingAsUser(
|
||||
eq("app4"), anyBoolean(), eq(testUserId));
|
||||
}
|
||||
|
||||
private void addSystemAppsForIntent(Intent intent, String... packages) throws Exception {
|
||||
final List<ResolveInfo> resolveInfos = new ArrayList<>();
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
resolveInfos.add(ri);
|
||||
addInstalledApp(ri, false);
|
||||
}
|
||||
when(mPm.queryIntentActivities(argThat(new IntentMatcher(intent)), anyInt()))
|
||||
.thenReturn(resolveInfos);
|
||||
}
|
||||
|
||||
private void addInapplicableSystemApps(String... packages) throws Exception {
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
addInstalledApp(ri, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDownloadedApps(String... packages) throws Exception {
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfo(pkg);
|
||||
addInstalledApp(ri, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAppsToTestUser(String... packages) throws Exception {
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfo(pkg);
|
||||
addInstalledApp(ri, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInstalledApp(ResolveInfo ri, boolean testUser)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
final String pkgName = ri.activityInfo.packageName;
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.applicationInfo = ri.activityInfo.applicationInfo;
|
||||
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
if (testUser) {
|
||||
mTestUserInstalledAppInfos.add(packageInfo.applicationInfo);
|
||||
} else {
|
||||
mCurrUserInstalledAppInfos.add(packageInfo.applicationInfo);
|
||||
}
|
||||
when(mPm.getPackageInfo(eq(pkgName), anyInt())).thenReturn(packageInfo);
|
||||
}
|
||||
|
||||
private ResolveInfo createResolveInfoForSystemApp(String packageName) {
|
||||
final ResolveInfo ri = createResolveInfo(packageName);
|
||||
ri.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
ri.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
return ri;
|
||||
}
|
||||
|
||||
private ResolveInfo createResolveInfo(String packageName) {
|
||||
final ResolveInfo ri = new ResolveInfo();
|
||||
final ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.packageName = packageName;
|
||||
final ActivityInfo activityInfo = new ActivityInfo();
|
||||
activityInfo.applicationInfo = applicationInfo;
|
||||
activityInfo.packageName = packageName;
|
||||
activityInfo.name = "";
|
||||
ri.activityInfo = activityInfo;
|
||||
final ServiceInfo serviceInfo = new ServiceInfo();
|
||||
serviceInfo.applicationInfo = applicationInfo;
|
||||
serviceInfo.packageName = packageName;
|
||||
serviceInfo.name = "";
|
||||
ri.serviceInfo = serviceInfo;
|
||||
return ri;
|
||||
}
|
||||
|
||||
private static class IntentMatcher implements ArgumentMatcher<Intent> {
|
||||
private final Intent mIntent;
|
||||
|
||||
IntentMatcher(Intent intent) {
|
||||
mIntent = intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Intent argument) {
|
||||
return argument != null && argument.filterEquals(mIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Expected: " + mIntent;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInjector extends AppCopyHelper.Injector {
|
||||
TestInjector() {
|
||||
super(mContext, mTestUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
UserHandle getUser() {
|
||||
return mTestUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
PackageManager getPackageManager() {
|
||||
return mPm;
|
||||
}
|
||||
|
||||
@Override
|
||||
IPackageManager getIPackageManager() {
|
||||
return mIpm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package com.android.settingslib.users;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.nullable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageDeleteObserver;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.settingslib.BaseTest;
|
||||
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
public class AppRestrictionsHelperTest extends BaseTest {
|
||||
private @Mock Context mContext;
|
||||
private @Mock PackageManager mPm;
|
||||
private @Mock IPackageManager mIpm;
|
||||
private @Mock UserManager mUm;
|
||||
|
||||
private TestInjector mInjector;
|
||||
private UserHandle mTestUser = UserHandle.of(1111);
|
||||
private AppRestrictionsHelper mHelper;
|
||||
|
||||
private ArrayList<String> mInstalledApps;
|
||||
private ArrayList<ApplicationInfo> mInstalledAppInfos;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mInjector = new TestInjector();
|
||||
final UserInfo user = new UserInfo(
|
||||
mTestUser.getIdentifier(), "test_user", UserInfo.FLAG_RESTRICTED);
|
||||
when(mUm.getUserInfo(mTestUser.getIdentifier())).thenReturn(user);
|
||||
mHelper = new AppRestrictionsHelper(mInjector);
|
||||
mInstalledApps = new ArrayList<>();
|
||||
mInstalledAppInfos = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void testFetchAndMergeApps() throws Exception {
|
||||
addSystemAppsWithRequiredAccounts("sys.app0");
|
||||
addsystemImes(new String[] {"sys.app1", "sys.app2"},
|
||||
new String[] {"sys.app3", "sys.app4", "sys.app5"});
|
||||
addSystemAppsForIntent(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER),
|
||||
"sys.app1", "sys.app4", "sys.app6");
|
||||
addSystemAppsForIntent(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
|
||||
"sys.app2", "sys.app5", "sys.app7");
|
||||
addDownloadedApps("app1", "app2");
|
||||
when(mPm.getInstalledApplications(anyInt())).thenReturn(mInstalledAppInfos);
|
||||
|
||||
mHelper.fetchAndMergeApps();
|
||||
|
||||
final ArrayList<String> notExpectedInVisibleApps = new ArrayList<>();
|
||||
// System apps that require an account and doesn't see restricted account are
|
||||
// not part of visibleApps.
|
||||
notExpectedInVisibleApps.add("sys.app0");
|
||||
// Default system IMEs are not part of visibleApps.
|
||||
notExpectedInVisibleApps.add("sys.app1");
|
||||
notExpectedInVisibleApps.add("sys.app2");
|
||||
|
||||
final ArrayList<String> expectedInVisibleApps = new ArrayList<>();
|
||||
expectedInVisibleApps.add("sys.app4");
|
||||
expectedInVisibleApps.add("sys.app5");
|
||||
expectedInVisibleApps.add("sys.app6");
|
||||
expectedInVisibleApps.add("sys.app7");
|
||||
expectedInVisibleApps.add("app1");
|
||||
expectedInVisibleApps.add("app2");
|
||||
|
||||
for (AppRestrictionsHelper.SelectableAppInfo info : mHelper.getVisibleApps()) {
|
||||
if (expectedInVisibleApps.contains(info.packageName)) {
|
||||
expectedInVisibleApps.remove(info.packageName);
|
||||
} else if (notExpectedInVisibleApps.contains(info.packageName)) {
|
||||
fail("Package: " + info.packageName + " should not be included in visibleApps");
|
||||
} else {
|
||||
fail("Unknown package: " + info.packageName);
|
||||
}
|
||||
}
|
||||
assertEquals("Some expected apps are not inclued in visibleApps: " + expectedInVisibleApps,
|
||||
0, expectedInVisibleApps.size());
|
||||
|
||||
assertFalse("System apps that require an account and doesn't see restricted account "
|
||||
+ "should be marked for removal", mHelper.isPackageSelected("sys.app0"));
|
||||
}
|
||||
|
||||
public void testApplyUserAppsStates() throws Exception {
|
||||
final int testUserId = mTestUser.getIdentifier();
|
||||
mHelper.setPackageSelected("app1", true);
|
||||
|
||||
mHelper.setPackageSelected("app2", true);
|
||||
ApplicationInfo info = new ApplicationInfo();
|
||||
info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
|
||||
info.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
when(mIpm.getApplicationInfo(eq("app2"), anyLong(), eq(testUserId)))
|
||||
.thenReturn(info);
|
||||
|
||||
mHelper.setPackageSelected("app3", false);
|
||||
info = new ApplicationInfo();
|
||||
when(mIpm.getApplicationInfo(eq("app3"), anyLong(), eq(testUserId)))
|
||||
.thenReturn(info);
|
||||
|
||||
AppRestrictionsHelper.OnDisableUiForPackageListener mockListener =
|
||||
mock(AppRestrictionsHelper.OnDisableUiForPackageListener.class);
|
||||
mHelper.applyUserAppsStates(mockListener);
|
||||
|
||||
verify(mIpm, times(1)).installExistingPackageAsUser("app1", testUserId,
|
||||
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
|
||||
PackageManager.INSTALL_REASON_UNKNOWN, null);
|
||||
verify(mIpm, times(1)).setApplicationHiddenSettingAsUser("app2", false, testUserId);
|
||||
verify(mockListener).onDisableUiForPackage("app2");
|
||||
verify(mPm, times(1)).deletePackageAsUser(eq("app3"),
|
||||
nullable(IPackageDeleteObserver.class), anyInt(), eq(mTestUser.getIdentifier()));
|
||||
}
|
||||
|
||||
private void addsystemImes(String[] defaultImes, String[] otherImes) throws
|
||||
PackageManager.NameNotFoundException, RemoteException {
|
||||
final ArrayList<InputMethodInfo> inputMethods = new ArrayList<>();
|
||||
for (String pkg : defaultImes) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
final InputMethodInfo inputMethodInfo = new InputMethodInfo(
|
||||
ri, false, null, null, 0, true, true, false);
|
||||
inputMethods.add(inputMethodInfo);
|
||||
addInstalledApp(ri);
|
||||
}
|
||||
for (String pkg : otherImes) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
final InputMethodInfo inputMethodInfo = new InputMethodInfo(
|
||||
ri, false, null, null, 0, false, true, false);
|
||||
inputMethods.add(inputMethodInfo);
|
||||
addInstalledApp(ri);
|
||||
}
|
||||
|
||||
mInjector.setInputMethodList(inputMethods);
|
||||
}
|
||||
|
||||
private void addSystemAppsForIntent(Intent intent, String... packages) throws Exception {
|
||||
List<ResolveInfo> resolveInfos = new ArrayList<>();
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
resolveInfos.add(ri);
|
||||
addInstalledApp(ri);
|
||||
}
|
||||
when(mPm.queryIntentActivities(argThat(new IntentMatcher(intent)), anyInt()))
|
||||
.thenReturn(resolveInfos);
|
||||
}
|
||||
|
||||
private void addSystemAppsWithRequiredAccounts(String... packages) throws Exception {
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.applicationInfo = ri.activityInfo.applicationInfo;
|
||||
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
packageInfo.requiredAccountType = "account";
|
||||
packageInfo.restrictedAccountType = null;
|
||||
mInstalledAppInfos.add(packageInfo.applicationInfo);
|
||||
when(mPm.getPackageInfo(eq(pkg), anyInt())).thenReturn(packageInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDownloadedApps(String... packages) throws Exception {
|
||||
for (String pkg : packages) {
|
||||
final ResolveInfo ri = createResolveInfo(pkg);
|
||||
addInstalledApp(ri);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInstalledApp(ResolveInfo ri) throws PackageManager.NameNotFoundException {
|
||||
final String pkgName = ri.activityInfo.packageName;
|
||||
if (mInstalledApps.contains(pkgName)) {
|
||||
return;
|
||||
}
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.applicationInfo = ri.activityInfo.applicationInfo;
|
||||
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
mInstalledAppInfos.add(packageInfo.applicationInfo);
|
||||
when(mPm.getPackageInfo(eq(pkgName), anyInt())).thenReturn(packageInfo);
|
||||
}
|
||||
|
||||
private ResolveInfo createResolveInfoForSystemApp(String packageName) {
|
||||
final ResolveInfo ri = createResolveInfo(packageName);
|
||||
ri.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
ri.serviceInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
return ri;
|
||||
}
|
||||
|
||||
private ResolveInfo createResolveInfo(String packageName) {
|
||||
final ResolveInfo ri = new ResolveInfo();
|
||||
final ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.packageName = packageName;
|
||||
final ActivityInfo activityInfo = new ActivityInfo();
|
||||
activityInfo.applicationInfo = applicationInfo;
|
||||
activityInfo.packageName = packageName;
|
||||
activityInfo.name = "";
|
||||
ri.activityInfo = activityInfo;
|
||||
final ServiceInfo serviceInfo = new ServiceInfo();
|
||||
serviceInfo.applicationInfo = applicationInfo;
|
||||
serviceInfo.packageName = packageName;
|
||||
serviceInfo.name = "";
|
||||
ri.serviceInfo = serviceInfo;
|
||||
return ri;
|
||||
}
|
||||
|
||||
private class IntentMatcher implements ArgumentMatcher<Intent> {
|
||||
private final Intent mIntent;
|
||||
|
||||
IntentMatcher(Intent intent) {
|
||||
mIntent = intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Intent argument) {
|
||||
return argument != null && argument.filterEquals(mIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Expected: " + mIntent;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInjector extends AppRestrictionsHelper.Injector {
|
||||
List<InputMethodInfo> mImis;
|
||||
|
||||
TestInjector() {
|
||||
super(mContext, mTestUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
UserHandle getUser() {
|
||||
return mTestUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
PackageManager getPackageManager() {
|
||||
return mPm;
|
||||
}
|
||||
|
||||
@Override
|
||||
IPackageManager getIPackageManager() {
|
||||
return mIpm;
|
||||
}
|
||||
|
||||
@Override
|
||||
UserManager getUserManager() {
|
||||
return mUm;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<InputMethodInfo> getInputMethodList() {
|
||||
return mImis;
|
||||
}
|
||||
|
||||
void setInputMethodList(List<InputMethodInfo> imis) {
|
||||
mImis = imis;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.users;
|
||||
|
||||
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CHOOSE_PHOTO;
|
||||
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_CROP_PHOTO;
|
||||
import static com.android.settingslib.users.AvatarPhotoController.REQUEST_CODE_TAKE_PHOTO;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AvatarPhotoControllerTest {
|
||||
|
||||
private static final long TIMEOUT_MILLIS = 5000;
|
||||
private static final int PHOTO_SIZE = 200;
|
||||
|
||||
@Mock AvatarPhotoController.AvatarUi mMockAvatarUi;
|
||||
|
||||
private File mImagesDir;
|
||||
private AvatarPhotoController mController;
|
||||
private Uri mTakePhotoUri = Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/TakeEditUserPhoto.jpg");
|
||||
private Uri mCropPhotoUri = Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/CropEditUserPhoto.jpg");
|
||||
private Context mContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockAvatarUi.getPhotoSize()).thenReturn(PHOTO_SIZE);
|
||||
when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(true);
|
||||
|
||||
mImagesDir = new File(
|
||||
InstrumentationRegistry.getTargetContext().getCacheDir(), "multi_user");
|
||||
mImagesDir.mkdir();
|
||||
|
||||
AvatarPhotoController.ContextInjector contextInjector =
|
||||
new AvatarPhotoController.ContextInjectorImpl(
|
||||
InstrumentationRegistry.getTargetContext(), "com.android.settingslib.test");
|
||||
mController = new AvatarPhotoController(mMockAvatarUi, contextInjector, false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
String[] entries = mImagesDir.list();
|
||||
for (String entry : entries) {
|
||||
new File(mImagesDir, entry).delete();
|
||||
}
|
||||
mImagesDir.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void takePhotoHasCorrectIntentAndResultCode() {
|
||||
mController.takePhoto();
|
||||
|
||||
verifyStartActivityForResult(
|
||||
MediaStore.ACTION_IMAGE_CAPTURE_SECURE, REQUEST_CODE_TAKE_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void choosePhotoHasCorrectIntentAndResultCode() {
|
||||
mController.choosePhoto();
|
||||
|
||||
verifyStartActivityForResult(
|
||||
MediaStore.ACTION_PICK_IMAGES, REQUEST_CODE_CHOOSE_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void takePhotoIsFollowedByCrop() throws IOException {
|
||||
new File(mImagesDir, "file.txt").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
verifyStartSystemActivityForResult(
|
||||
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void takePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
|
||||
new File(mImagesDir, "file.txt").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_CANCELED, intent);
|
||||
|
||||
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
|
||||
verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void takePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
|
||||
new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(mTakePhotoUri);
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
verifyStartSystemActivityForResult(
|
||||
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void choosePhotoIsFollowedByCrop() throws IOException {
|
||||
new File(mImagesDir, "file.txt").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
verifyStartSystemActivityForResult(
|
||||
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void choosePhotoIsNotFollowedByCropWhenResultCodeNotOk() throws IOException {
|
||||
new File(mImagesDir, "file.txt").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_CANCELED, intent);
|
||||
|
||||
verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt());
|
||||
verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void choosePhotoIsFollowedByCropWhenTakePhotoUriReturned() throws IOException {
|
||||
new File(mImagesDir, "TakeEditUserPhoto.jpg").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(mTakePhotoUri);
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_CHOOSE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
verifyStartSystemActivityForResult(
|
||||
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cropPhotoResultIsReturnedIfResultOkAndContent() {
|
||||
Intent intent = new Intent();
|
||||
intent.setData(mCropPhotoUri);
|
||||
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cropPhotoResultIsNotReturnedIfResultCancel() {
|
||||
Intent intent = new Intent();
|
||||
intent.setData(mCropPhotoUri);
|
||||
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_CANCELED, intent);
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cropPhotoResultIsNotReturnedIfResultNotContent() {
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse("file://test"));
|
||||
mController.onActivityResult(REQUEST_CODE_CROP_PHOTO, Activity.RESULT_OK, intent);
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS).times(0)).returnUriResult(mCropPhotoUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cropDoesNotUseTakePhotoUri() throws IOException {
|
||||
new File(mImagesDir, "file.txt").createNewFile();
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
Intent startIntent = verifyStartSystemActivityForResult(
|
||||
"com.android.camera.action.CROP", REQUEST_CODE_CROP_PHOTO);
|
||||
assertThat(startIntent.getData()).isNotEqualTo(mTakePhotoUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void internalCropUsedIfNoSystemCropperFound() throws IOException {
|
||||
when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false);
|
||||
|
||||
File file = new File(mImagesDir, "file.txt");
|
||||
saveBitmapToFile(file);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setData(Uri.parse(
|
||||
"content://com.android.settingslib.test/my_cache/multi_user/file.txt"));
|
||||
mController.onActivityResult(
|
||||
REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent);
|
||||
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS)).returnUriResult(mCropPhotoUri);
|
||||
|
||||
InputStream imageStream = mContext.getContentResolver().openInputStream(mCropPhotoUri);
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
|
||||
assertThat(bitmap.getWidth()).isEqualTo(PHOTO_SIZE);
|
||||
assertThat(bitmap.getHeight()).isEqualTo(PHOTO_SIZE);
|
||||
}
|
||||
|
||||
private Intent verifyStartActivityForResult(String action, int resultCode) {
|
||||
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
|
||||
.startActivityForResult(captor.capture(), eq(resultCode));
|
||||
Intent intent = captor.getValue();
|
||||
assertThat(intent.getAction()).isEqualTo(action);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent verifyStartSystemActivityForResult(String action, int resultCode) {
|
||||
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockAvatarUi, timeout(TIMEOUT_MILLIS))
|
||||
.startSystemActivityForResult(captor.capture(), eq(resultCode));
|
||||
Intent intent = captor.getValue();
|
||||
assertThat(intent.getAction()).isEqualTo(action);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private void saveBitmapToFile(File file) throws IOException {
|
||||
Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
|
||||
OutputStream os = new FileOutputStream(file);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package com.android.settingslib.utils;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import android.net.NetworkPolicy;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkTemplate;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.settingslib.NetworkPolicyEditor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkPolicyEditorTest {
|
||||
private static final long MAX_LIMIT_BYTES = 500000;
|
||||
private static final long TEST_LIMIT_BYTES = 2500;
|
||||
private static final long[] WARNING_BYTES_LIST = {100, 1000, 2000, 3000, 40000};
|
||||
|
||||
private NetworkTemplate mNetworkTemplate;
|
||||
private NetworkPolicyEditor mNetworkPolicyEditor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_CARRIER)
|
||||
.setMeteredness(NetworkStats.METERED_YES)
|
||||
.setSubscriberIds(Set.of("123456789123456")).build();
|
||||
NetworkPolicyManager policyManager = NetworkPolicyManager.from(InstrumentationRegistry
|
||||
.getContext());
|
||||
mNetworkPolicyEditor = new NetworkPolicyEditor(policyManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPolicyWarningBytes_withoutLimit_shouldNotCapWarningBytes() {
|
||||
// Set the limit to disable so we can change the warning bytes freely
|
||||
mNetworkPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, NetworkPolicy.LIMIT_DISABLED);
|
||||
|
||||
for (int i = 0; i < WARNING_BYTES_LIST.length; i++) {
|
||||
mNetworkPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, WARNING_BYTES_LIST[i]);
|
||||
assertEquals(WARNING_BYTES_LIST[i],
|
||||
mNetworkPolicyEditor.getPolicyWarningBytes(mNetworkTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPolicyWarningBytes_withLimit_shouldCapWarningBytes() {
|
||||
// Set the limit bytes, so warning bytes cannot exceed the limit bytes.
|
||||
mNetworkPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, TEST_LIMIT_BYTES);
|
||||
|
||||
for (int i = 0; i < WARNING_BYTES_LIST.length; i++) {
|
||||
mNetworkPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, WARNING_BYTES_LIST[i]);
|
||||
long expectedWarningBytes = Math.min(WARNING_BYTES_LIST[i], TEST_LIMIT_BYTES);
|
||||
assertEquals(expectedWarningBytes,
|
||||
mNetworkPolicyEditor.getPolicyWarningBytes(mNetworkTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPolicyLimitBytes_warningBytesSmallerThanLimit_shouldNotCapWarningBytes() {
|
||||
long testWarningBytes = MAX_LIMIT_BYTES / 2;
|
||||
|
||||
mNetworkPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, MAX_LIMIT_BYTES);
|
||||
mNetworkPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, testWarningBytes);
|
||||
|
||||
assertEquals(MAX_LIMIT_BYTES, mNetworkPolicyEditor.getPolicyLimitBytes(mNetworkTemplate));
|
||||
assertEquals(testWarningBytes,
|
||||
mNetworkPolicyEditor.getPolicyWarningBytes(mNetworkTemplate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPolicyLimitBytes_warningBytesBiggerThanLimit_shouldCapWarningBytes() {
|
||||
long testWarningBytes = TEST_LIMIT_BYTES * 2;
|
||||
|
||||
mNetworkPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, MAX_LIMIT_BYTES);
|
||||
mNetworkPolicyEditor.setPolicyWarningBytes(mNetworkTemplate, testWarningBytes);
|
||||
mNetworkPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, TEST_LIMIT_BYTES);
|
||||
|
||||
assertEquals(TEST_LIMIT_BYTES, mNetworkPolicyEditor.getPolicyLimitBytes(mNetworkTemplate));
|
||||
long expectedWarningBytes = Math.min(testWarningBytes, TEST_LIMIT_BYTES);
|
||||
assertEquals(expectedWarningBytes,
|
||||
mNetworkPolicyEditor.getPolicyWarningBytes(mNetworkTemplate));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
package com.android.settingslib.utils;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.TtsSpan;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.settingslib.datetime.ZoneGetter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ZoneGetterTest {
|
||||
private static final String TIME_ZONE_LONDON_ID = "Europe/London";
|
||||
private static final String TIME_ZONE_LA_ID = "America/Los_Angeles";
|
||||
private static final String TIME_ZONE_ALGIERS_ID = "Africa/Algiers";
|
||||
private static final String TIME_ZONE_CEUTA_ID = "Africa/Ceuta";
|
||||
private Locale mLocaleEnUs;
|
||||
private Calendar mCalendar;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mLocaleEnUs = new Locale("en", "us");
|
||||
Locale.setDefault(mLocaleEnUs);
|
||||
mCalendar = new GregorianCalendar(2016, 9, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() {
|
||||
// Check it will ends with 'British Summer Time', not 'London' or sth else
|
||||
testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeZoneOffsetAndName_setLosAngeles_returnPacificDaylightTime() {
|
||||
// Check it will ends with 'Pacific Daylight Time', not 'Los_Angeles'
|
||||
testTimeZoneOffsetAndNameInner(TIME_ZONE_LA_ID, "Pacific Daylight Time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeZoneOffsetAndName_setAlgiers_returnCentralEuropeanStandardTime() {
|
||||
testTimeZoneOffsetAndNameInner(TIME_ZONE_ALGIERS_ID, "Central European Standard Time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeZoneOffsetAndName_setCeuta_returnCentralEuropeanSummerTime() {
|
||||
testTimeZoneOffsetAndNameInner(TIME_ZONE_CEUTA_ID, "Central European Summer Time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getZonesList_checkTypes() {
|
||||
final List<Map<String, Object>> zones =
|
||||
ZoneGetter.getZonesList(InstrumentationRegistry.getContext());
|
||||
for (Map<String, Object> zone : zones) {
|
||||
assertTrue(zone.get(ZoneGetter.KEY_DISPLAYNAME) instanceof String);
|
||||
assertTrue(zone.get(ZoneGetter.KEY_DISPLAY_LABEL) instanceof CharSequence);
|
||||
assertTrue(zone.get(ZoneGetter.KEY_OFFSET) instanceof Integer);
|
||||
assertTrue(zone.get(ZoneGetter.KEY_OFFSET_LABEL) instanceof CharSequence);
|
||||
assertTrue(zone.get(ZoneGetter.KEY_ID) instanceof String);
|
||||
assertTrue(zone.get(ZoneGetter.KEY_GMT) instanceof String);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTimeZoneOffsetAndName_withTtsSpan() {
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
final TimeZone timeZone = TimeZone.getTimeZone(TIME_ZONE_LA_ID);
|
||||
|
||||
CharSequence timeZoneString = ZoneGetter.getTimeZoneOffsetAndName(context, timeZone,
|
||||
mCalendar.getTime());
|
||||
assertTrue("Time zone string should be spanned", timeZoneString instanceof Spanned);
|
||||
assertTrue("Time zone display name should have TTS spans",
|
||||
((Spanned) timeZoneString).getSpans(
|
||||
0, timeZoneString.length(), TtsSpan.class).length > 0);
|
||||
}
|
||||
|
||||
private void testTimeZoneOffsetAndNameInner(String timeZoneId, String expectedName) {
|
||||
final Context context = InstrumentationRegistry.getContext();
|
||||
final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
|
||||
|
||||
CharSequence timeZoneString = ZoneGetter.getTimeZoneOffsetAndName(context, timeZone,
|
||||
mCalendar.getTime());
|
||||
|
||||
assertTrue(timeZoneString.toString().endsWith(expectedName));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.view.accessibility.data.repository
|
||||
|
||||
import android.view.accessibility.CaptioningManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CaptioningRepositoryTest {
|
||||
|
||||
@Captor
|
||||
private lateinit var listenerCaptor: ArgumentCaptor<CaptioningManager.CaptioningChangeListener>
|
||||
|
||||
@Mock private lateinit var captioningManager: CaptioningManager
|
||||
|
||||
private lateinit var underTest: CaptioningRepository
|
||||
|
||||
private val testScope = TestScope()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
underTest =
|
||||
CaptioningRepositoryImpl(
|
||||
captioningManager,
|
||||
testScope.testScheduler,
|
||||
testScope.backgroundScope
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSystemAudioCaptioningEnabled_change_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
`when`(captioningManager.isEnabled).thenReturn(false)
|
||||
val isSystemAudioCaptioningEnabled = mutableListOf<Boolean>()
|
||||
underTest.isSystemAudioCaptioningEnabled
|
||||
.onEach { isSystemAudioCaptioningEnabled.add(it) }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerOnSystemAudioCaptioningChange()
|
||||
runCurrent()
|
||||
|
||||
assertThat(isSystemAudioCaptioningEnabled)
|
||||
.containsExactlyElementsIn(listOf(false, true))
|
||||
.inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSystemAudioCaptioningUiEnabled_change_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
`when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(false)
|
||||
val isSystemAudioCaptioningUiEnabled = mutableListOf<Boolean>()
|
||||
underTest.isSystemAudioCaptioningUiEnabled
|
||||
.onEach { isSystemAudioCaptioningUiEnabled.add(it) }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerSystemAudioCaptioningUiChange()
|
||||
runCurrent()
|
||||
|
||||
assertThat(isSystemAudioCaptioningUiEnabled)
|
||||
.containsExactlyElementsIn(listOf(false, true))
|
||||
.inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerSystemAudioCaptioningUiChange(enabled: Boolean = true) {
|
||||
verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
|
||||
listenerCaptor.value.onSystemAudioCaptioningUiChanged(enabled)
|
||||
}
|
||||
|
||||
private fun triggerOnSystemAudioCaptioningChange(enabled: Boolean = true) {
|
||||
verify(captioningManager).addCaptioningChangeListener(listenerCaptor.capture())
|
||||
listenerCaptor.value.onSystemAudioCaptioningChanged(enabled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include /packages/SettingsLib/src/com/android/settingslib/volume/OWNERS
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.volume.data.repository
|
||||
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
|
||||
import com.android.settingslib.volume.shared.model.AudioManagerEvent
|
||||
import com.android.settingslib.volume.shared.model.AudioStream
|
||||
import com.android.settingslib.volume.shared.model.AudioStreamModel
|
||||
import com.android.settingslib.volume.shared.model.RingerMode
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AudioRepositoryTest {
|
||||
|
||||
@Captor
|
||||
private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
|
||||
@Captor
|
||||
private lateinit var communicationDeviceListenerCaptor:
|
||||
ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
|
||||
|
||||
@Mock private lateinit var audioManager: AudioManager
|
||||
@Mock private lateinit var communicationDevice: AudioDeviceInfo
|
||||
|
||||
private val eventsReceiver = FakeAudioManagerEventsReceiver()
|
||||
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
|
||||
private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
|
||||
private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
|
||||
private val testScope = TestScope()
|
||||
|
||||
private lateinit var underTest: AudioRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
|
||||
`when`(audioManager.communicationDevice).thenReturn(communicationDevice)
|
||||
`when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
|
||||
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
|
||||
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
|
||||
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
|
||||
val streamType = it.arguments[1] as Int
|
||||
volumeByStream[it.arguments[0] as Int] = streamType
|
||||
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
|
||||
}
|
||||
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
|
||||
val streamType = it.arguments[0] as Int
|
||||
isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
|
||||
triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
|
||||
}
|
||||
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
|
||||
volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
|
||||
}
|
||||
`when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
|
||||
isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
|
||||
}
|
||||
`when`(audioManager.isStreamMute(anyInt())).thenAnswer {
|
||||
isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
|
||||
}
|
||||
|
||||
underTest =
|
||||
AudioRepositoryImpl(
|
||||
eventsReceiver,
|
||||
audioManager,
|
||||
testScope.testScheduler,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun audioModeChanges_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
val modes = mutableListOf<Int>()
|
||||
underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerModeChange(AudioManager.MODE_IN_CALL)
|
||||
runCurrent()
|
||||
|
||||
assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ringerModeChanges_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
val modes = mutableListOf<RingerMode>()
|
||||
underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
|
||||
triggerEvent(AudioManagerEvent.InternalRingerModeChanged)
|
||||
runCurrent()
|
||||
|
||||
assertThat(modes)
|
||||
.containsExactly(
|
||||
RingerMode(AudioManager.RINGER_MODE_NORMAL),
|
||||
RingerMode(AudioManager.RINGER_MODE_SILENT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun communicationDeviceChanges_repositoryEmits() {
|
||||
testScope.runTest {
|
||||
var device: AudioDeviceInfo? = null
|
||||
underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerConnectedDeviceChange(communicationDevice)
|
||||
runCurrent()
|
||||
|
||||
assertThat(device).isSameInstanceAs(communicationDevice)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustingVolume_changesTheStream() {
|
||||
testScope.runTest {
|
||||
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
|
||||
var streamModel: AudioStreamModel? = null
|
||||
underTest
|
||||
.getAudioStream(audioStream)
|
||||
.onEach { streamModel = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
underTest.setVolume(audioStream, 50)
|
||||
runCurrent()
|
||||
|
||||
assertThat(streamModel)
|
||||
.isEqualTo(
|
||||
AudioStreamModel(
|
||||
audioStream = audioStream,
|
||||
volume = 50,
|
||||
minVolume = MIN_VOLUME,
|
||||
maxVolume = MAX_VOLUME,
|
||||
isAffectedByRingerMode = false,
|
||||
isMuted = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun muteStream_mutesTheStream() {
|
||||
testScope.runTest {
|
||||
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
|
||||
var streamModel: AudioStreamModel? = null
|
||||
underTest
|
||||
.getAudioStream(audioStream)
|
||||
.onEach { streamModel = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
underTest.setMuted(audioStream, true)
|
||||
runCurrent()
|
||||
|
||||
assertThat(streamModel)
|
||||
.isEqualTo(
|
||||
AudioStreamModel(
|
||||
audioStream = audioStream,
|
||||
volume = 0,
|
||||
minVolume = MIN_VOLUME,
|
||||
maxVolume = MAX_VOLUME,
|
||||
isAffectedByRingerMode = false,
|
||||
isMuted = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unmuteStream_unmutesTheStream() {
|
||||
testScope.runTest {
|
||||
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
|
||||
isMuteByStream[audioStream.value] = true
|
||||
var streamModel: AudioStreamModel? = null
|
||||
underTest
|
||||
.getAudioStream(audioStream)
|
||||
.onEach { streamModel = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
underTest.setMuted(audioStream, false)
|
||||
runCurrent()
|
||||
|
||||
assertThat(streamModel)
|
||||
.isEqualTo(
|
||||
AudioStreamModel(
|
||||
audioStream = audioStream,
|
||||
volume = 0,
|
||||
minVolume = MIN_VOLUME,
|
||||
maxVolume = MAX_VOLUME,
|
||||
isAffectedByRingerMode = false,
|
||||
isMuted = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
|
||||
verify(audioManager)
|
||||
.addOnCommunicationDeviceChangedListener(
|
||||
any(),
|
||||
communicationDeviceListenerCaptor.capture(),
|
||||
)
|
||||
communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
|
||||
}
|
||||
|
||||
private fun triggerModeChange(mode: Int) {
|
||||
verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
|
||||
modeListenerCaptor.value.onModeChanged(mode)
|
||||
}
|
||||
|
||||
private fun triggerEvent(event: AudioManagerEvent) {
|
||||
testScope.launch { eventsReceiver.triggerEvent(event) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MIN_VOLUME = 0
|
||||
const val MAX_VOLUME = 100
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.volume.data.repository
|
||||
|
||||
import android.media.MediaRoute2Info
|
||||
import android.media.MediaRouter2Manager
|
||||
import android.media.RoutingSessionInfo
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.media.LocalMediaManager
|
||||
import com.android.settingslib.media.MediaDevice
|
||||
import com.android.settingslib.volume.data.model.RoutingSession
|
||||
import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.anyString
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class LocalMediaRepositoryImplTest {
|
||||
|
||||
@Mock private lateinit var localMediaManager: LocalMediaManager
|
||||
@Mock private lateinit var mediaDevice1: MediaDevice
|
||||
@Mock private lateinit var mediaDevice2: MediaDevice
|
||||
@Mock private lateinit var mediaRouter2Manager: MediaRouter2Manager
|
||||
|
||||
@Captor
|
||||
private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
|
||||
|
||||
private val eventsReceiver = FakeAudioManagerEventsReceiver()
|
||||
private val testScope = TestScope()
|
||||
|
||||
private lateinit var underTest: LocalMediaRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
underTest =
|
||||
LocalMediaRepositoryImpl(
|
||||
eventsReceiver,
|
||||
localMediaManager,
|
||||
mediaRouter2Manager,
|
||||
testScope.backgroundScope,
|
||||
testScope.testScheduler,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mediaDevices_areUpdated() {
|
||||
testScope.runTest {
|
||||
var mediaDevices: Collection<MediaDevice>? = null
|
||||
underTest.mediaDevices.onEach { mediaDevices = it }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
|
||||
deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
|
||||
runCurrent()
|
||||
|
||||
assertThat(mediaDevices).hasSize(2)
|
||||
assertThat(mediaDevices).contains(mediaDevice1)
|
||||
assertThat(mediaDevices).contains(mediaDevice2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceListUpdated_currentConnectedDeviceUpdated() {
|
||||
testScope.runTest {
|
||||
var currentConnectedDevice: MediaDevice? = null
|
||||
underTest.currentConnectedDevice
|
||||
.onEach { currentConnectedDevice = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
`when`(localMediaManager.currentConnectedDevice).thenReturn(mediaDevice1)
|
||||
verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
|
||||
deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
|
||||
runCurrent()
|
||||
|
||||
assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun kek() {
|
||||
testScope.runTest {
|
||||
`when`(localMediaManager.remoteRoutingSessions)
|
||||
.thenReturn(
|
||||
listOf(
|
||||
testRoutingSessionInfo1,
|
||||
testRoutingSessionInfo2,
|
||||
testRoutingSessionInfo3,
|
||||
)
|
||||
)
|
||||
`when`(localMediaManager.shouldEnableVolumeSeekBar(any())).then {
|
||||
(it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo1
|
||||
}
|
||||
`when`(mediaRouter2Manager.getTransferableRoutes(any<RoutingSessionInfo>())).then {
|
||||
if ((it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo2) {
|
||||
return@then listOf(mock(MediaRoute2Info::class.java))
|
||||
}
|
||||
emptyList<MediaRoute2Info>()
|
||||
}
|
||||
var remoteRoutingSessions: Collection<RoutingSession>? = null
|
||||
underTest.remoteRoutingSessions
|
||||
.onEach { remoteRoutingSessions = it }
|
||||
.launchIn(backgroundScope)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(remoteRoutingSessions)
|
||||
.containsExactlyElementsIn(
|
||||
listOf(
|
||||
RoutingSession(
|
||||
routingSessionInfo = testRoutingSessionInfo1,
|
||||
isVolumeSeekBarEnabled = true,
|
||||
isMediaOutputDisabled = true,
|
||||
),
|
||||
RoutingSession(
|
||||
routingSessionInfo = testRoutingSessionInfo2,
|
||||
isVolumeSeekBarEnabled = false,
|
||||
isMediaOutputDisabled = false,
|
||||
),
|
||||
RoutingSession(
|
||||
routingSessionInfo = testRoutingSessionInfo3,
|
||||
isVolumeSeekBarEnabled = false,
|
||||
isMediaOutputDisabled = true,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun adjustSessionVolume_adjusts() {
|
||||
testScope.runTest {
|
||||
var volume = 0
|
||||
`when`(localMediaManager.adjustSessionVolume(anyString(), anyInt())).then {
|
||||
volume = it.arguments[1] as Int
|
||||
Unit
|
||||
}
|
||||
|
||||
underTest.adjustSessionVolume("test_session", 10)
|
||||
|
||||
assertThat(volume).isEqualTo(10)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val testRoutingSessionInfo1 =
|
||||
RoutingSessionInfo.Builder("id_1", "test.pkg.1").addSelectedRoute("route_1").build()
|
||||
val testRoutingSessionInfo2 =
|
||||
RoutingSessionInfo.Builder("id_2", "test.pkg.2").addSelectedRoute("route_2").build()
|
||||
val testRoutingSessionInfo3 =
|
||||
RoutingSessionInfo.Builder("id_3", "test.pkg.3").addSelectedRoute("route_3").build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.volume.data.repository
|
||||
|
||||
import android.media.session.MediaController
|
||||
import android.media.session.MediaController.PlaybackInfo
|
||||
import android.media.session.MediaSessionManager
|
||||
import android.media.session.PlaybackState
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.bluetooth.BluetoothCallback
|
||||
import com.android.settingslib.bluetooth.BluetoothEventManager
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
||||
import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
|
||||
import com.android.settingslib.volume.shared.model.AudioManagerEvent
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class MediaControllerRepositoryImplTest {
|
||||
|
||||
@Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
|
||||
|
||||
@Mock private lateinit var mediaSessionManager: MediaSessionManager
|
||||
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
|
||||
@Mock private lateinit var eventManager: BluetoothEventManager
|
||||
|
||||
@Mock private lateinit var stoppedMediaController: MediaController
|
||||
@Mock private lateinit var statelessMediaController: MediaController
|
||||
@Mock private lateinit var errorMediaController: MediaController
|
||||
@Mock private lateinit var remoteMediaController: MediaController
|
||||
@Mock private lateinit var localMediaController: MediaController
|
||||
|
||||
@Mock private lateinit var remotePlaybackInfo: PlaybackInfo
|
||||
@Mock private lateinit var localPlaybackInfo: PlaybackInfo
|
||||
|
||||
private val testScope = TestScope()
|
||||
private val eventsReceiver = FakeAudioManagerEventsReceiver()
|
||||
|
||||
private lateinit var underTest: MediaControllerRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(localBluetoothManager.eventManager).thenReturn(eventManager)
|
||||
|
||||
`when`(stoppedMediaController.playbackState).thenReturn(stateStopped)
|
||||
`when`(stoppedMediaController.packageName).thenReturn("test.pkg.stopped")
|
||||
`when`(statelessMediaController.playbackState).thenReturn(stateNone)
|
||||
`when`(statelessMediaController.packageName).thenReturn("test.pkg.stateless")
|
||||
`when`(errorMediaController.playbackState).thenReturn(stateError)
|
||||
`when`(errorMediaController.packageName).thenReturn("test.pkg.error")
|
||||
`when`(remoteMediaController.playbackState).thenReturn(statePlaying)
|
||||
`when`(remoteMediaController.playbackInfo).thenReturn(remotePlaybackInfo)
|
||||
`when`(remoteMediaController.packageName).thenReturn("test.pkg.remote")
|
||||
`when`(localMediaController.playbackState).thenReturn(statePlaying)
|
||||
`when`(localMediaController.playbackInfo).thenReturn(localPlaybackInfo)
|
||||
`when`(localMediaController.packageName).thenReturn("test.pkg.local")
|
||||
|
||||
`when`(remotePlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
|
||||
`when`(localPlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
|
||||
|
||||
underTest =
|
||||
MediaControllerRepositoryImpl(
|
||||
eventsReceiver,
|
||||
mediaSessionManager,
|
||||
localBluetoothManager,
|
||||
testScope.backgroundScope,
|
||||
testScope.testScheduler,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun playingMediaDevicesAvailable_sessionIsActive() {
|
||||
testScope.runTest {
|
||||
`when`(mediaSessionManager.getActiveSessions(any()))
|
||||
.thenReturn(
|
||||
listOf(
|
||||
stoppedMediaController,
|
||||
statelessMediaController,
|
||||
errorMediaController,
|
||||
remoteMediaController,
|
||||
localMediaController
|
||||
)
|
||||
)
|
||||
var mediaController: MediaController? = null
|
||||
underTest.activeLocalMediaController
|
||||
.onEach { mediaController = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
|
||||
triggerOnAudioModeChanged()
|
||||
runCurrent()
|
||||
|
||||
assertThat(mediaController).isSameInstanceAs(localMediaController)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noPlayingMediaDevicesAvailable_sessionIsInactive() {
|
||||
testScope.runTest {
|
||||
`when`(mediaSessionManager.getActiveSessions(any()))
|
||||
.thenReturn(
|
||||
listOf(
|
||||
stoppedMediaController,
|
||||
statelessMediaController,
|
||||
errorMediaController,
|
||||
)
|
||||
)
|
||||
var mediaController: MediaController? = null
|
||||
underTest.activeLocalMediaController
|
||||
.onEach { mediaController = it }
|
||||
.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
|
||||
triggerOnAudioModeChanged()
|
||||
runCurrent()
|
||||
|
||||
assertThat(mediaController).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerOnAudioModeChanged() {
|
||||
verify(eventManager).registerCallback(callbackCaptor.capture())
|
||||
callbackCaptor.value.onAudioModeChanged()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val statePlaying: PlaybackState =
|
||||
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
|
||||
val stateError: PlaybackState =
|
||||
PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
|
||||
val stateStopped: PlaybackState =
|
||||
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
|
||||
val stateNone: PlaybackState =
|
||||
PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.volume.shared
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.AudioManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.volume.shared.model.AudioManagerEvent
|
||||
import com.android.settingslib.volume.shared.model.AudioStream
|
||||
import com.google.common.truth.Expect
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AudioManagerEventsReceiverTest {
|
||||
|
||||
@JvmField @Rule val expect = Expect.create()
|
||||
private val testScope = TestScope()
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
|
||||
private lateinit var underTest: AudioManagerEventsReceiver
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validIntent_translatedToEvent() {
|
||||
testScope.runTest {
|
||||
val events = mutableListOf<AudioManagerEvent>()
|
||||
underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerIntent(
|
||||
Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION).apply {
|
||||
putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM)
|
||||
}
|
||||
)
|
||||
triggerIntent(
|
||||
Intent(AudioManager.VOLUME_CHANGED_ACTION).apply {
|
||||
putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM)
|
||||
}
|
||||
)
|
||||
triggerIntent(Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION))
|
||||
triggerIntent(Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION))
|
||||
triggerIntent(Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
|
||||
runCurrent()
|
||||
|
||||
expect
|
||||
.that(events)
|
||||
.containsExactly(
|
||||
AudioManagerEvent.StreamMuteChanged(
|
||||
AudioStream(AudioManager.STREAM_SYSTEM),
|
||||
),
|
||||
AudioManagerEvent.StreamVolumeChanged(
|
||||
AudioStream(AudioManager.STREAM_SYSTEM),
|
||||
),
|
||||
AudioManagerEvent.StreamMasterMuteChanged,
|
||||
AudioManagerEvent.InternalRingerModeChanged,
|
||||
AudioManagerEvent.StreamDevicesChanged,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun streamAudioManagerEvent_withoutAudioStream_areSkipped() {
|
||||
testScope.runTest {
|
||||
val events = mutableListOf<AudioManagerEvent>()
|
||||
underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerIntent(Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION))
|
||||
triggerIntent(Intent(AudioManager.VOLUME_CHANGED_ACTION))
|
||||
runCurrent()
|
||||
|
||||
expect.that(events).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invalidIntents_areSkipped() {
|
||||
testScope.runTest {
|
||||
val events = mutableListOf<AudioManagerEvent>()
|
||||
underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
|
||||
runCurrent()
|
||||
|
||||
triggerIntent(null)
|
||||
triggerIntent(Intent())
|
||||
triggerIntent(Intent("invalid_action"))
|
||||
runCurrent()
|
||||
|
||||
expect.that(events).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerIntent(intent: Intent?) {
|
||||
verify(context).registerReceiver(receiverCaptor.capture(), any())
|
||||
receiverCaptor.value.onReceive(context, intent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.settingslib.volume.shared
|
||||
|
||||
import com.android.settingslib.volume.shared.model.AudioManagerEvent
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
class FakeAudioManagerEventsReceiver : AudioManagerEventsReceiver {
|
||||
|
||||
private val mutableIntents = MutableSharedFlow<AudioManagerEvent>()
|
||||
override val events: SharedFlow<AudioManagerEvent> = mutableIntents.asSharedFlow()
|
||||
|
||||
suspend fun triggerEvent(event: AudioManagerEvent) {
|
||||
mutableIntents.emit(event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.settingslib.widget;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.settingslib.widget.spinner.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SettingsSpinnerPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private PreferenceViewHolder mViewHolder;
|
||||
private Spinner mSpinner;
|
||||
private SettingsSpinnerPreference mSpinnerPreference;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mSpinnerPreference = new SettingsSpinnerPreference(mContext);
|
||||
final LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
final View rootView = inflater.inflate(mSpinnerPreference.getLayoutResource(),
|
||||
new LinearLayout(mContext), false /* attachToRoot */);
|
||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
|
||||
mSpinner = (Spinner) mViewHolder.findViewById(R.id.spinner);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_noSetSelection_getDefaultItem() {
|
||||
final List<CharSequence> list = new ArrayList<>();
|
||||
list.add("TEST1");
|
||||
list.add("TEST2");
|
||||
list.add("TEST3");
|
||||
final SettingsSpinnerAdapter adapter = new SettingsSpinnerAdapter(mContext);
|
||||
adapter.addAll(list);
|
||||
mSpinnerPreference.setAdapter(adapter);
|
||||
|
||||
mSpinnerPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
assertThat(adapter).isEqualTo(mSpinner.getAdapter());
|
||||
assertThat(mSpinnerPreference.getSelectedItem())
|
||||
.isEqualTo(mSpinner.getAdapter().getItem(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_setSelection_getSelectedItem() {
|
||||
final List<CharSequence> list = new ArrayList<>();
|
||||
list.add("TEST1");
|
||||
list.add("TEST2");
|
||||
list.add("TEST3");
|
||||
final SettingsSpinnerAdapter adapter = new SettingsSpinnerAdapter(mContext);
|
||||
adapter.addAll(list);
|
||||
mSpinnerPreference.setAdapter(adapter);
|
||||
mSpinnerPreference.setSelection(1);
|
||||
|
||||
mSpinnerPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
assertThat(mSpinnerPreference.getSelectedItem())
|
||||
.isEqualTo(mSpinner.getAdapter().getItem(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.settingslib.widget;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.SpannedString;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.settingslib.widget.preference.usage.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UsageProgressBarPreferenceTest {
|
||||
|
||||
private UsageProgressBarPreference mUsageProgressBarPreference;
|
||||
private PreferenceViewHolder mViewHolder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
final Context context = ApplicationProvider.getApplicationContext();
|
||||
mUsageProgressBarPreference = new UsageProgressBarPreference(context);
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
final View rootView = inflater.inflate(mUsageProgressBarPreference.getLayoutResource(),
|
||||
new LinearLayout(context), false /* attachToRoot */);
|
||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUsageSummary_noNumber_noAbsoluteSizeSpan() {
|
||||
mUsageProgressBarPreference.setUsageSummary("test");
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView usageSummary = (TextView) mViewHolder.findViewById(R.id.usage_summary);
|
||||
final SpannedString summary = new SpannedString(usageSummary.getText());
|
||||
assertThat(summary.getSpans(0, summary.length(), AbsoluteSizeSpan.class).length)
|
||||
.isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUsageSummary_integerNumber_findAbsoluteSizeSpan() {
|
||||
mUsageProgressBarPreference.setUsageSummary("10Test");
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView usageSummary = (TextView) mViewHolder.findViewById(R.id.usage_summary);
|
||||
final SpannedString summary = new SpannedString(usageSummary.getText());
|
||||
final AbsoluteSizeSpan[] spans = summary
|
||||
.getSpans(0, summary.length(), AbsoluteSizeSpan.class);
|
||||
assertThat(spans.length).isEqualTo(1);
|
||||
assertThat(summary.getSpanStart(spans[0])).isEqualTo(0);
|
||||
assertThat(summary.getSpanEnd(spans[0])).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUsageSummary_floatingPointNumber_findAbsoluteSizeSpan() {
|
||||
mUsageProgressBarPreference.setUsageSummary("3.14Test");
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView usageSummary = (TextView) mViewHolder.findViewById(R.id.usage_summary);
|
||||
final SpannedString summary = new SpannedString(usageSummary.getText());
|
||||
final AbsoluteSizeSpan[] spans = summary
|
||||
.getSpans(0, summary.length(), AbsoluteSizeSpan.class);
|
||||
assertThat(spans.length).isEqualTo(1);
|
||||
assertThat(summary.getSpanStart(spans[0])).isEqualTo(0);
|
||||
assertThat(summary.getSpanEnd(spans[0])).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUsageSummary_commaFloatingPointNumber_findAbsoluteSizeSpan() {
|
||||
mUsageProgressBarPreference.setUsageSummary("3,14Test");
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView usageSummary = (TextView) mViewHolder.findViewById(R.id.usage_summary);
|
||||
final SpannedString summary = new SpannedString(usageSummary.getText());
|
||||
final AbsoluteSizeSpan[] spans = summary
|
||||
.getSpans(0, summary.length(), AbsoluteSizeSpan.class);
|
||||
assertThat(spans.length).isEqualTo(1);
|
||||
assertThat(summary.getSpanStart(spans[0])).isEqualTo(0);
|
||||
assertThat(summary.getSpanEnd(spans[0])).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBottomSummary_getCorrectSummary() {
|
||||
final String expectedText = "Should last until about 7:45 PM";
|
||||
mUsageProgressBarPreference.setBottomSummary(expectedText);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView bottomSummary = (TextView) mViewHolder.findViewById(R.id.bottom_summary);
|
||||
assertThat(bottomSummary.getText()).isEqualTo(expectedText);
|
||||
assertThat(bottomSummary.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setBottomSummary_emptyText_isGone() {
|
||||
mUsageProgressBarPreference.setBottomSummary(null);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final TextView bottomSummary = (TextView) mViewHolder.findViewById(R.id.bottom_summary);
|
||||
assertThat(bottomSummary.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPercent_getCorrectProgress() {
|
||||
mUsageProgressBarPreference.setPercent(31, 80);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final ProgressBar progressBar = (ProgressBar) mViewHolder
|
||||
.findViewById(android.R.id.progress);
|
||||
assertThat(progressBar.getProgress()).isEqualTo((int) (31.0f / 80 * 100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPercent_totalSizeZero_getProgressZero() {
|
||||
mUsageProgressBarPreference.setPercent(0 /* usage */, 0 /* total */);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final ProgressBar progressBar = (ProgressBar) mViewHolder
|
||||
.findViewById(android.R.id.progress);
|
||||
assertThat(progressBar.getProgress()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCustomContent_setNullImageView_noChild() {
|
||||
mUsageProgressBarPreference.setCustomContent(null /* imageView */);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final FrameLayout customContent =
|
||||
(FrameLayout) mViewHolder.findViewById(R.id.custom_content);
|
||||
assertThat(customContent.getChildCount()).isEqualTo(0);
|
||||
assertThat(customContent.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCustomContent_setImageView_oneChild() {
|
||||
final ImageView imageView = mock(ImageView.class);
|
||||
mUsageProgressBarPreference.setCustomContent(imageView);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final FrameLayout customContent =
|
||||
(FrameLayout) mViewHolder.findViewById(R.id.custom_content);
|
||||
assertThat(customContent.getChildCount()).isEqualTo(1);
|
||||
assertThat(customContent.getChildAt(0)).isEqualTo(imageView);
|
||||
assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCustomContent_setImageViewTwice_oneAndLatestChild() {
|
||||
final ImageView imageViewLegacy = mock(ImageView.class);
|
||||
final ImageView imageViewNew = mock(ImageView.class);
|
||||
mUsageProgressBarPreference.setCustomContent(imageViewLegacy);
|
||||
mUsageProgressBarPreference.setCustomContent(imageViewNew);
|
||||
|
||||
mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
final FrameLayout customContent =
|
||||
(FrameLayout) mViewHolder.findViewById(R.id.custom_content);
|
||||
assertThat(customContent.getChildCount()).isEqualTo(1);
|
||||
assertThat(customContent.getChildAt(0)).isEqualTo(imageViewNew);
|
||||
assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
# People who can approve changes for submission
|
||||
arcwang@google.com
|
||||
govenliu@google.com
|
||||
qal@google.com
|
||||
File diff suppressed because it is too large
Load Diff
103
SettingsLib/tests/robotests/Android.bp
Normal file
103
SettingsLib/tests/robotests/Android.bp
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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.
|
||||
|
||||
//###########################################################
|
||||
// SettingsLib Shell app just for Robolectric test target. #
|
||||
//###########################################################
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
android_app {
|
||||
name: "SettingsLibShell",
|
||||
use_resource_processor: true,
|
||||
defaults: ["SettingsLibDefaults"],
|
||||
platform_apis: true,
|
||||
|
||||
privileged: true,
|
||||
|
||||
resource_dirs: ["res"],
|
||||
}
|
||||
|
||||
//###########################################################
|
||||
// SettingsLib Robolectric test target. #
|
||||
//###########################################################
|
||||
android_robolectric_test {
|
||||
name: "SettingsLibRoboTests",
|
||||
srcs: ["src/**/*.java"],
|
||||
static_libs: [
|
||||
"Settings_robolectric_meta_service_file",
|
||||
"Robolectric_shadows_androidx_fragment_upstream",
|
||||
"SettingsLib-robo-testutils",
|
||||
"androidx.fragment_fragment",
|
||||
"androidx.test.core",
|
||||
"androidx.core_core",
|
||||
"flag-junit",
|
||||
"settingslib_media_flags_lib",
|
||||
"testng", // TODO: remove once JUnit on Android provides assertThrows
|
||||
],
|
||||
java_resource_dirs: ["config"],
|
||||
instrumentation_for: "SettingsLibShell",
|
||||
coverage_libs: ["SettingsLib"],
|
||||
test_options: {
|
||||
timeout: 36000,
|
||||
},
|
||||
upstream: true,
|
||||
}
|
||||
|
||||
java_genrule {
|
||||
name: "Settings_robolectric_meta_service_file",
|
||||
out: ["robolectric_meta_service_file.jar"],
|
||||
tools: ["soong_zip"],
|
||||
cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
|
||||
"echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
|
||||
"echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
|
||||
"echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
|
||||
//"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
|
||||
"echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
|
||||
"$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "SettingsLib-robo-testutils",
|
||||
srcs: [
|
||||
"testutils/com/android/settingslib/testutils/**/*.java",
|
||||
],
|
||||
javacflags: [
|
||||
"-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow",
|
||||
"-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
|
||||
// Uncomment the below to debug annotation processors not firing.
|
||||
//"-verbose",
|
||||
//"-XprintRounds",
|
||||
//"-XprintProcessorInfo",
|
||||
//"-Xlint",
|
||||
//"-J-verbose",
|
||||
],
|
||||
plugins: [
|
||||
"auto_value_plugin_1.9",
|
||||
"auto_value_builder_plugin_1.9",
|
||||
"Robolectric_processor_upstream",
|
||||
],
|
||||
libs: [
|
||||
"Robolectric_all-target_upstream",
|
||||
"mockito-robolectric-prebuilt",
|
||||
"truth",
|
||||
],
|
||||
}
|
||||
24
SettingsLib/tests/robotests/AndroidManifest.xml
Normal file
24
SettingsLib/tests/robotests/AndroidManifest.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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"
|
||||
coreApp="true"
|
||||
package="com.android.settingslib.robotests">
|
||||
|
||||
<application/>
|
||||
|
||||
</manifest>
|
||||
2
SettingsLib/tests/robotests/OWNERS
Normal file
2
SettingsLib/tests/robotests/OWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
# We do not guard tests - everyone is welcomed to contribute to tests.
|
||||
per-file *.java=*
|
||||
@@ -0,0 +1,2 @@
|
||||
sdk=NEWEST_SDK
|
||||
instrumentedPackages=androidx.preference
|
||||
40
SettingsLib/tests/robotests/fragment/Android.bp
Normal file
40
SettingsLib/tests/robotests/fragment/Android.bp
Normal file
@@ -0,0 +1,40 @@
|
||||
//#############################################
|
||||
// Compile Robolectric shadows framework misapplied to androidx
|
||||
//#############################################
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "Robolectric_shadows_androidx_fragment_upstream",
|
||||
srcs: [
|
||||
"src/main/java/**/*.java",
|
||||
"src/main/java/**/*.kt",
|
||||
],
|
||||
javacflags: [
|
||||
"-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
|
||||
"-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
|
||||
// Uncomment the below to debug annotation processors not firing.
|
||||
//"-verbose",
|
||||
//"-XprintRounds",
|
||||
//"-XprintProcessorInfo",
|
||||
//"-Xlint",
|
||||
//"-J-verbose",
|
||||
],
|
||||
libs: [
|
||||
"Robolectric_all-target_upstream",
|
||||
"androidx.fragment_fragment",
|
||||
],
|
||||
plugins: [
|
||||
"auto_value_plugin_1.9",
|
||||
"auto_value_builder_plugin_1.9",
|
||||
"Robolectric_processor_upstream",
|
||||
],
|
||||
|
||||
}
|
||||
69
SettingsLib/tests/robotests/fragment/BUILD
Normal file
69
SettingsLib/tests/robotests/fragment/BUILD
Normal file
@@ -0,0 +1,69 @@
|
||||
load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//third_party/java_src/robolectric:license"],
|
||||
default_visibility = ["//third_party/java_src/robolectric:__subpackages__"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
#==============================================================================
|
||||
# Test resources library
|
||||
#==============================================================================
|
||||
jetify_android_library(
|
||||
name = "test_resources",
|
||||
custom_package = "org.robolectric.shadows.androidx.fragment",
|
||||
manifest = "src/test/AndroidManifest.xml",
|
||||
resource_files = glob(
|
||||
["src/test/resources/**/*"],
|
||||
),
|
||||
)
|
||||
|
||||
#==============================================================================
|
||||
# AndroidX fragment module library
|
||||
#==============================================================================
|
||||
jetify_android_library(
|
||||
name = "androidx_fragment",
|
||||
testonly = 1,
|
||||
srcs = glob(
|
||||
["src/main/java/**"],
|
||||
),
|
||||
custom_package = "org.robolectric.shadows.androidx.fragment",
|
||||
javacopts = [
|
||||
"-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
|
||||
],
|
||||
jetify_sources = True,
|
||||
plugins = [
|
||||
"//java/com/google/thirdparty/robolectric/processor",
|
||||
],
|
||||
deps = [
|
||||
"//third_party/java/androidx/core",
|
||||
"//third_party/java/androidx/fragment",
|
||||
"//third_party/java/androidx/lifecycle",
|
||||
"//third_party/java_src/robolectric/shadowapi",
|
||||
"//third_party/java_src/robolectric/shadows/framework",
|
||||
],
|
||||
)
|
||||
|
||||
[
|
||||
jetify_android_local_test(
|
||||
name = "test_" + src.rstrip(".java"),
|
||||
size = "small",
|
||||
srcs = glob(
|
||||
["src/test/java/**/*.java"],
|
||||
),
|
||||
jetify_sources = True,
|
||||
deps = [
|
||||
":androidx_fragment",
|
||||
":test_resources",
|
||||
"//third_party/java/androidx/fragment",
|
||||
"//third_party/java/androidx/loader",
|
||||
"//third_party/java/mockito",
|
||||
"//third_party/java/robolectric",
|
||||
"//third_party/java/truth",
|
||||
],
|
||||
)
|
||||
for src in glob(
|
||||
["src/test/java/**/*Test.java"],
|
||||
)
|
||||
]
|
||||
48
SettingsLib/tests/robotests/fragment/build.gradle
Normal file
48
SettingsLib/tests/robotests/fragment/build.gradle
Normal file
@@ -0,0 +1,48 @@
|
||||
plugins {
|
||||
id "net.ltgt.errorprone" version "0.0.13"
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
res.srcDirs = ['src/test/resources/res']
|
||||
}
|
||||
}
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Project dependencies
|
||||
compileOnly project(":robolectric")
|
||||
|
||||
// Compile dependencies
|
||||
compileOnly AndroidSdk.MAX_SDK.coordinates
|
||||
compileOnly "androidx.core:core:1.0.0-rc02"
|
||||
compileOnly 'androidx.fragment:fragment:1.0.0-rc02'
|
||||
compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
|
||||
compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
|
||||
|
||||
// Testing dependencies
|
||||
testImplementation "com.google.truth:truth:0.44"
|
||||
testImplementation "org.mockito:mockito-core:2.5.4"
|
||||
testImplementation "androidx.arch.core:core-common:2.0.0-beta01"
|
||||
testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01"
|
||||
testImplementation "androidx.collection:collection:1.0.0-rc01"
|
||||
testImplementation "androidx.core:core:1.0.0-rc02"
|
||||
testImplementation 'androidx.fragment:fragment:1.0.0-rc02'
|
||||
testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
|
||||
testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
|
||||
testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
|
||||
testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01"
|
||||
testImplementation "androidx.loader:loader:1.0.0-rc02"
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.android.controller.ComponentController;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */
|
||||
public class FragmentController<F extends Fragment>
|
||||
extends ComponentController<FragmentController<F>, F> {
|
||||
|
||||
private final F mFragment;
|
||||
private final ActivityController<? extends FragmentActivity> mActivityController;
|
||||
|
||||
private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
|
||||
this(fragment, activityClass, null /*intent*/, null /*arguments*/);
|
||||
}
|
||||
|
||||
private FragmentController(
|
||||
F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
|
||||
this(fragment, activityClass, intent, null /*arguments*/);
|
||||
}
|
||||
|
||||
private FragmentController(
|
||||
F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
|
||||
this(fragment, activityClass, null /*intent*/, arguments);
|
||||
}
|
||||
|
||||
private FragmentController(
|
||||
F fragment,
|
||||
Class<? extends FragmentActivity> activityClass,
|
||||
Intent intent,
|
||||
Bundle arguments) {
|
||||
super(fragment, intent);
|
||||
this.mFragment = fragment;
|
||||
if (arguments != null) {
|
||||
this.mFragment.setArguments(arguments);
|
||||
}
|
||||
this.mActivityController =
|
||||
ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(F fragment) {
|
||||
return new FragmentController<>(fragment, FragmentControllerActivity.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment and intent.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param intent the intent which will be retained by activity
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) {
|
||||
return new FragmentController<>(fragment, FragmentControllerActivity.class, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment and arguments.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param arguments the arguments which will be retained by fragment
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) {
|
||||
return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment and activity class.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param activityClass the activity which will be attached by fragment
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(
|
||||
F fragment, Class<? extends FragmentActivity> activityClass) {
|
||||
return new FragmentController<>(fragment, activityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment, intent and arguments.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param intent the intent which will be retained by activity
|
||||
* @param arguments the arguments which will be retained by fragment
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(
|
||||
F fragment, Intent intent, Bundle arguments) {
|
||||
return new FragmentController<>(fragment, FragmentControllerActivity.class, intent,
|
||||
arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment, activity class and intent.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param activityClass the activity which will be attached by fragment
|
||||
* @param intent the intent which will be retained by activity
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(
|
||||
F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
|
||||
return new FragmentController<>(fragment, activityClass, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment, activity class and arguments.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param activityClass the activity which will be attached by fragment
|
||||
* @param arguments the arguments which will be retained by fragment
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(
|
||||
F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
|
||||
return new FragmentController<>(fragment, activityClass, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the {@link FragmentController} for specific fragment, activity class, intent and
|
||||
* arguments.
|
||||
*
|
||||
* @param fragment the fragment which you'd like to drive lifecycle
|
||||
* @param activityClass the activity which will be attached by fragment
|
||||
* @param intent the intent which will be retained by activity
|
||||
* @param arguments the arguments which will be retained by fragment
|
||||
* @return {@link FragmentController}
|
||||
*/
|
||||
public static <F extends Fragment> FragmentController<F> of(
|
||||
F fragment,
|
||||
Class<? extends FragmentActivity> activityClass,
|
||||
Intent intent,
|
||||
Bundle arguments) {
|
||||
return new FragmentController<>(fragment, activityClass, intent, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the given fragment by attaching it to an activity, calling its onCreate() through
|
||||
* onResume() lifecycle methods, and then making it visible. Note that the fragment will be
|
||||
* added
|
||||
* to the view with ID 1.
|
||||
*/
|
||||
public static <F extends Fragment> F setupFragment(F fragment) {
|
||||
return FragmentController.of(fragment).create().start().resume().visible().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the given fragment by attaching it to an activity, calling its onCreate() through
|
||||
* onResume() lifecycle methods, and then making it visible. Note that the fragment will be
|
||||
* added
|
||||
* to the view with ID 1.
|
||||
*/
|
||||
public static <F extends Fragment> F setupFragment(
|
||||
F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
|
||||
return FragmentController.of(fragment, fragmentActivityClass)
|
||||
.create()
|
||||
.start()
|
||||
.resume()
|
||||
.visible()
|
||||
.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the given fragment by attaching it to an activity created with the given bundle,
|
||||
* calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
|
||||
* that the fragment will be added to the view with ID 1.
|
||||
*/
|
||||
public static <F extends Fragment> F setupFragment(
|
||||
F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
|
||||
return FragmentController.of(fragment, fragmentActivityClass)
|
||||
.create(bundle)
|
||||
.start()
|
||||
.resume()
|
||||
.visible()
|
||||
.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the given fragment by attaching it to an activity created with the given bundle and
|
||||
* container id, calling its onCreate() through onResume() lifecycle methods, and then making it
|
||||
* visible.
|
||||
*/
|
||||
public static <F extends Fragment> F setupFragment(
|
||||
F fragment,
|
||||
Class<? extends FragmentActivity> fragmentActivityClass,
|
||||
int containerViewId,
|
||||
Bundle bundle) {
|
||||
return FragmentController.of(fragment, fragmentActivityClass)
|
||||
.create(containerViewId, bundle)
|
||||
.start()
|
||||
.resume()
|
||||
.visible()
|
||||
.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code
|
||||
* contentViewId}.
|
||||
*/
|
||||
public FragmentController<F> create(final int contentViewId, final Bundle bundle) {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController
|
||||
.create(bundle)
|
||||
.get()
|
||||
.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(contentViewId, mFragment)
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
|
||||
* will be added to the view with ID 1.
|
||||
*/
|
||||
public FragmentController<F> create(final Bundle bundle) {
|
||||
return create(1, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link Fragment} in a newly initialized state and hence will receive a null
|
||||
* savedInstanceState {@link Bundle parameter}
|
||||
*/
|
||||
@Override
|
||||
public FragmentController<F> create() {
|
||||
return create(null);
|
||||
}
|
||||
|
||||
/** Drive lifecycle of activity to Start lifetime */
|
||||
public FragmentController<F> start() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.start();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Drive lifecycle of activity to Resume lifetime */
|
||||
public FragmentController<F> resume() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.resume();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Drive lifecycle of activity to Pause lifetime */
|
||||
public FragmentController<F> pause() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.pause();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Drive lifecycle of activity to Stop lifetime */
|
||||
public FragmentController<F> stop() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.stop();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Drive lifecycle of activity to Destroy lifetime */
|
||||
@Override
|
||||
public FragmentController<F> destroy() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.destroy();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Let activity can be visible lifetime */
|
||||
public FragmentController<F> visible() {
|
||||
shadowMainLooper.runPaused(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivityController.visible();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class FragmentControllerActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LinearLayout view = new LinearLayout(this);
|
||||
view.setId(1);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Testing infrastructure for androidx.fragment library.
|
||||
*
|
||||
* <p>To use this in your project, add the artifact {@code
|
||||
* org.robolectric:shadows-androidx-fragment} to your project.
|
||||
*/
|
||||
package org.robolectric.shadows.androidx.fragment;
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.robolectric.shadows.androidx.fragment">
|
||||
|
||||
<uses-sdk android:targetSdkVersion="28"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
|
||||
|
||||
import static android.os.Looper.getMainLooper;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Tests for {@link FragmentController} */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class FragmentControllerTest {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TranscriptFragment.clearLifecycleEvents();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialNotAttached() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment());
|
||||
|
||||
assertThat(controller.get().getView()).isNull();
|
||||
assertThat(controller.get().getActivity()).isNull();
|
||||
assertThat(controller.get().isAdded()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialNotAttached_customActivity() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
assertThat(controller.get().getView()).isNull();
|
||||
assertThat(controller.get().getActivity()).isNull();
|
||||
assertThat(controller.get().isAdded()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attachedAfterCreate() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment());
|
||||
|
||||
controller.create();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attachedAfterCreate_customActivity() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
controller.create();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class);
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attachedAfterCreate_customizedViewId() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class);
|
||||
|
||||
controller.create(R.id.custom_activity_view, null).start();
|
||||
|
||||
assertThat(controller.get().getView()).isNotNull();
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isFalse();
|
||||
assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasViewAfterStart() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment());
|
||||
|
||||
controller.create().start();
|
||||
|
||||
assertThat(controller.get().getView()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isResumed() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
controller.create().start().resume();
|
||||
|
||||
assertThat(controller.get().getView()).isNotNull();
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isTrue();
|
||||
assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPaused() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
controller.create().start().resume().pause();
|
||||
|
||||
assertThat(controller.get().getView()).isNotNull();
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isFalse();
|
||||
assertThat(controller.get().getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume", "onPause")
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isStopped() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
controller.create().start().resume().pause().stop();
|
||||
|
||||
assertThat(controller.get().getView()).isNotNull();
|
||||
assertThat(controller.get().getActivity()).isNotNull();
|
||||
assertThat(controller.get().isAdded()).isTrue();
|
||||
assertThat(controller.get().isResumed()).isFalse();
|
||||
assertThat(controller.get().getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop")
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withIntent() {
|
||||
final Intent intent = generateTestIntent();
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class, intent);
|
||||
|
||||
controller.create();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
final Intent intentInFragment = controller.get().getActivity().getIntent();
|
||||
|
||||
assertThat(intentInFragment.getAction()).isEqualTo("test_action");
|
||||
assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withArguments() {
|
||||
final Bundle bundle = generateTestBundle();
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle);
|
||||
|
||||
controller.create();
|
||||
final Bundle args = controller.get().getArguments();
|
||||
|
||||
assertThat(args.getString("test_key")).isEqualTo("test_value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withIntentAndArguments() {
|
||||
final Bundle bundle = generateTestBundle();
|
||||
final Intent intent = generateTestIntent();
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle);
|
||||
|
||||
controller.create();
|
||||
shadowOf(getMainLooper()).idle();
|
||||
final Intent intentInFragment = controller.get().getActivity().getIntent();
|
||||
final Bundle args = controller.get().getArguments();
|
||||
|
||||
assertThat(intentInFragment.getAction()).isEqualTo("test_action");
|
||||
assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
|
||||
assertThat(args.getString("test_key")).isEqualTo("test_value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void visible() {
|
||||
final FragmentController<TranscriptFragment> controller =
|
||||
FragmentController.of(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
controller.create().start().resume();
|
||||
|
||||
assertThat(controller.get().isVisible()).isFalse();
|
||||
|
||||
controller.visible();
|
||||
|
||||
assertThat(controller.get().isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() {
|
||||
TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment());
|
||||
|
||||
assertThat(fragment.getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume")
|
||||
.inOrder();
|
||||
assertThat(fragment.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() {
|
||||
TranscriptFragment fragment =
|
||||
FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class);
|
||||
|
||||
assertThat(fragment.getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume")
|
||||
.inOrder();
|
||||
assertThat(fragment.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() {
|
||||
Bundle testBundle = generateTestBundle();
|
||||
TranscriptFragment fragment =
|
||||
FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class,
|
||||
testBundle);
|
||||
|
||||
assertThat(fragment.getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume")
|
||||
.inOrder();
|
||||
assertThat(fragment.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() {
|
||||
Bundle testBundle = generateTestBundle();
|
||||
TranscriptFragment fragment =
|
||||
FragmentController.setupFragment(
|
||||
new TranscriptFragment(),
|
||||
CustomizedViewIdTestActivity.class,
|
||||
R.id.custom_activity_view,
|
||||
testBundle);
|
||||
|
||||
assertThat(fragment.getLifecycleEvents())
|
||||
.containsExactly("onCreate", "onStart", "onResume")
|
||||
.inOrder();
|
||||
assertThat(fragment.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
private Intent generateTestIntent() {
|
||||
final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value");
|
||||
return testIntent;
|
||||
}
|
||||
|
||||
private Bundle generateTestBundle() {
|
||||
final Bundle testBundle = new Bundle();
|
||||
testBundle.putString("test_key", "test_value");
|
||||
|
||||
return testBundle;
|
||||
}
|
||||
|
||||
/** A Fragment which can record lifecycle status for test. */
|
||||
public static class TranscriptFragment extends Fragment {
|
||||
|
||||
public static final List<String> sLifecycleEvents = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sLifecycleEvents.add("onCreate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
sLifecycleEvents.add("onStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
sLifecycleEvents.add("onResume");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
sLifecycleEvents.add("onPause");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
sLifecycleEvents.add("onStop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_contents, container, false);
|
||||
}
|
||||
|
||||
public List<String> getLifecycleEvents() {
|
||||
return sLifecycleEvents;
|
||||
}
|
||||
|
||||
public static void clearLifecycleEvents() {
|
||||
sLifecycleEvents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** A Activity which set a default view for test. */
|
||||
public static class TestActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LinearLayout view = new LinearLayout(this);
|
||||
view.setId(1);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
}
|
||||
|
||||
/** A Activity which has a custom view for test. */
|
||||
public static class CustomizedViewIdTestActivity extends FragmentActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.custom_activity_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/custom_activity_view"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tacos"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TACOS"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/burritos"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="BURRITOS"/>
|
||||
|
||||
</LinearLayout>
|
||||
6
SettingsLib/tests/robotests/readme.md
Normal file
6
SettingsLib/tests/robotests/readme.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Unit test suite for SettingsLib using Robolectric.
|
||||
|
||||
```
|
||||
$ croot
|
||||
$ make RunSettingsLibRoboTests -j40
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/id_collapsing_test"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/text_hello_world"
|
||||
android:text="Hello World!"/>
|
||||
|
||||
</com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout>
|
||||
68
SettingsLib/tests/robotests/res/layout/preference_footer.xml
Normal file
68
SettingsLib/tests/robotests/res/layout/preference_footer.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/icon_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="56dp"
|
||||
android:gravity="start|top"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="4dp">
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee" />
|
||||
|
||||
<com.android.settingslib.widget.LinkTextView
|
||||
android:id="@+id/settingslib_learn_more"
|
||||
android:text="@string/settingslib_learn_more_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:visibility="gone"
|
||||
style="@style/TextAppearance.Footer.Title.SettingsLib"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CustomEditTextPreferenceComaptTest {
|
||||
|
||||
@Mock
|
||||
private View mView;
|
||||
|
||||
private TestPreference mPreference;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mPreference = new TestPreference(RuntimeEnvironment.application);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindDialogView_shouldRequestFocus() {
|
||||
final String testText = "";
|
||||
final EditText editText = spy(new EditText(RuntimeEnvironment.application));
|
||||
editText.setText(testText);
|
||||
when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
|
||||
|
||||
mPreference.onBindDialogView(mView);
|
||||
|
||||
verify(editText).requestFocus();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEditText_noDialog_shouldNotCrash() {
|
||||
ReflectionHelpers.setField(mPreference, "mFragment",
|
||||
mock(CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment.class));
|
||||
|
||||
mPreference.getEditText();
|
||||
|
||||
// no crash
|
||||
}
|
||||
|
||||
private static class TestPreference extends CustomEditTextPreferenceCompat {
|
||||
private TestPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CustomEditTextPreferenceTest {
|
||||
|
||||
@Mock
|
||||
private View mView;
|
||||
|
||||
private TestPreference mPreference;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mPreference = new TestPreference(RuntimeEnvironment.application);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindDialogView_shouldRequestFocus() {
|
||||
final String testText = "";
|
||||
final EditText editText = spy(new EditText(RuntimeEnvironment.application));
|
||||
editText.setText(testText);
|
||||
when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
|
||||
|
||||
mPreference.onBindDialogView(mView);
|
||||
|
||||
verify(editText).requestFocus();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEditText_noDialog_shouldNotCrash() {
|
||||
ReflectionHelpers.setField(mPreference, "mFragment",
|
||||
mock(CustomEditTextPreference.CustomPreferenceDialogFragment.class));
|
||||
|
||||
mPreference.getEditText();
|
||||
|
||||
// no crash
|
||||
}
|
||||
|
||||
private static class TestPreference extends CustomEditTextPreference {
|
||||
private TestPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.StructUtsname;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DeviceInfoUtilsTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatKernelVersion_regularInputVersion_shouldStripOptionalValues() {
|
||||
final String sysName = "Linux";
|
||||
final String nodeName = "localhost";
|
||||
final String release = "4.4.88-g134be430baab";
|
||||
final String version = "#1 SMP PREEMPT Tue Dec 31 12:00:00 UTC 2017";
|
||||
final String machine = "aarch64";
|
||||
final StructUtsname uname = new StructUtsname(sysName, nodeName, release, version, machine);
|
||||
|
||||
final String expected = release + "\n" + "#1 Tue Dec 31 12:00:00 UTC 2017";
|
||||
|
||||
assertThat(DeviceInfoUtils.formatKernelVersion(mContext, uname)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatKernelVersion_nonRegularInputVersion_shouldBeUnavailable() {
|
||||
final String sysName = "Linux";
|
||||
final String nodeName = "localhost";
|
||||
final String release = "4.4.88-g134be430baab";
|
||||
final String version = "%@%!asd%#@!$" + "\n " + "fasdfasdfa13ta";
|
||||
final String machine = "aarch64";
|
||||
final StructUtsname uname = new StructUtsname(sysName, nodeName, release, version, machine);
|
||||
|
||||
final String expected = mContext.getString(R.string.status_unavailable);
|
||||
|
||||
assertThat(DeviceInfoUtils.formatKernelVersion(mContext, uname)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatKernelVersion_nullInputVersion_shouldBeUnavailable() {
|
||||
final String expected = mContext.getString(R.string.status_unavailable);
|
||||
|
||||
assertThat(DeviceInfoUtils.formatKernelVersion(mContext, null)).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.settingslib;
|
||||
|
||||
import static com.android.settingslib.HelpUtils.MENU_HELP;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.R;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.provider.Settings;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
/**
|
||||
* Tests for {@link HelpUtils}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HelpUtilsTest {
|
||||
private static final String TEST_HELP_URL = "intent:#Intent;action=com.android.test;end";
|
||||
private static final String PACKAGE_NAME_KEY = "package-name-key";
|
||||
private static final String PACKAGE_NAME_VALUE = "package-name-value";
|
||||
private static final String HELP_INTENT_EXTRA_KEY = "help-intent-extra";
|
||||
private static final String HELP_INTENT_NAME_KEY = "help-intent-name";
|
||||
private static final String FEEDBACK_INTENT_EXTRA_KEY = "feedback-intent-extra";
|
||||
private static final String FEEDBACK_INTENT_NAME_KEY = "feedback-intent-name";
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private Activity mActivity;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mContext.getResources().getString(R.string.config_helpPackageNameKey))
|
||||
.thenReturn(PACKAGE_NAME_KEY);
|
||||
when(mContext.getResources().getString(R.string.config_helpPackageNameValue))
|
||||
.thenReturn(PACKAGE_NAME_VALUE);
|
||||
when(mContext.getResources().getString(R.string.config_helpIntentExtraKey))
|
||||
.thenReturn(HELP_INTENT_EXTRA_KEY);
|
||||
when(mContext.getResources().getString(R.string.config_helpIntentNameKey))
|
||||
.thenReturn(HELP_INTENT_NAME_KEY);
|
||||
when(mContext.getResources().getString(R.string.config_feedbackIntentExtraKey))
|
||||
.thenReturn(FEEDBACK_INTENT_EXTRA_KEY);
|
||||
when(mContext.getResources().getString(R.string.config_feedbackIntentNameKey))
|
||||
.thenReturn(FEEDBACK_INTENT_NAME_KEY);
|
||||
when(mActivity.getPackageManager()).thenReturn(mPackageManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIntentParameters_configTrue_argumentTrue() {
|
||||
when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(true);
|
||||
Intent intent = new Intent();
|
||||
|
||||
HelpUtils.addIntentParameters(
|
||||
mContext, intent, null /* backupContext */, true /* sendPackageName */);
|
||||
|
||||
assertThat(intent.getStringArrayExtra(HELP_INTENT_EXTRA_KEY)).asList()
|
||||
.containsExactly(PACKAGE_NAME_KEY);
|
||||
assertThat(intent.getStringArrayExtra(HELP_INTENT_NAME_KEY)).asList()
|
||||
.containsExactly(PACKAGE_NAME_VALUE);
|
||||
assertThat(intent.getStringArrayExtra(FEEDBACK_INTENT_EXTRA_KEY)).asList()
|
||||
.containsExactly(PACKAGE_NAME_KEY);
|
||||
assertThat(intent.getStringArrayExtra(FEEDBACK_INTENT_NAME_KEY)).asList()
|
||||
.containsExactly(PACKAGE_NAME_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIntentParameters_configTrue_argumentFalse() {
|
||||
when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(true);
|
||||
Intent intent = new Intent();
|
||||
|
||||
HelpUtils.addIntentParameters(
|
||||
mContext, intent, null /* backupContext */, false /* sendPackageName */);
|
||||
|
||||
assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIntentParameters_configFalse_argumentTrue() {
|
||||
when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(false);
|
||||
Intent intent = new Intent();
|
||||
|
||||
HelpUtils.addIntentParameters(
|
||||
mContext, intent, null /* backupContext */, true /* sendPackageName */);
|
||||
|
||||
assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIntentParameters_configFalse_argumentFalse() {
|
||||
when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(false);
|
||||
Intent intent = new Intent();
|
||||
|
||||
HelpUtils.addIntentParameters(
|
||||
mContext, intent, null /* backupContext */, false /* sendPackageName */);
|
||||
|
||||
assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
|
||||
assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareHelpMenuItem_shouldShowIcon() {
|
||||
Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
|
||||
Settings.Global.DEVICE_PROVISIONED, 1);
|
||||
final Resources res = mock(Resources.class);
|
||||
final ResolveInfo resolveInfo = new ResolveInfo();
|
||||
resolveInfo.activityInfo = new ActivityInfo();
|
||||
resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
|
||||
resolveInfo.activityInfo.applicationInfo.packageName = "pkg";
|
||||
resolveInfo.activityInfo.name = "name";
|
||||
final MenuItem item = mock(MenuItem.class);
|
||||
|
||||
|
||||
when(mActivity.getContentResolver())
|
||||
.thenReturn(RuntimeEnvironment.application.getContentResolver());
|
||||
when(mActivity.getResources()).thenReturn(res);
|
||||
when(mActivity.obtainStyledAttributes(any(int[].class)))
|
||||
.thenReturn(mock(TypedArray.class));
|
||||
when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
|
||||
.thenReturn(resolveInfo);
|
||||
|
||||
HelpUtils.prepareHelpMenuItem(mActivity, item, TEST_HELP_URL, "backup_url");
|
||||
|
||||
verify(item).setVisible(true);
|
||||
verify(item).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareHelpMenuItem_noItem_addItem() {
|
||||
final Menu item = mock(Menu.class);
|
||||
when(item.findItem(MENU_HELP)).thenReturn(null);
|
||||
when(item.add(0, MENU_HELP, 0,
|
||||
com.android.settingslib.widget.help.R.string.help_feedback_label)).thenReturn(
|
||||
mock(MenuItem.class));
|
||||
|
||||
HelpUtils.prepareHelpMenuItem(mActivity, item, TEST_HELP_URL, "backup_url");
|
||||
HelpUtils.prepareHelpMenuItem(mActivity, item, 0, "backup_url");
|
||||
|
||||
verify(item, times(2)).add(0, MENU_HELP, 0,
|
||||
com.android.settingslib.widget.help.R.string.help_feedback_label);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareHelpMenuItem_hasItem_notAddItem() {
|
||||
final Menu item = mock(Menu.class);
|
||||
when(item.findItem(MENU_HELP)).thenReturn(mock(MenuItem.class));
|
||||
when(item.add(0, MENU_HELP, 0,
|
||||
com.android.settingslib.widget.help.R.string.help_feedback_label)).thenReturn(
|
||||
mock(MenuItem.class));
|
||||
|
||||
HelpUtils.prepareHelpMenuItem(mActivity, item, TEST_HELP_URL, "backup_url");
|
||||
HelpUtils.prepareHelpMenuItem(mActivity, item, 0, "backup_url");
|
||||
|
||||
verify(item, never()).add(0, MENU_HELP, 0,
|
||||
com.android.settingslib.widget.help.R.string.help_feedback_label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.settingslib.mobile.TelephonyIcons;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MobileNetworkTypeIconsTest {
|
||||
|
||||
@Test
|
||||
public void getNetworkTypeIcon_hPlus_returnsHPlus() {
|
||||
MobileNetworkTypeIcon icon =
|
||||
MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.H_PLUS);
|
||||
|
||||
assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
|
||||
assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_H_PLUS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNetworkTypeIcon_fourG_returnsFourG() {
|
||||
MobileNetworkTypeIcon icon =
|
||||
MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
|
||||
|
||||
assertThat(icon.getName()).isEqualTo(TelephonyIcons.FOUR_G.name);
|
||||
assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNetworkTypeIcon_unknown_returnsUnknown() {
|
||||
SignalIcon.MobileIconGroup unknownGroup = new SignalIcon.MobileIconGroup(
|
||||
"testUnknownNameHere", /* dataContentDesc= */ 45, /* dataType= */ 6);
|
||||
|
||||
MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
|
||||
|
||||
assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
|
||||
assertThat(icon.getIconResId()).isEqualTo(6);
|
||||
assertThat(icon.getContentDescriptionResId()).isEqualTo(45);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.preference.Preference.OnPreferenceChangeListener;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowInteractionJankMonitor.class})
|
||||
public class PrimarySwitchPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private PrimarySwitchPreference mPreference;
|
||||
private PreferenceViewHolder mHolder;
|
||||
private LinearLayout mWidgetView;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPreference = new PrimarySwitchPreference(mContext);
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
|
||||
com.android.settingslib.widget.preference.twotarget.R.layout.preference_two_target,
|
||||
null));
|
||||
mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame);
|
||||
inflater.inflate(androidx.preference.R.layout.preference_widget_switch_compat, mWidgetView,
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_shouldUpdateButtonCheckedState() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
mPreference.setChecked(true);
|
||||
assertThat(toggle.isChecked()).isTrue();
|
||||
|
||||
mPreference.setChecked(false);
|
||||
assertThat(toggle.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSwitchEnabled_shouldUpdateButtonEnabledState() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
mPreference.setSwitchEnabled(true);
|
||||
assertThat(toggle.isEnabled()).isTrue();
|
||||
|
||||
mPreference.setSwitchEnabled(false);
|
||||
assertThat(toggle.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSwitchEnabled_shouldUpdateButtonEnabledState_beforeViewBound() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
|
||||
mPreference.setSwitchEnabled(false);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
assertThat(toggle.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickWidgetView_shouldToggleButton() {
|
||||
assertThat(mWidgetView).isNotNull();
|
||||
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
toggle.performClick();
|
||||
assertThat(toggle.isChecked()).isTrue();
|
||||
|
||||
toggle.performClick();
|
||||
assertThat(toggle.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickWidgetView_shouldNotToggleButtonIfDisabled() {
|
||||
assertThat(mWidgetView).isNotNull();
|
||||
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
toggle.setEnabled(false);
|
||||
|
||||
mWidgetView.performClick();
|
||||
assertThat(toggle.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clickWidgetView_shouldNotifyPreferenceChanged() {
|
||||
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
|
||||
final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
|
||||
mPreference.setOnPreferenceChangeListener(listener);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
mPreference.setChecked(false);
|
||||
toggle.performClick();
|
||||
verify(listener).onPreferenceChange(mPreference, true);
|
||||
|
||||
mPreference.setChecked(true);
|
||||
toggle.performClick();
|
||||
verify(listener).onPreferenceChange(mPreference, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDisabledByAdmin_hasEnforcedAdmin_shouldDisableButton() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
toggle.setEnabled(true);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
mPreference.setDisabledByAdmin(mock(EnforcedAdmin.class));
|
||||
assertThat(toggle.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDisabledByAdmin_noEnforcedAdmin_shouldEnableButton() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
toggle.setEnabled(false);
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
mPreference.setDisabledByAdmin(null);
|
||||
assertThat(toggle.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_toggleButtonShouldHaveContentDescription() {
|
||||
final CompoundButton toggle =
|
||||
(CompoundButton) mHolder.findViewById(androidx.preference.R.id.switchWidget);
|
||||
final String label = "TestButton";
|
||||
mPreference.setTitle(label);
|
||||
|
||||
mPreference.onBindViewHolder(mHolder);
|
||||
|
||||
assertThat(toggle.getContentDescription()).isEqualTo(label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.settingslib;
|
||||
|
||||
import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RestrictedLockUtilsTest {
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private DevicePolicyManager mDevicePolicyManager;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private RestrictedLockUtilsInternal.Proxy mProxy;
|
||||
|
||||
private final int mUserId = 194;
|
||||
private final int mProfileId = 160;
|
||||
private final ComponentName mAdmin1 = new ComponentName("admin1", "admin1class");
|
||||
private final ComponentName mAdmin2 = new ComponentName("admin2", "admin2class");
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
|
||||
.thenReturn(mDevicePolicyManager);
|
||||
when(mContext.getSystemService(DevicePolicyManager.class))
|
||||
.thenReturn(mDevicePolicyManager);
|
||||
when(mContext.getSystemService(Context.USER_SERVICE))
|
||||
.thenReturn(mUserManager);
|
||||
when(mContext.getPackageManager())
|
||||
.thenReturn(mPackageManager);
|
||||
|
||||
RestrictedLockUtilsInternal.sProxy = mProxy;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfRestrictionEnforced_deviceOwner()
|
||||
throws PackageManager.NameNotFoundException {
|
||||
UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
|
||||
UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
|
||||
final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
|
||||
when(mUserManager.getUserRestrictionSources(userRestriction,
|
||||
UserHandle.of(mUserId))).
|
||||
thenReturn(Collections.singletonList(enforcingUser));
|
||||
|
||||
when(mContext.createPackageContextAsUser(any(), eq(0),
|
||||
eq(UserHandle.of(mUserId))))
|
||||
.thenReturn(mContext);
|
||||
|
||||
setUpDeviceOwner(mAdmin1, mUserId);
|
||||
|
||||
EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isNotNull();
|
||||
assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(userRestriction);
|
||||
assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfRestrictionEnforced_profileOwner()
|
||||
throws PackageManager.NameNotFoundException {
|
||||
UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
|
||||
UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
|
||||
final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
|
||||
when(mUserManager.getUserRestrictionSources(userRestriction,
|
||||
UserHandle.of(mUserId))).
|
||||
thenReturn(Collections.singletonList(enforcingUser));
|
||||
|
||||
when(mContext.createPackageContextAsUser(any(), eq(0),
|
||||
eq(UserHandle.of(mUserId))))
|
||||
.thenReturn(mContext);
|
||||
|
||||
setUpProfileOwner(mAdmin1);
|
||||
|
||||
EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isNotNull();
|
||||
assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(userRestriction);
|
||||
assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfDevicePolicyServiceDisabled_noEnforceAdminForManagedProfile() {
|
||||
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(null);
|
||||
final EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfAccountManagementDisabled(mContext, "account_type", mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfDeviceAdminFeatureDisabled_noEnforceAdminForManagedProfile() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
|
||||
.thenReturn(false);
|
||||
final EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfAccountManagementDisabled(mContext, "account_type", mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesDisabled_noEnforcedAdminForManagedProfile() {
|
||||
setUpManagedProfile(mUserId, new ComponentName[] {mAdmin1, mAdmin2});
|
||||
|
||||
final EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfKeyguardFeaturesDisabled(mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesDisabled_oneEnforcedAdminForManagedProfile() {
|
||||
setUpManagedProfile(mUserId, new ComponentName[] {mAdmin1, mAdmin2});
|
||||
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT);
|
||||
|
||||
final EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfKeyguardFeaturesDisabled(mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isEqualTo(new EnforcedAdmin(mAdmin1, UserHandle.of(mUserId)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesDisabled_multipleEnforcedAdminForManagedProfile() {
|
||||
setUpManagedProfile(mUserId, new ComponentName[] {mAdmin1, mAdmin2});
|
||||
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_REMOTE_INPUT);
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin2, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_REMOTE_INPUT);
|
||||
|
||||
final EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
|
||||
.checkIfKeyguardFeaturesDisabled(mContext, KEYGUARD_DISABLE_REMOTE_INPUT, mUserId);
|
||||
|
||||
assertThat(enforcedAdmin).isEqualTo(EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesAreDisabled_doesMatchAllowedFeature_unifiedManagedProfile() {
|
||||
UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
|
||||
UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
|
||||
when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
|
||||
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin2, mProfileId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT);
|
||||
|
||||
// Querying the parent should return the policy, since it affects the parent.
|
||||
EnforcedAdmin parent = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId);
|
||||
assertThat(parent).isEqualTo(new EnforcedAdmin(mAdmin2, UserHandle.of(mProfileId)));
|
||||
|
||||
// Querying the child should return that too.
|
||||
EnforcedAdmin profile = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mProfileId);
|
||||
assertThat(profile).isEqualTo(new EnforcedAdmin(mAdmin2, UserHandle.of(mProfileId)));
|
||||
|
||||
// Querying for some unrelated feature should return nothing. Nothing!
|
||||
assertThat(RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_REMOTE_INPUT, mUserId)).isNull();
|
||||
assertThat(RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_REMOTE_INPUT, mProfileId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesAreDisabled_notMatchOtherFeatures_unifiedManagedProfile() {
|
||||
UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
|
||||
UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
|
||||
when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
|
||||
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin2, mProfileId))
|
||||
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
|
||||
|
||||
// Querying the parent should not return the policy, because it's not a policy that should
|
||||
// affect parents even when the lock screen is unified.
|
||||
EnforcedAdmin primary = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS, mUserId);
|
||||
assertThat(primary).isNull();
|
||||
|
||||
// Querying the child should still return the policy.
|
||||
EnforcedAdmin profile = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS, mProfileId);
|
||||
assertThat(profile).isEqualTo(new EnforcedAdmin(mAdmin2, UserHandle.of(mProfileId)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesAreDisabled_onlyMatchesProfile_separateManagedProfile() {
|
||||
UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
|
||||
UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
|
||||
when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
|
||||
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
|
||||
when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin2, mProfileId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT);
|
||||
|
||||
// Crucially for this test, isSeparateWorkChallengeEnabled => true.
|
||||
doReturn(true).when(mProxy).isSeparateProfileChallengeEnabled(any(), eq(mProfileId));
|
||||
|
||||
// Querying the parent should not return the policy, even though it's shared by default,
|
||||
// because the parent doesn't share a lock screen with the profile any more.
|
||||
EnforcedAdmin parent = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId);
|
||||
assertThat(parent).isNull();
|
||||
|
||||
// Querying the child should still return the policy.
|
||||
EnforcedAdmin profile = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mProfileId);
|
||||
assertThat(profile).isEqualTo(new EnforcedAdmin(mAdmin2, UserHandle.of(mProfileId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test works great. The real world implementation is sketchy though.
|
||||
* <p>
|
||||
* DevicePolicyManager.getParentProfileInstance(UserInfo) does not do what it looks like it does
|
||||
* (which would be to get an instance for the parent of the user that's passed in to it.)
|
||||
* <p>
|
||||
* Instead it just always returns a parent instance for the current user.
|
||||
* <p>
|
||||
* Still, the test works.
|
||||
*/
|
||||
@Test
|
||||
public void checkIfKeyguardFeaturesAreDisabled_onlyMatchesParent_profileParentPolicy() {
|
||||
UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
|
||||
UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
|
||||
when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
|
||||
|
||||
when(mProxy.getParentProfileInstance(any(DevicePolicyManager.class), any())
|
||||
.getKeyguardDisabledFeatures(mAdmin2, mProfileId))
|
||||
.thenReturn(KEYGUARD_DISABLE_FINGERPRINT);
|
||||
|
||||
// Parent should get the policy.
|
||||
EnforcedAdmin parent = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId);
|
||||
assertThat(parent).isEqualTo(new EnforcedAdmin(mAdmin2, UserHandle.of(mProfileId)));
|
||||
|
||||
// Profile should not get the policy.
|
||||
EnforcedAdmin profile = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||
mContext, KEYGUARD_DISABLE_FINGERPRINT, mProfileId);
|
||||
assertThat(profile).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendShowAdminSupportDetailsIntent_extraRestrictionProvided() {
|
||||
EnforcedAdmin enforcedAdmin = new EnforcedAdmin();
|
||||
enforcedAdmin.enforcedRestriction = "Fake";
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, enforcedAdmin);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
|
||||
assertThat(intentCaptor.getValue().getExtra(EXTRA_RESTRICTION)).isEqualTo("Fake");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendShowAdminSupportDetailsIntent_noExtraRestriction() {
|
||||
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, null);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
|
||||
assertThat(intentCaptor.getValue().getExtra(EXTRA_RESTRICTION)).isNull();
|
||||
}
|
||||
|
||||
private UserInfo setUpUser(int userId, ComponentName[] admins) {
|
||||
UserInfo userInfo = new UserInfo(userId, "primary", 0);
|
||||
when(mUserManager.getUserInfo(userId)).thenReturn(userInfo);
|
||||
setUpActiveAdmins(userId, admins);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private UserInfo setUpManagedProfile(int userId, ComponentName[] admins) {
|
||||
UserInfo userInfo = new UserInfo(userId, "profile", UserInfo.FLAG_MANAGED_PROFILE);
|
||||
when(mUserManager.getUserInfo(userId)).thenReturn(userInfo);
|
||||
setUpActiveAdmins(userId, admins);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
private void setUpActiveAdmins(int userId, ComponentName[] activeAdmins) {
|
||||
when(mDevicePolicyManager.getActiveAdminsAsUser(userId))
|
||||
.thenReturn(Arrays.asList(activeAdmins));
|
||||
}
|
||||
|
||||
private void setUpDeviceOwner(ComponentName admin, int userId) {
|
||||
when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(admin);
|
||||
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(UserHandle.of(userId));
|
||||
}
|
||||
|
||||
private void setUpProfileOwner(ComponentName admin) {
|
||||
when(mDevicePolicyManager.getProfileOwner()).thenReturn(admin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyResourcesManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RestrictedPreferenceHelperTest {
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private Preference mPreference;
|
||||
@Mock
|
||||
private DevicePolicyManager mDevicePolicyManager;
|
||||
@Mock
|
||||
private DevicePolicyResourcesManager mDevicePolicyResourcesManager;
|
||||
@Mock
|
||||
private RestrictedTopLevelPreference mRestrictedTopLevelPreference;
|
||||
|
||||
private PreferenceViewHolder mViewHolder;
|
||||
private RestrictedPreferenceHelper mHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mDevicePolicyResourcesManager).when(mDevicePolicyManager)
|
||||
.getResources();
|
||||
doReturn(mDevicePolicyManager).when(mContext)
|
||||
.getSystemService(DevicePolicyManager.class);
|
||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class));
|
||||
mHelper = new RestrictedPreferenceHelper(mContext, mPreference, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindPreference_disabled_shouldDisplayDisabledSummary() {
|
||||
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
|
||||
when(mViewHolder.itemView.findViewById(android.R.id.summary))
|
||||
.thenReturn(summaryView);
|
||||
when(summaryView.getContext().getText(R.string.disabled_by_admin_summary_text))
|
||||
.thenReturn("test");
|
||||
when(mDevicePolicyResourcesManager.getString(any(), any())).thenReturn("test");
|
||||
|
||||
mHelper.useAdminDisabledSummary(true);
|
||||
mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
||||
mHelper.onBindViewHolder(mViewHolder);
|
||||
|
||||
verify(summaryView).setText("test");
|
||||
verify(summaryView, never()).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindPreference_disabledByEcm_shouldDisplayDisabledSummary() {
|
||||
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
|
||||
when(mViewHolder.itemView.findViewById(android.R.id.summary))
|
||||
.thenReturn(summaryView);
|
||||
|
||||
mHelper.setDisabledByEcm(mock(Intent.class));
|
||||
mHelper.onBindViewHolder(mViewHolder);
|
||||
|
||||
verify(mPreference).setSummary(R.string.disabled_by_app_ops_text);
|
||||
verify(summaryView, never()).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindPreference_notDisabled_shouldNotHideSummary() {
|
||||
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
|
||||
when(mViewHolder.itemView.findViewById(android.R.id.summary))
|
||||
.thenReturn(summaryView);
|
||||
when(summaryView.getContext().getText(R.string.disabled_by_admin_summary_text))
|
||||
.thenReturn("test");
|
||||
when(mDevicePolicyResourcesManager.getString(any(), any())).thenReturn("test");
|
||||
when(summaryView.getText()).thenReturn("test");
|
||||
|
||||
mHelper.useAdminDisabledSummary(true);
|
||||
mHelper.setDisabledByAdmin(null);
|
||||
mHelper.onBindViewHolder(mViewHolder);
|
||||
|
||||
verify(summaryView).setText(null);
|
||||
verify(summaryView, never()).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDisabledByAdmin_RestrictedPreference_shouldDisablePreference() {
|
||||
mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
||||
|
||||
verify(mPreference).setEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDisabledByAdmin_TopLevelRestrictedPreference_shouldNotDisablePreference() {
|
||||
mHelper = new RestrictedPreferenceHelper(mContext,
|
||||
mRestrictedTopLevelPreference, /* attrs= */ null);
|
||||
|
||||
mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
||||
|
||||
verify(mRestrictedTopLevelPreference, never()).setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the instance of {@link RestrictedLockUtils.EnforcedAdmin} is received by
|
||||
* {@link RestrictedPreferenceHelper#setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin)} as a
|
||||
* copy or as a reference.
|
||||
*/
|
||||
@Test
|
||||
public void setDisabledByAdmin_disablePreference_receivedEnforcedAdminIsNotAReference() {
|
||||
RestrictedLockUtils.EnforcedAdmin enforcedAdmin =
|
||||
new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
|
||||
/* enforcedRestriction */ "some_restriction",
|
||||
/* userHandle */ null);
|
||||
|
||||
mHelper.setDisabledByAdmin(enforcedAdmin);
|
||||
|
||||
// If `setDisabledByAdmin` stored `enforcedAdmin` as a reference, then the following
|
||||
// assignment would be propagated.
|
||||
enforcedAdmin.enforcedRestriction = null;
|
||||
assertThat(mHelper.mEnforcedAdmin.enforcedRestriction).isEqualTo("some_restriction");
|
||||
|
||||
assertThat(mHelper.isDisabledByAdmin()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RestrictedSwitchPreferenceTest {
|
||||
|
||||
private static final int SIZE = 50;
|
||||
|
||||
private RestrictedSwitchPreference mPreference;
|
||||
private Context mContext;
|
||||
private PreferenceViewHolder mViewHolder;
|
||||
private View mRootView;
|
||||
private ImageView mImageView;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPreference = new RestrictedSwitchPreference(mContext);
|
||||
mRootView = View.inflate(mContext, R.layout.restricted_switch_preference,
|
||||
null /* parent */);
|
||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
|
||||
mImageView = (ImageView) mViewHolder.findViewById(android.R.id.icon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_setIconSize_shouldHaveCorrectLayoutParam() {
|
||||
mPreference.setIconSize(SIZE);
|
||||
|
||||
mPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
assertThat(mImageView.getLayoutParams().height).isEqualTo(SIZE);
|
||||
assertThat(mImageView.getLayoutParams().width).isEqualTo(SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_notSetIconSize_shouldHaveCorrectLayoutParam() {
|
||||
mPreference.onBindViewHolder(mViewHolder);
|
||||
|
||||
assertThat(mImageView.getLayoutParams().height).isEqualTo(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
assertThat(mImageView.getLayoutParams().width).isEqualTo(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RestrictedTopLevelPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private RestrictedTopLevelPreference mPreference;
|
||||
private RestrictedPreferenceHelper mHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPreference = spy(new RestrictedTopLevelPreference(mContext));
|
||||
mHelper = spy(new RestrictedPreferenceHelper(mContext, mPreference, null));
|
||||
ReflectionHelpers.setField(mPreference, "mHelper", mHelper);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_disabledByAdmin_shouldCallSetDisabledByAdmin() {
|
||||
when(mHelper.isDisabledByAdmin()).thenReturn(true);
|
||||
|
||||
mPreference.setEnabled(true);
|
||||
|
||||
verify(mHelper).setDisabledByAdmin(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_notDisabledByAdmin_shouldNotCallSetDisabledByAdmin() {
|
||||
when(mHelper.isDisabledByAdmin()).thenReturn(false);
|
||||
|
||||
mPreference.setEnabled(true);
|
||||
|
||||
verify(mHelper, never()).setDisabledByAdmin(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDisabledByAdmin_shouldNotCallSetEnabled() {
|
||||
mPreference.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
||||
|
||||
verify(mPreference, never()).setEnabled(anyBoolean());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class TetherUtilTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mConnectivityManager)
|
||||
.when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
doReturn(mUserManager)
|
||||
.when(mContext).getSystemService(Context.USER_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_supported_configDisallowed_hasUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(true, true, true);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_notSupported_configDisallowed_hasUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(false, true, true);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_supported_configAllowed_hasUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(true, false, true);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_notSupported_configAllowed_hasUserRestriction_returnFalse() {
|
||||
setupIsTetherAvailable(false, false, true);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_supported_configDisallowed_noUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(true, true, false);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_notSupported_configDisallowed_noUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(false, true, false);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_supported_configAllowed_noUserRestriction_returnTrue() {
|
||||
setupIsTetherAvailable(true, false, false);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTetherAvailable_notSupported_configAllowed_noUserRestriction_returnFalse() {
|
||||
setupIsTetherAvailable(false, false, false);
|
||||
|
||||
assertThat(TetherUtil.isTetherAvailable(mContext)).isFalse();
|
||||
}
|
||||
|
||||
private void setupIsTetherAvailable(boolean tetherSupported, boolean configAllowed,
|
||||
boolean hasBseUserRestriction) {
|
||||
when(mConnectivityManager.isTetheringSupported()).thenReturn(tetherSupported);
|
||||
|
||||
// For RestrictedLockUtils.checkIfRestrictionEnforced
|
||||
final int userId = UserHandle.myUserId();
|
||||
List<UserManager.EnforcingUser> enforcingUsers = new ArrayList<>();
|
||||
if (configAllowed) {
|
||||
// Add two enforcing users so that RestrictedLockUtils.checkIfRestrictionEnforced
|
||||
// returns non-null
|
||||
enforcingUsers.add(new UserManager.EnforcingUser(userId,
|
||||
UserManager.RESTRICTION_SOURCE_DEVICE_OWNER));
|
||||
enforcingUsers.add(new UserManager.EnforcingUser(userId,
|
||||
UserManager.RESTRICTION_SOURCE_PROFILE_OWNER));
|
||||
}
|
||||
when(mUserManager.getUserRestrictionSources(
|
||||
UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId)))
|
||||
.thenReturn(enforcingUsers);
|
||||
|
||||
// For RestrictedLockUtils.hasBaseUserRestriction
|
||||
when(mUserManager.hasBaseUserRestriction(
|
||||
UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId)))
|
||||
.thenReturn(hasBseUserRestriction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,654 @@
|
||||
/*
|
||||
* 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.settingslib;
|
||||
|
||||
import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
|
||||
import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
|
||||
import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
|
||||
import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.hardware.usb.UsbPort;
|
||||
import android.hardware.usb.UsbPortStatus;
|
||||
import android.hardware.usb.flags.Flags;
|
||||
import android.location.LocationManager;
|
||||
import android.media.AudioManager;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.AccessNetworkConstants;
|
||||
import android.telephony.NetworkRegistrationInfo;
|
||||
import android.telephony.ServiceState;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadows.ShadowSettings;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {UtilsTest.ShadowLocationManager.class})
|
||||
public class UtilsTest {
|
||||
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
|
||||
private static final String TAG = "UtilsTest";
|
||||
private static final String PERCENTAGE_0 = "0%";
|
||||
private static final String PERCENTAGE_1 = "1%";
|
||||
private static final String PERCENTAGE_49 = "49%";
|
||||
private static final String PERCENTAGE_50 = "50%";
|
||||
private static final String PERCENTAGE_100 = "100%";
|
||||
private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
|
||||
|
||||
private AudioManager mAudioManager;
|
||||
private Context mContext;
|
||||
@Mock private LocationManager mLocationManager;
|
||||
@Mock private ServiceState mServiceState;
|
||||
@Mock private NetworkRegistrationInfo mNetworkRegistrationInfo;
|
||||
@Mock private UsbPort mUsbPort;
|
||||
@Mock private UsbManager mUsbManager;
|
||||
@Mock private UsbPortStatus mUsbPortStatus;
|
||||
|
||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
|
||||
when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
|
||||
ShadowSettings.ShadowSecure.reset();
|
||||
mAudioManager = mContext.getSystemService(AudioManager.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void reset() {
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLocationEnabled() {
|
||||
int currentUserId = ActivityManager.getCurrentUser();
|
||||
Utils.updateLocationEnabled(
|
||||
mContext, true, currentUserId, Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
|
||||
|
||||
assertThat(
|
||||
Settings.Secure.getInt(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Secure.LOCATION_CHANGER,
|
||||
Settings.Secure.LOCATION_CHANGER_UNKNOWN))
|
||||
.isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
|
||||
final String[] expectedPercentages = {
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_1,
|
||||
PERCENTAGE_1,
|
||||
PERCENTAGE_49,
|
||||
PERCENTAGE_49,
|
||||
PERCENTAGE_50,
|
||||
PERCENTAGE_50,
|
||||
PERCENTAGE_100
|
||||
};
|
||||
|
||||
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
|
||||
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
|
||||
assertThat(percentage).isEqualTo(expectedPercentages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatPercentage_RoundFalse_NoRound() {
|
||||
final String[] expectedPercentages = {
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_0,
|
||||
PERCENTAGE_49,
|
||||
PERCENTAGE_49,
|
||||
PERCENTAGE_49,
|
||||
PERCENTAGE_50,
|
||||
PERCENTAGE_100
|
||||
};
|
||||
|
||||
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
|
||||
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
|
||||
assertThat(percentage).isEqualTo(expectedPercentages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
|
||||
Resources resources = mock(Resources.class);
|
||||
when(resources.getInteger(
|
||||
eq(
|
||||
com.android.internal.R.integer
|
||||
.config_storageManagerDaystoRetainDefault)))
|
||||
.thenReturn(60);
|
||||
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsStorageManagerEnabled_UsesSystemProperties() {
|
||||
SystemProperties.set(STORAGE_MANAGER_ENABLED_PROPERTY, "true");
|
||||
assertThat(Utils.isStorageManagerEnabled(mContext)).isTrue();
|
||||
}
|
||||
|
||||
private static ArgumentMatcher<Intent> actionMatches(String expected) {
|
||||
return intent -> TextUtils.equals(expected, intent.getAction());
|
||||
}
|
||||
|
||||
@Implements(value = LocationManager.class)
|
||||
public static class ShadowLocationManager {
|
||||
|
||||
@Implementation
|
||||
public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAudioModeOngoingCall_modeInCommunication_returnTrue() {
|
||||
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
|
||||
assertThat(Utils.isAudioModeOngoingCall(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAudioModeOngoingCall_modeInCall_returnTrue() {
|
||||
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
|
||||
|
||||
assertThat(Utils.isAudioModeOngoingCall(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAudioModeOngoingCall_modeRingtone_returnTrue() {
|
||||
mAudioManager.setMode(AudioManager.MODE_RINGTONE);
|
||||
|
||||
assertThat(Utils.isAudioModeOngoingCall(mContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAudioModeOngoingCall_modeNormal_returnFalse() {
|
||||
mAudioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
|
||||
assertThat(Utils.isAudioModeOngoingCall(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_servicestateNull_returnFalse() {
|
||||
assertThat(Utils.isInService(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_voiceInService_returnTrue() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
|
||||
.thenReturn(mNetworkRegistrationInfo);
|
||||
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
|
||||
.thenReturn(mNetworkRegistrationInfo);
|
||||
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
|
||||
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_voiceOutOfServiceDataNull_returnFalse() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
|
||||
.thenReturn(null);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
|
||||
.thenReturn(mNetworkRegistrationInfo);
|
||||
when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInService_ServiceStatePowerOff_returnFalse() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
|
||||
|
||||
assertThat(Utils.isInService(mServiceState)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_servicestateNull_returnOutOfService() {
|
||||
assertThat(Utils.getCombinedServiceState(null))
|
||||
.isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
|
||||
|
||||
assertThat(Utils.getCombinedServiceState(mServiceState))
|
||||
.isEqualTo(ServiceState.STATE_POWER_OFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_voiceInService_returnInService() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
|
||||
|
||||
assertThat(Utils.getCombinedServiceState(mServiceState))
|
||||
.isEqualTo(ServiceState.STATE_IN_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
|
||||
.thenReturn(mNetworkRegistrationInfo);
|
||||
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
|
||||
|
||||
assertThat(Utils.getCombinedServiceState(mServiceState))
|
||||
.isEqualTo(ServiceState.STATE_IN_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getNetworkRegistrationInfo(
|
||||
NetworkRegistrationInfo.DOMAIN_PS,
|
||||
AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
|
||||
.thenReturn(mNetworkRegistrationInfo);
|
||||
when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
|
||||
|
||||
assertThat(Utils.getCombinedServiceState(mServiceState))
|
||||
.isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
|
||||
when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
when(mServiceState.getDataRegistrationState())
|
||||
.thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
|
||||
assertThat(Utils.getCombinedServiceState(mServiceState))
|
||||
.isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_statusIsFull_returnFullString() {
|
||||
final Intent intent =
|
||||
new Intent()
|
||||
.putExtra(BatteryManager.EXTRA_LEVEL, 100)
|
||||
.putExtra(BatteryManager.EXTRA_SCALE, 100);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_full));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
|
||||
final Intent intent =
|
||||
new Intent()
|
||||
.putExtra(BatteryManager.EXTRA_LEVEL, 100)
|
||||
.putExtra(BatteryManager.EXTRA_SCALE, 100);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_batteryLevelIs100_returnFullString() {
|
||||
final Intent intent =
|
||||
new Intent()
|
||||
.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_full));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
|
||||
final Intent intent =
|
||||
new Intent()
|
||||
.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_batteryLevel99_returnChargingString() {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_charging));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_chargingDock_returnDockChargingString() {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_charging_dock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_chargingWireless_returnWirelessChargingString() {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_charging_wireless));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_chargingAndUseCompactStatus_returnCompactString() {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_charging));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBatteryStatus_chargingWirelessAndUseCompactStatus_returnCompactString() {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
|
||||
final Resources resources = mContext.getResources();
|
||||
|
||||
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
|
||||
.isEqualTo(resources.getString(R.string.battery_info_status_charging));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_nullPorts_returnFalse() {
|
||||
when(mUsbManager.getPorts()).thenReturn(null);
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_emptyPorts_returnFalse() {
|
||||
when(mUsbManager.getPorts()).thenReturn(new ArrayList<>());
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_nullPortStatus_returnFalse() {
|
||||
final List<UsbPort> usbPorts = new ArrayList<>();
|
||||
usbPorts.add(mUsbPort);
|
||||
when(mUsbManager.getPorts()).thenReturn(usbPorts);
|
||||
when(mUsbPort.getStatus()).thenReturn(null);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_complianeWarningOther_returnTrue_flagDisabled() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_complianeWarningPower_returnFalse_flagDisabled() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_complianeWarningOther_returnFalse_flagEnabled() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_complianeWarningPower_returnTrue_flagEnabled() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING);
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INPUT_POWER_LIMITED_WARNING);
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_complianeWarningDebug_returnTrue() {
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_unexpectedWarningType_returnFalse() {
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_BC_1_2);
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
|
||||
setupIncompatibleCharging();
|
||||
when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_notSupportComplianceWarnings_returnFalse() {
|
||||
setupIncompatibleCharging();
|
||||
when(mUsbPort.supportsComplianceWarnings()).thenReturn(false);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_usbNotConnected_returnFalse() {
|
||||
setupIncompatibleCharging();
|
||||
when(mUsbPortStatus.isConnected()).thenReturn(false);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsIncompatibleChargers_disableWarning_returnFalse() {
|
||||
setupIncompatibleCharging();
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
|
||||
|
||||
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
|
||||
updateWirelessChargingNotificationTimestamp(
|
||||
mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
|
||||
|
||||
assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
|
||||
updateWirelessChargingNotificationTimestamp(
|
||||
mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
|
||||
|
||||
Utils.shouldShowWirelessChargingNotification(mContext, TAG);
|
||||
|
||||
assertThat(getWirelessChargingNotificationTimestamp())
|
||||
.isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
|
||||
assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
|
||||
updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
|
||||
|
||||
assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
|
||||
updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
|
||||
|
||||
assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
|
||||
final long monthAgo = Duration.ofDays(31).toMillis();
|
||||
final long timestamp = CURRENT_TIMESTAMP - monthAgo;
|
||||
updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
|
||||
|
||||
assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
|
||||
final long monthAgo = Duration.ofDays(31).toMillis();
|
||||
final long timestamp = CURRENT_TIMESTAMP - monthAgo;
|
||||
updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
|
||||
|
||||
Utils.shouldShowWirelessChargingNotification(mContext, TAG);
|
||||
|
||||
assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
|
||||
assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
|
||||
updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
|
||||
|
||||
assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
|
||||
updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
|
||||
|
||||
assertThat(getWirelessChargingNotificationTimestamp())
|
||||
.isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
|
||||
assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
|
||||
Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
|
||||
|
||||
assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowWirelessChargingWarningTip_disabled_returnFalse() {
|
||||
Utils.updateWirelessChargingWarningEnabled(mContext, false, TAG);
|
||||
|
||||
assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isFalse();
|
||||
}
|
||||
|
||||
private void setupIncompatibleCharging() {
|
||||
setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
|
||||
}
|
||||
|
||||
private void setupIncompatibleCharging(int complianceWarningType) {
|
||||
final List<UsbPort> usbPorts = new ArrayList<>();
|
||||
usbPorts.add(mUsbPort);
|
||||
when(mUsbManager.getPorts()).thenReturn(usbPorts);
|
||||
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
|
||||
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
|
||||
when(mUsbPortStatus.isConnected()).thenReturn(true);
|
||||
when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
|
||||
}
|
||||
|
||||
private long getWirelessChargingNotificationTimestamp() {
|
||||
return Settings.Secure.getLong(
|
||||
mContext.getContentResolver(),
|
||||
Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
|
||||
WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.accessibility;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowSecure;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowSecure.class})
|
||||
public class AccessibilityUtilsTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEnabledServicesFromSettings_noService_emptyResult() {
|
||||
assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEnabledServicesFromSettings_badFormat_emptyResult() {
|
||||
ShadowSecure.putStringForUser(
|
||||
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
||||
":",
|
||||
UserHandle.myUserId());
|
||||
|
||||
assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEnabledServicesFromSettings_1Service_1result() {
|
||||
final ComponentName cn = new ComponentName("pkg", "serv");
|
||||
ShadowSecure.putStringForUser(
|
||||
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
||||
cn.flattenToString() + ":",
|
||||
UserHandle.myUserId());
|
||||
|
||||
assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
|
||||
.containsExactly(cn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEnabledServicesFromSettings_2Services_2results() {
|
||||
final ComponentName cn1 = new ComponentName("pkg", "serv");
|
||||
final ComponentName cn2 = new ComponentName("pkg", "serv2");
|
||||
ShadowSecure.putStringForUser(
|
||||
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
||||
cn1.flattenToString() + ":" + cn2.flattenToString(),
|
||||
UserHandle.myUserId());
|
||||
|
||||
assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
|
||||
.containsExactly(cn1, cn2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AppIconCacheManagerTest {
|
||||
|
||||
private static final String APP_PACKAGE_NAME = "com.test.app";
|
||||
private static final String APP_PACKAGE_NAME1 = "com.test.app1";
|
||||
private static final String APP_PACKAGE_NAME2 = "com.test.app2";
|
||||
private static final String APP_PACKAGE_NAME3 = "com.test.app3";
|
||||
private static final int APP_UID = 9999;
|
||||
|
||||
@Mock
|
||||
private Drawable mIcon;
|
||||
|
||||
@Mock
|
||||
private Drawable mIcon1;
|
||||
@Mock
|
||||
private Drawable mIcon2;
|
||||
@Mock
|
||||
private Drawable mIcon3;
|
||||
|
||||
private AppIconCacheManager mAppIconCacheManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mAppIconCacheManager = AppIconCacheManager.getInstance();
|
||||
doReturn(10).when(mIcon).getIntrinsicHeight();
|
||||
doReturn(10).when(mIcon).getIntrinsicWidth();
|
||||
doReturn(mIcon).when(mIcon).mutate();
|
||||
|
||||
// Algorithm for trim memory test:
|
||||
// The real maxsize is defined by AppIconCacheManager.MAX_CACHE_SIZE_IN_KB, and the size
|
||||
// of each element is calculated as following:
|
||||
// n * n * 4 / 1024
|
||||
// In the testcase, we want to mock the maxsize of LruCache is 3, so the formula calculating
|
||||
// the size of each element will be like:
|
||||
// n * n * 4 / 1024 = maxsize / 3
|
||||
// Thus, n = square_root(maxsize / 3 * 1024 / 4), which can be used as an icon size.
|
||||
final int iconSize =
|
||||
(int) Math.sqrt(AppIconCacheManager.MAX_CACHE_SIZE_IN_KB / 3f * 1024f / 4f);
|
||||
|
||||
doReturn(iconSize).when(mIcon1).getIntrinsicHeight();
|
||||
doReturn(iconSize).when(mIcon1).getIntrinsicWidth();
|
||||
doReturn(mIcon1).when(mIcon1).mutate();
|
||||
|
||||
doReturn(iconSize).when(mIcon2).getIntrinsicHeight();
|
||||
doReturn(iconSize).when(mIcon2).getIntrinsicWidth();
|
||||
doReturn(mIcon2).when(mIcon2).mutate();
|
||||
|
||||
doReturn(iconSize).when(mIcon3).getIntrinsicHeight();
|
||||
doReturn(iconSize).when(mIcon3).getIntrinsicWidth();
|
||||
doReturn(mIcon3).when(mIcon3).mutate();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
AppIconCacheManager.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get_invalidPackageOrUid_shouldReturnNull() {
|
||||
assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidPackageOrUid_shouldNotCrash() {
|
||||
mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidIcon_shouldNotCacheIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_invalidIconSize_shouldNotCacheIcon() {
|
||||
doReturn(-1).when(mIcon).getIntrinsicHeight();
|
||||
doReturn(-1).when(mIcon).getIntrinsicWidth();
|
||||
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_shouldCacheIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_noInstance_shouldNotCrash() {
|
||||
mAppIconCacheManager = null;
|
||||
|
||||
AppIconCacheManager.release();
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release_existInstance_shouldClearCache() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
AppIconCacheManager.release();
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimMemory_levelSatisfied_shouldNotCacheIcon() {
|
||||
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
|
||||
|
||||
// Expected to trim size to 0
|
||||
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
|
||||
mAppIconCacheManager.trimMemory(level);
|
||||
|
||||
// None of the elements should be cached
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNull();
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimMemory_levelSatisfied_shouldCacheAtLeastHalf() {
|
||||
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
|
||||
|
||||
// Get the last element
|
||||
mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID);
|
||||
|
||||
// Expected to trim size to half of it, which is int( 3 / 2 ) = 1
|
||||
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
|
||||
mAppIconCacheManager.trimMemory(level);
|
||||
|
||||
// There should be only one cached element, which is the last recently used one
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNotNull();
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.applications;
|
||||
|
||||
import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Environment;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AppUtilsTest {
|
||||
|
||||
private static final String APP_PACKAGE_NAME = "com.test.app";
|
||||
private static final int APP_UID = 9999;
|
||||
|
||||
@Mock
|
||||
private Drawable mIcon;
|
||||
|
||||
private Context mContext;
|
||||
private AppIconCacheManager mAppIconCacheManager;
|
||||
private ApplicationInfo mAppInfo;
|
||||
private ApplicationsState.AppEntry mAppEntry;
|
||||
private ArrayList<ApplicationsState.AppEntry> mAppEntries;
|
||||
private ShadowPackageManager mShadowPackageManager;
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mAppIconCacheManager = AppIconCacheManager.getInstance();
|
||||
mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
|
||||
mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
|
||||
mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
|
||||
doReturn(mIcon).when(mIcon).mutate();
|
||||
mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
AppIconCacheManager.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_nullAppEntry_shouldReturnNull() {
|
||||
assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = ShadowUtils.class)
|
||||
public void getIcon_noCachedIcon_shouldNotReturnNull() {
|
||||
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconFromCache_nullAppEntry_shouldReturnNull() {
|
||||
assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconFromCache_shouldReturnCachedIcon() {
|
||||
mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
|
||||
|
||||
assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
|
||||
AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
|
||||
// no crash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
|
||||
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
|
||||
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = ShadowUtils.class)
|
||||
public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
|
||||
AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
|
||||
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAppInstalled_noAppEntry_shouldReturnFalse() {
|
||||
assertThat(AppUtils.isAppInstalled(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAppInstalled_hasAppEntryWithInstalledFlag_shouldReturnTrue() {
|
||||
final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
|
||||
appEntry.info = new ApplicationInfo();
|
||||
appEntry.info.flags = ApplicationInfo.FLAG_INSTALLED;
|
||||
|
||||
assertThat(AppUtils.isAppInstalled(appEntry)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAppInstalled_hasAppEntryWithoutInstalledFlag_shouldReturnFalse() {
|
||||
final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
|
||||
appEntry.info = new ApplicationInfo();
|
||||
|
||||
assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
|
||||
mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
|
||||
PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.packageName = APP_PACKAGE_NAME;
|
||||
packageInfo.setApexPackageName("com.test.apex.package");
|
||||
mShadowPackageManager.installPackage(packageInfo);
|
||||
|
||||
assertThat(
|
||||
AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
|
||||
mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
|
||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
|
||||
PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.packageName = APP_PACKAGE_NAME;
|
||||
packageInfo.applicationInfo = applicationInfo;
|
||||
mShadowPackageManager.installPackage(packageInfo);
|
||||
|
||||
assertThat(
|
||||
AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
|
||||
}
|
||||
|
||||
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
|
||||
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
|
||||
appEntry.label = "label";
|
||||
appEntry.mounted = true;
|
||||
final File apkFile = mock(File.class);
|
||||
doReturn(true).when(apkFile).exists();
|
||||
try {
|
||||
Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
|
||||
field.setAccessible(true);
|
||||
field.set(appEntry, apkFile);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
fail("Not able to mock apkFile: " + e);
|
||||
}
|
||||
return appEntry;
|
||||
}
|
||||
|
||||
private ApplicationInfo createApplicationInfo(String packageName, int uid) {
|
||||
ApplicationInfo appInfo = new ApplicationInfo();
|
||||
appInfo.sourceDir = "appPath";
|
||||
appInfo.packageName = packageName;
|
||||
appInfo.uid = uid;
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
@Implements(Utils.class)
|
||||
private static class ShadowUtils {
|
||||
@Implementation
|
||||
public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
|
||||
final Drawable icon = mock(Drawable.class);
|
||||
doReturn(10).when(icon).getIntrinsicHeight();
|
||||
doReturn(10).when(icon).getIntrinsicWidth();
|
||||
doReturn(icon).when(icon).mutate();
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,863 @@
|
||||
/*
|
||||
* 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.settingslib.applications;
|
||||
|
||||
import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
|
||||
import static android.os.UserHandle.MU_ENABLED;
|
||||
import static android.os.UserHandle.USER_SYSTEM;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.shadow.api.Shadow.extract;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Application;
|
||||
import android.app.ApplicationPackageManager;
|
||||
import android.app.usage.StorageStats;
|
||||
import android.app.usage.StorageStatsManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.ModuleInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserProperties;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.text.TextUtils;
|
||||
import android.util.IconDrawableFactory;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.applications.ApplicationsState.Callbacks;
|
||||
import com.android.settingslib.applications.ApplicationsState.Session;
|
||||
import com.android.settingslib.testutils.shadow.ShadowUserManager;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
import org.robolectric.shadows.ShadowContextImpl;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowUserManager.class,
|
||||
ApplicationsStateRoboTest.ShadowIconDrawableFactory.class,
|
||||
ApplicationsStateRoboTest.ShadowPackageManager.class})
|
||||
public class ApplicationsStateRoboTest {
|
||||
|
||||
private final static String HOME_PACKAGE_NAME = "com.android.home";
|
||||
private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable";
|
||||
|
||||
private static final int PROFILE_USERID = 10;
|
||||
private static final int PROFILE_USERID2 = 11;
|
||||
|
||||
private static final String PKG_1 = "PKG1";
|
||||
private static final int OWNER_UID_1 = 1001;
|
||||
private static final int PROFILE_UID_1 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_1);
|
||||
|
||||
private static final String PKG_2 = "PKG2";
|
||||
private static final int OWNER_UID_2 = 1002;
|
||||
private static final int PROFILE_UID_2 = UserHandle.getUid(PROFILE_USERID, OWNER_UID_2);
|
||||
|
||||
private static final String PKG_3 = "PKG3";
|
||||
private static final int OWNER_UID_3 = 1003;
|
||||
private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3);
|
||||
|
||||
private static final String CLONE_USER = "clone_user";
|
||||
private static final String RANDOM_USER = "random_user";
|
||||
|
||||
/** Class under test */
|
||||
private ApplicationsState mApplicationsState;
|
||||
private Session mSession;
|
||||
|
||||
private Application mApplication;
|
||||
|
||||
@Spy
|
||||
Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock
|
||||
private Callbacks mCallbacks;
|
||||
@Captor
|
||||
private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor;
|
||||
@Mock
|
||||
private StorageStatsManager mStorageStatsManager;
|
||||
@Mock
|
||||
private IPackageManager mPackageManagerService;
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Implements(value = IconDrawableFactory.class)
|
||||
public static class ShadowIconDrawableFactory {
|
||||
|
||||
@Implementation
|
||||
protected Drawable getBadgedIcon(ApplicationInfo appInfo) {
|
||||
return new ColorDrawable(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Implements(value = ApplicationPackageManager.class)
|
||||
public static class ShadowPackageManager extends
|
||||
org.robolectric.shadows.ShadowApplicationPackageManager {
|
||||
|
||||
// test installed modules, 2 regular, 2 hidden
|
||||
private final String[] mModuleNames = {
|
||||
"test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"};
|
||||
private final List<ModuleInfo> mInstalledModules = new ArrayList<>();
|
||||
|
||||
@Implementation
|
||||
protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
|
||||
ResolveInfo resolveInfo = new ResolveInfo();
|
||||
resolveInfo.activityInfo = new ActivityInfo();
|
||||
resolveInfo.activityInfo.packageName = HOME_PACKAGE_NAME;
|
||||
resolveInfo.activityInfo.enabled = true;
|
||||
outActivities.add(resolveInfo);
|
||||
return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo");
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public List<ModuleInfo> getInstalledModules(int flags) {
|
||||
if (mInstalledModules.isEmpty()) {
|
||||
for (String moduleName : mModuleNames) {
|
||||
mInstalledModules.add(
|
||||
createModuleInfo(moduleName,
|
||||
TextUtils.concat(moduleName, ".apex").toString()));
|
||||
}
|
||||
}
|
||||
return mInstalledModules;
|
||||
}
|
||||
|
||||
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
|
||||
@PackageManager.ResolveInfoFlagsBits int flags, @UserIdInt int userId) {
|
||||
List<ResolveInfo> resolveInfos = new ArrayList<>();
|
||||
ResolveInfo resolveInfo = new ResolveInfo();
|
||||
resolveInfo.activityInfo = new ActivityInfo();
|
||||
resolveInfo.activityInfo.packageName = LAUNCHABLE_PACKAGE_NAME;
|
||||
resolveInfo.activityInfo.enabled = true;
|
||||
resolveInfo.filter = new IntentFilter();
|
||||
resolveInfo.filter.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
resolveInfos.add(resolveInfo);
|
||||
return resolveInfos;
|
||||
}
|
||||
|
||||
private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
|
||||
final ModuleInfo info = new ModuleInfo();
|
||||
info.setName(packageName);
|
||||
info.setPackageName(packageName);
|
||||
info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
|
||||
// will treat any app with package name that contains "hidden" as hidden module
|
||||
info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
// Robolectric does not know about the StorageStatsManager as a system service.
|
||||
// Registering a mock of this service as a replacement.
|
||||
ShadowContextImpl shadowContext = Shadow.extract(
|
||||
RuntimeEnvironment.application.getBaseContext());
|
||||
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
|
||||
mApplication = spy(RuntimeEnvironment.application);
|
||||
StorageStats storageStats = new StorageStats();
|
||||
storageStats.codeBytes = 10;
|
||||
storageStats.cacheBytes = 30;
|
||||
// Data bytes are a superset of cache bytes.
|
||||
storageStats.dataBytes = storageStats.cacheBytes + 20;
|
||||
when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
|
||||
anyString(), any(UserHandle.class))).thenReturn(storageStats);
|
||||
|
||||
// Set up 3 installed apps, in which 1 is hidden module
|
||||
final List<ApplicationInfo> infos = new ArrayList<>();
|
||||
infos.add(createApplicationInfo("test.package.1"));
|
||||
infos.add(createApplicationInfo("test.hidden.module.2"));
|
||||
infos.add(createApplicationInfo("test.package.3"));
|
||||
when(mPackageManagerService.getInstalledApplications(
|
||||
anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
|
||||
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
|
||||
mApplicationsState.clearEntries();
|
||||
|
||||
mSession = mApplicationsState.newSession(mCallbacks);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mSession.onDestroy();
|
||||
}
|
||||
|
||||
private ApplicationInfo createApplicationInfo(String packageName) {
|
||||
return createApplicationInfo(packageName, 0);
|
||||
}
|
||||
|
||||
private ApplicationInfo createApplicationInfo(String packageName, int uid) {
|
||||
ApplicationInfo appInfo = new ApplicationInfo();
|
||||
appInfo.sourceDir = "foo";
|
||||
appInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
|
||||
appInfo.storageUuid = UUID.randomUUID();
|
||||
appInfo.packageName = packageName;
|
||||
appInfo.uid = uid;
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
private AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
|
||||
AppEntry appEntry = new AppEntry(RuntimeEnvironment.application, appInfo, id);
|
||||
appEntry.label = "label";
|
||||
appEntry.mounted = true;
|
||||
return appEntry;
|
||||
}
|
||||
|
||||
private void addApp(String packageName, int id) {
|
||||
addApp(packageName, id, 0);
|
||||
}
|
||||
|
||||
private void addApp(String packageName, int id, int userId) {
|
||||
ApplicationInfo appInfo = createApplicationInfo(packageName, id);
|
||||
AppEntry appEntry = createAppEntry(appInfo, id);
|
||||
mApplicationsState.mAppEntries.add(appEntry);
|
||||
mApplicationsState.mEntriesMap.get(userId).put(appInfo.packageName, appEntry);
|
||||
}
|
||||
|
||||
private void processAllMessages() {
|
||||
Handler mainHandler = mApplicationsState.mMainHandler;
|
||||
Handler bkgHandler = mApplicationsState.mBackgroundHandler;
|
||||
ShadowLooper shadowBkgLooper = extract(bkgHandler.getLooper());
|
||||
ShadowLooper shadowMainLooper = extract(mainHandler.getLooper());
|
||||
shadowBkgLooper.idle();
|
||||
shadowMainLooper.idle();
|
||||
}
|
||||
|
||||
private AppEntry findAppEntry(List<AppEntry> appEntries, long id) {
|
||||
for (AppEntry appEntry : appEntries) {
|
||||
if (appEntry.id == id) {
|
||||
return appEntry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSession_isResumed_LoadsAll() {
|
||||
mSession.onResume();
|
||||
|
||||
addApp(HOME_PACKAGE_NAME, 1);
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
assertThat(appEntries.size()).isEqualTo(2);
|
||||
|
||||
for (AppEntry appEntry : appEntries) {
|
||||
assertThat(appEntry.size).isGreaterThan(0L);
|
||||
assertThat(appEntry.icon).isNotNull();
|
||||
}
|
||||
|
||||
AppEntry homeEntry = findAppEntry(appEntries, 1);
|
||||
assertThat(homeEntry.isHomeApp).isTrue();
|
||||
assertThat(homeEntry.hasLauncherEntry).isFalse();
|
||||
|
||||
AppEntry launchableEntry = findAppEntry(appEntries, 2);
|
||||
assertThat(launchableEntry.hasLauncherEntry).isTrue();
|
||||
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSession_isPaused_NotLoadsAll() {
|
||||
mSession.onResume();
|
||||
|
||||
addApp(HOME_PACKAGE_NAME, 1);
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
|
||||
mSession.mResumed = false;
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
|
||||
verify(mCallbacks, never()).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomSessionLoadsIconsOnly() {
|
||||
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
|
||||
mSession.onResume();
|
||||
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
assertThat(appEntries.size()).isEqualTo(1);
|
||||
|
||||
AppEntry launchableEntry = findAppEntry(appEntries, 1);
|
||||
assertThat(launchableEntry.icon).isNotNull();
|
||||
assertThat(launchableEntry.size).isEqualTo(-1);
|
||||
assertThat(launchableEntry.hasLauncherEntry).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomSessionLoadsSizesOnly() {
|
||||
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
|
||||
mSession.onResume();
|
||||
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
assertThat(appEntries.size()).isEqualTo(1);
|
||||
|
||||
AppEntry launchableEntry = findAppEntry(appEntries, 1);
|
||||
assertThat(launchableEntry.hasLauncherEntry).isFalse();
|
||||
assertThat(launchableEntry.size).isGreaterThan(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomSessionLoadsHomeOnly() {
|
||||
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
|
||||
mSession.onResume();
|
||||
|
||||
addApp(HOME_PACKAGE_NAME, 1);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
assertThat(appEntries.size()).isEqualTo(1);
|
||||
|
||||
AppEntry launchableEntry = findAppEntry(appEntries, 1);
|
||||
assertThat(launchableEntry.hasLauncherEntry).isFalse();
|
||||
assertThat(launchableEntry.size).isEqualTo(-1);
|
||||
assertThat(launchableEntry.isHomeApp).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomSessionLoadsLeanbackOnly() {
|
||||
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
|
||||
mSession.onResume();
|
||||
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
assertThat(appEntries.size()).isEqualTo(1);
|
||||
|
||||
AppEntry launchableEntry = findAppEntry(appEntries, 1);
|
||||
assertThat(launchableEntry.size).isEqualTo(-1);
|
||||
assertThat(launchableEntry.isHomeApp).isFalse();
|
||||
assertThat(launchableEntry.hasLauncherEntry).isTrue();
|
||||
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onResume_shouldNotIncludeSystemHiddenModule() {
|
||||
mSession.onResume();
|
||||
|
||||
final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
|
||||
assertThat(mApplications).hasSize(2);
|
||||
assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
|
||||
assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAndInstall_noWorkprofile_doResumeIfNeededLocked_shouldClearEntries()
|
||||
throws RemoteException {
|
||||
// scenario: only owner user
|
||||
// (PKG_1, PKG_2) -> (PKG_2, PKG_3)
|
||||
// PKG_1 is removed and PKG_3 is installed before app is resumed.
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = spy(
|
||||
ApplicationsState
|
||||
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
|
||||
|
||||
// Previous Applications:
|
||||
ApplicationInfo appInfo;
|
||||
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
mApplicationsState.mApplications = prevAppList;
|
||||
|
||||
// Previous Entries:
|
||||
// (PKG_1, PKG_2)
|
||||
addApp(PKG_1, OWNER_UID_1, 0);
|
||||
addApp(PKG_2, OWNER_UID_2, 0);
|
||||
|
||||
// latest Applications:
|
||||
// (PKG_2, PKG_3)
|
||||
final ArrayList<ApplicationInfo> appList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
appList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_3, OWNER_UID_3);
|
||||
appList.add(appInfo);
|
||||
setupDoResumeIfNeededLocked(appList, null);
|
||||
|
||||
mApplicationsState.doResumeIfNeededLocked();
|
||||
|
||||
verify(mApplicationsState).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAppRemoved_noWorkprofile_doResumeIfNeededLocked_shouldNotClearEntries()
|
||||
throws RemoteException {
|
||||
// scenario: only owner user
|
||||
// (PKG_1, PKG_2)
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = spy(
|
||||
ApplicationsState
|
||||
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
// Previous Applications
|
||||
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
mApplicationsState.mApplications = prevAppList;
|
||||
|
||||
// Previous Entries:
|
||||
// (pk1, PKG_2)
|
||||
addApp(PKG_1, OWNER_UID_1, 0);
|
||||
addApp(PKG_2, OWNER_UID_2, 0);
|
||||
|
||||
// latest Applications:
|
||||
// (PKG_2, PKG_3)
|
||||
final ArrayList<ApplicationInfo> appList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
appList.add(appInfo);
|
||||
setupDoResumeIfNeededLocked(appList, null);
|
||||
|
||||
mApplicationsState.doResumeIfNeededLocked();
|
||||
|
||||
verify(mApplicationsState, never()).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
|
||||
throws RemoteException {
|
||||
if (!MU_ENABLED) {
|
||||
return;
|
||||
}
|
||||
// [Preconditions]
|
||||
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
|
||||
// 2 apps (PKG_1, PKG_2) for non-owner.
|
||||
//
|
||||
// [Actions]
|
||||
// profile user's PKG_2 is removed before resume
|
||||
//
|
||||
// Applications:
|
||||
// owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2)
|
||||
// profile - (PKG_1, PKG_2) -> (PKG_1)
|
||||
//
|
||||
// Previous Entries:
|
||||
// owner - (PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
|
||||
ShadowUserManager shadowUserManager = Shadow
|
||||
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
|
||||
shadowUserManager.addProfile(PROFILE_USERID, "profile");
|
||||
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = spy(
|
||||
ApplicationsState
|
||||
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
// Previous Applications
|
||||
// owner - (PKG_1 - uninstalled, PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
mApplicationsState.mApplications = prevAppList;
|
||||
// Previous Entries:
|
||||
// owner (PKG_2), profile (pk1, PKG_2)
|
||||
// PKG_1 is not installed for owner, hence it's removed from entries
|
||||
addApp(PKG_2, OWNER_UID_2, 0);
|
||||
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
|
||||
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
|
||||
|
||||
// latest Applications:
|
||||
// owner (PKG_1, PKG_2), profile (PKG_1)
|
||||
// owner's PKG_1 is still listed and is in non-installed state
|
||||
// profile user's PKG_2 is removed by a user before resume
|
||||
//owner
|
||||
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
ownerAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
ownerAppList.add(appInfo);
|
||||
//profile
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
setupDoResumeIfNeededLocked(ownerAppList, new ArrayList<>(Arrays.asList(appInfo)));
|
||||
|
||||
mApplicationsState.doResumeIfNeededLocked();
|
||||
|
||||
verify(mApplicationsState).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
|
||||
throws RemoteException {
|
||||
if (!MU_ENABLED) {
|
||||
return;
|
||||
}
|
||||
// [Preconditions]
|
||||
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
|
||||
// 2 apps (PKG_1, PKG_2) for non-owner.
|
||||
//
|
||||
// [Actions]
|
||||
// Owner user's PKG_2 is removed before resume
|
||||
//
|
||||
// Applications:
|
||||
// owner - (PKG_1 - uninstalled, PKG_2) -> (PKG_1 - uninstalled, PKG_2 - uninstalled)
|
||||
// profile - (PKG_1, PKG_2) -> (PKG_1, PKG_2)
|
||||
//
|
||||
// Previous Entries:
|
||||
// owner - (PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
|
||||
ShadowUserManager shadowUserManager = Shadow
|
||||
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
|
||||
shadowUserManager.addProfile(PROFILE_USERID, "profile");
|
||||
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = spy(
|
||||
ApplicationsState
|
||||
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
// Previous Applications:
|
||||
// owner - (PKG_1 - uninstalled, PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
mApplicationsState.mApplications = prevAppList;
|
||||
|
||||
// Previous Entries:
|
||||
// owner (PKG_2), profile (pk1, PKG_2)
|
||||
// PKG_1 is not installed for owner, hence it's removed from entries
|
||||
addApp(PKG_2, OWNER_UID_2, 0);
|
||||
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
|
||||
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
|
||||
|
||||
// latest Applications:
|
||||
// owner (PKG_1 - uninstalled, PKG_2 - uninstalled), profile (PKG_1, PKG_2)
|
||||
// owner's PKG_1, PKG_2 is still listed and is in non-installed state
|
||||
// profile user's PKG_2 is removed before resume
|
||||
//owner
|
||||
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
ownerAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
ownerAppList.add(appInfo);
|
||||
|
||||
//profile
|
||||
final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
profileAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
|
||||
profileAppList.add(appInfo);
|
||||
setupDoResumeIfNeededLocked(ownerAppList, profileAppList);
|
||||
|
||||
mApplicationsState.doResumeIfNeededLocked();
|
||||
|
||||
verify(mApplicationsState).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
|
||||
throws RemoteException {
|
||||
if (!MU_ENABLED) {
|
||||
return;
|
||||
}
|
||||
// [Preconditions]
|
||||
// 2 apps (PKG_1, PKG_2) for owner, PKG_1 is not in installed state
|
||||
// 2 apps (PKG_1, PKG_2) for non-owner.
|
||||
//
|
||||
// Applications:
|
||||
// owner - (PKG_1 - uninstalled, PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
//
|
||||
// Previous Entries:
|
||||
// owner - (PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
|
||||
ShadowUserManager shadowUserManager = Shadow
|
||||
.extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
|
||||
shadowUserManager.addProfile(PROFILE_USERID, "profile");
|
||||
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = spy(
|
||||
ApplicationsState
|
||||
.getInstance(RuntimeEnvironment.application, mock(IPackageManager.class)));
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
// Previous Applications:
|
||||
// owner - (PKG_1 - uninstalled, PKG_2)
|
||||
// profile - (PKG_1, PKG_2)
|
||||
final ArrayList<ApplicationInfo> prevAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
prevAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
|
||||
prevAppList.add(appInfo);
|
||||
|
||||
mApplicationsState.mApplications = prevAppList;
|
||||
// Previous Entries:
|
||||
// owner (PKG_2), profile (pk1, PKG_2)
|
||||
// PKG_1 is not installed for owner, hence it's removed from entries
|
||||
addApp(PKG_2, OWNER_UID_2, 0);
|
||||
addApp(PKG_1, PROFILE_UID_1, PROFILE_USERID);
|
||||
addApp(PKG_2, PROFILE_UID_2, PROFILE_USERID);
|
||||
|
||||
// latest Applications:
|
||||
// owner (PKG_1 - uninstalled, PKG_2), profile (PKG_1, PKG_2)
|
||||
// owner's PKG_1 is still listed and is in non-installed state
|
||||
|
||||
// owner
|
||||
final ArrayList<ApplicationInfo> ownerAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, OWNER_UID_1);
|
||||
appInfo.flags ^= ApplicationInfo.FLAG_INSTALLED;
|
||||
ownerAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, OWNER_UID_2);
|
||||
ownerAppList.add(appInfo);
|
||||
|
||||
// profile
|
||||
final ArrayList<ApplicationInfo> profileAppList = new ArrayList<>();
|
||||
appInfo = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
profileAppList.add(appInfo);
|
||||
appInfo = createApplicationInfo(PKG_2, PROFILE_UID_2);
|
||||
profileAppList.add(appInfo);
|
||||
setupDoResumeIfNeededLocked(ownerAppList, profileAppList);
|
||||
|
||||
mApplicationsState.doResumeIfNeededLocked();
|
||||
|
||||
verify(mApplicationsState, never()).clearEntries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
|
||||
when(mApplication.getPackageName()).thenReturn("com.android.settings");
|
||||
mSession.onResume();
|
||||
|
||||
addApp(HOME_PACKAGE_NAME, 1);
|
||||
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
|
||||
mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
|
||||
processAllMessages();
|
||||
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
|
||||
|
||||
List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
|
||||
for (AppEntry appEntry : appEntries) {
|
||||
assertThat(appEntry.icon).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
|
||||
ArrayList<ApplicationInfo> profileApps)
|
||||
throws RemoteException {
|
||||
|
||||
if (ownerApps != null) {
|
||||
when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0)))
|
||||
.thenReturn(new ParceledListSlice<>(ownerApps));
|
||||
}
|
||||
if (profileApps != null) {
|
||||
when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID)))
|
||||
.thenReturn(new ParceledListSlice<>(profileApps));
|
||||
}
|
||||
final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class);
|
||||
when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false);
|
||||
mApplicationsState.setInterestingConfigChanges(configChanges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
|
||||
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
|
||||
ApplicationInfo appInfo = createApplicationInfo(PKG_1);
|
||||
AppEntry primaryUserApp = createAppEntry(appInfo, 1);
|
||||
|
||||
assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
|
||||
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
|
||||
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
|
||||
Build.VERSION_CODES.TIRAMISU);
|
||||
// Create an app (and subsequent AppEntry) in a non-primary user profile.
|
||||
ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
|
||||
|
||||
assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(um, appInfo1.uid)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
|
||||
// Mark system user as parent for both profile users.
|
||||
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
|
||||
ShadowUserManager shadowUserManager = Shadow.extract(um);
|
||||
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
|
||||
CLONE_USER, 0);
|
||||
shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
|
||||
RANDOM_USER, 0);
|
||||
shadowUserManager.setupUserProperty(PROFILE_USERID,
|
||||
/*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT);
|
||||
shadowUserManager.setupUserProperty(PROFILE_USERID2,
|
||||
/*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO);
|
||||
|
||||
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
|
||||
|
||||
// Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it.
|
||||
ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
|
||||
// Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it.
|
||||
ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3);
|
||||
AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
|
||||
AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
|
||||
|
||||
assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(um, appInfo1.uid)).isTrue();
|
||||
assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(um, appInfo2.uid)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntry_hasCache_shouldReturnCacheEntry() {
|
||||
mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
|
||||
addApp(PKG_1, /* id= */ 1);
|
||||
|
||||
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
|
||||
.isEqualTo(PKG_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntry_hasNoCache_shouldReturnEntry() {
|
||||
mApplicationsState.mEntriesMap.clear();
|
||||
ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
|
||||
mApplicationsState.mApplications.add(appInfo);
|
||||
mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
|
||||
|
||||
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
|
||||
.isEqualTo(PKG_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
|
||||
mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
|
||||
String normalModulePackage = "test.module.1";
|
||||
String hiddenModulePackage = "test.hidden.module.2";
|
||||
String hiddenApexPackage = "test.hidden.module.2.apex";
|
||||
|
||||
assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
|
||||
assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
|
||||
assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
|
||||
mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
|
||||
ApplicationsState.sInstance = null;
|
||||
mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
|
||||
String normalModulePackage = "test.module.1";
|
||||
String hiddenModulePackage = "test.hidden.module.2";
|
||||
String hiddenApexPackage = "test.hidden.module.2.apex";
|
||||
|
||||
assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
|
||||
assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
|
||||
assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DefaultAppInfoTest {
|
||||
|
||||
@Mock
|
||||
private PackageItemInfo mPackageItemInfo;
|
||||
@Mock
|
||||
private ComponentName mComponentName;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private ApplicationInfo mApplicationInfo;
|
||||
@Mock
|
||||
private Drawable mIcon;
|
||||
|
||||
private Context mContext;
|
||||
private DefaultAppInfo mInfo;
|
||||
|
||||
@Before
|
||||
public void setUp() throws PackageManager.NameNotFoundException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(),
|
||||
anyInt())).thenReturn(mApplicationInfo);
|
||||
when(mPackageManager.loadUnbadgedItemIcon(mPackageItemInfo, mApplicationInfo)).thenReturn(
|
||||
mIcon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initInfoWithActivityInfo_shouldLoadInfo() {
|
||||
mPackageItemInfo.packageName = "test";
|
||||
mInfo = new DefaultAppInfo(mContext, mPackageManager, 0 /* uid */, mPackageItemInfo);
|
||||
mInfo.loadLabel();
|
||||
Drawable icon = mInfo.loadIcon();
|
||||
|
||||
assertThat(mInfo.getKey()).isEqualTo(mPackageItemInfo.packageName);
|
||||
assertThat(icon).isNotNull();
|
||||
verify(mPackageItemInfo).loadLabel(mPackageManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initInfoWithComponent_shouldLoadInfo() {
|
||||
when(mComponentName.getPackageName()).thenReturn("com.android.settings");
|
||||
|
||||
mInfo = new DefaultAppInfo(mContext, mPackageManager, 0 /* uid */, mComponentName);
|
||||
mInfo.getKey();
|
||||
|
||||
verify(mComponentName).flattenToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager.OpEntry;
|
||||
import android.app.AppOpsManager.PackageOps;
|
||||
import android.content.Context;
|
||||
import android.content.PermissionChecker;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowPermissionChecker.class})
|
||||
public class RecentAppOpsAccessesTest {
|
||||
|
||||
private static final int TEST_UID = 1234;
|
||||
private static final long NOW = 1_000_000_000; // Approximately 9/8/2001
|
||||
private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
|
||||
private static final long TWENTY_THREE_HOURS_AGO = NOW - TimeUnit.HOURS.toMillis(23);
|
||||
private static final long TWO_DAYS_AGO = NOW - TimeUnit.DAYS.toMillis(2);
|
||||
private static final String[] TEST_PACKAGE_NAMES =
|
||||
{"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
|
||||
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private AppOpsManager mAppOpsManager;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
@Mock
|
||||
private Clock mClock;
|
||||
private Context mContext;
|
||||
private int mTestUserId;
|
||||
private RecentAppOpsAccess mRecentAppOpsAccess;
|
||||
|
||||
@Before
|
||||
public void setUp() throws NameNotFoundException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
|
||||
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
||||
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
|
||||
when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
|
||||
.thenReturn("testApplicationLabel");
|
||||
when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
|
||||
.thenReturn("testUserBadgedLabel");
|
||||
when(mPackageManager.getPermissionFlags(any(), any(), any()))
|
||||
.thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
|
||||
| PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED);
|
||||
for (String testPackageName : TEST_PACKAGE_NAMES) {
|
||||
ShadowPermissionChecker.setResult(
|
||||
testPackageName,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
PermissionChecker.PERMISSION_GRANTED);
|
||||
ShadowPermissionChecker.setResult(
|
||||
testPackageName,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
PermissionChecker.PERMISSION_GRANTED);
|
||||
}
|
||||
mTestUserId = UserHandle.getUserId(TEST_UID);
|
||||
when(mUserManager.getUserProfiles())
|
||||
.thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
|
||||
|
||||
long[] testRequestTime = {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
|
||||
appOps);
|
||||
mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
|
||||
|
||||
when(mClock.millis()).thenReturn(NOW);
|
||||
mRecentAppOpsAccess = new RecentAppOpsAccess(mContext, mClock,
|
||||
RecentAppOpsAccess.LOCATION_OPS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAppList_shouldFilterRecentAccesses() {
|
||||
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(false);
|
||||
// Only two of the apps have requested location within 15 min.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
|
||||
assertThat(requests.get(0).accessFinishTime).isEqualTo(ONE_MIN_AGO);
|
||||
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
|
||||
assertThat(requests.get(1).accessFinishTime).isEqualTo(TWENTY_THREE_HOURS_AGO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
|
||||
// Add android OS to the list of apps.
|
||||
PackageOps androidSystemPackageOps =
|
||||
createPackageOps(
|
||||
RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME,
|
||||
Process.SYSTEM_UID,
|
||||
AppOpsManager.OP_FINE_LOCATION,
|
||||
ONE_MIN_AGO);
|
||||
long[] testRequestTime =
|
||||
{ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO, ONE_MIN_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
appOps.add(androidSystemPackageOps);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
|
||||
appOps);
|
||||
mockTestApplicationInfos(
|
||||
Process.SYSTEM_UID, RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME);
|
||||
|
||||
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(true);
|
||||
// Android OS shouldn't show up in the list of apps.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
|
||||
assertThat(requests.get(0).accessFinishTime).isEqualTo(ONE_MIN_AGO);
|
||||
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
|
||||
assertThat(requests.get(1).accessFinishTime).isEqualTo(TWENTY_THREE_HOURS_AGO);
|
||||
}
|
||||
|
||||
private void mockTestApplicationInfos(int userId, String... packageNameList)
|
||||
throws NameNotFoundException {
|
||||
for (String packageName : packageNameList) {
|
||||
ApplicationInfo appInfo = new ApplicationInfo();
|
||||
appInfo.packageName = packageName;
|
||||
when(mPackageManager.getApplicationInfoAsUser(
|
||||
packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
|
||||
List<PackageOps> packageOpsList = new ArrayList<>();
|
||||
for (int i = 0; i < packageNameList.length; i++) {
|
||||
PackageOps packageOps = createPackageOps(
|
||||
packageNameList[i],
|
||||
TEST_UID,
|
||||
AppOpsManager.OP_FINE_LOCATION,
|
||||
time[i]);
|
||||
packageOpsList.add(packageOps);
|
||||
}
|
||||
return packageOpsList;
|
||||
}
|
||||
|
||||
private PackageOps createPackageOps(String packageName, int uid, int op, long time) {
|
||||
return new PackageOps(
|
||||
packageName,
|
||||
uid,
|
||||
Collections.singletonList(createOpEntryWithTime(op, time)));
|
||||
}
|
||||
|
||||
private OpEntry createOpEntryWithTime(int op, long time) {
|
||||
// Slot for background access timestamp.
|
||||
final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
|
||||
accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
|
||||
AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
|
||||
|
||||
return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
|
||||
new AppOpsManager.AttributedOpEntry(op, false, accessEvents, null)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ServiceListingTest {
|
||||
|
||||
private static final String TEST_SETTING = "testSetting";
|
||||
private static final String TEST_INTENT = "com.example.intent";
|
||||
|
||||
private ServiceListing mServiceListing;
|
||||
private Context mContext;
|
||||
private PackageManager mPm;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mPm = mock(PackageManager.class);
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
when(mContext.getPackageManager()).thenReturn(mPm);
|
||||
|
||||
mServiceListing = new ServiceListing.Builder(mContext)
|
||||
.setTag("testTag")
|
||||
.setSetting(TEST_SETTING)
|
||||
.setNoun("testNoun")
|
||||
.setIntentAction(TEST_INTENT)
|
||||
.setPermission("testPermission")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidator() {
|
||||
ServiceInfo s1 = new ServiceInfo();
|
||||
s1.permission = "testPermission";
|
||||
s1.packageName = "pkg";
|
||||
ServiceInfo s2 = new ServiceInfo();
|
||||
s2.permission = "testPermission";
|
||||
s2.packageName = "pkg2";
|
||||
ResolveInfo r1 = new ResolveInfo();
|
||||
r1.serviceInfo = s1;
|
||||
ResolveInfo r2 = new ResolveInfo();
|
||||
r2.serviceInfo = s2;
|
||||
|
||||
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
|
||||
ImmutableList.of(r1, r2));
|
||||
|
||||
mServiceListing = new ServiceListing.Builder(mContext)
|
||||
.setTag("testTag")
|
||||
.setSetting(TEST_SETTING)
|
||||
.setNoun("testNoun")
|
||||
.setIntentAction(TEST_INTENT)
|
||||
.setValidator(info -> {
|
||||
if (info.packageName.equals("pkg")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.setPermission("testPermission")
|
||||
.build();
|
||||
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
|
||||
mServiceListing.addCallback(callback);
|
||||
mServiceListing.reload();
|
||||
|
||||
verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
|
||||
ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(callback, times(1)).onServicesReloaded(captor.capture());
|
||||
|
||||
assertThat(captor.getValue().size()).isEqualTo(1);
|
||||
assertThat(captor.getValue().get(0)).isEqualTo(s1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoValidator() {
|
||||
ServiceInfo s1 = new ServiceInfo();
|
||||
s1.permission = "testPermission";
|
||||
s1.packageName = "pkg";
|
||||
ServiceInfo s2 = new ServiceInfo();
|
||||
s2.permission = "testPermission";
|
||||
s2.packageName = "pkg2";
|
||||
ResolveInfo r1 = new ResolveInfo();
|
||||
r1.serviceInfo = s1;
|
||||
ResolveInfo r2 = new ResolveInfo();
|
||||
r2.serviceInfo = s2;
|
||||
|
||||
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
|
||||
ImmutableList.of(r1, r2));
|
||||
|
||||
mServiceListing = new ServiceListing.Builder(mContext)
|
||||
.setTag("testTag")
|
||||
.setSetting(TEST_SETTING)
|
||||
.setNoun("testNoun")
|
||||
.setIntentAction(TEST_INTENT)
|
||||
.setPermission("testPermission")
|
||||
.build();
|
||||
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
|
||||
mServiceListing.addCallback(callback);
|
||||
mServiceListing.reload();
|
||||
|
||||
verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
|
||||
ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(callback, times(1)).onServicesReloaded(captor.capture());
|
||||
|
||||
assertThat(captor.getValue().size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallback() {
|
||||
ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
|
||||
mServiceListing.addCallback(callback);
|
||||
mServiceListing.reload();
|
||||
verify(callback, times(1)).onServicesReloaded(anyList());
|
||||
mServiceListing.removeCallback(callback);
|
||||
mServiceListing.reload();
|
||||
verify(callback, times(1)).onServicesReloaded(anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveLoad() {
|
||||
ComponentName testComponent1 = new ComponentName("testPackage1", "testClass1");
|
||||
ComponentName testComponent2 = new ComponentName("testPackage2", "testClass2");
|
||||
Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING,
|
||||
testComponent1.flattenToString() + ":" + testComponent2.flattenToString());
|
||||
|
||||
mServiceListing.reload();
|
||||
|
||||
assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
|
||||
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).contains(testComponent1.flattenToString());
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).contains(testComponent2.flattenToString());
|
||||
|
||||
mServiceListing.setEnabled(testComponent1, false);
|
||||
|
||||
assertThat(mServiceListing.isEnabled(testComponent1)).isFalse();
|
||||
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).doesNotContain(testComponent1.flattenToString());
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).contains(testComponent2.flattenToString());
|
||||
|
||||
mServiceListing.setEnabled(testComponent1, true);
|
||||
|
||||
assertThat(mServiceListing.isEnabled(testComponent1)).isTrue();
|
||||
assertThat(mServiceListing.isEnabled(testComponent2)).isTrue();
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).contains(testComponent1.flattenToString());
|
||||
assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
|
||||
TEST_SETTING)).contains(testComponent2.flattenToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothA2dp;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothCodecConfig;
|
||||
import android.bluetooth.BluetoothCodecStatus;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class A2dpProfileTest {
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice;
|
||||
@Mock
|
||||
private BluetoothA2dp mBluetoothA2dp;
|
||||
@Mock
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
|
||||
private A2dpProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new A2dpProfile(mContext, mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
|
||||
when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.A2DP)))
|
||||
.thenReturn(Arrays.asList(mDevice));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_isProfileReady() {
|
||||
assertThat(mProfile.isProfileReady()).isTrue();
|
||||
verify(mProfileManager).callServiceConnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceDisconnected_profileNotReady() {
|
||||
mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isFalse();
|
||||
verify(mProfileManager).callServiceDisconnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsHighQualityAudio() {
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
|
||||
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue();
|
||||
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
|
||||
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
|
||||
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
|
||||
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isHighQualityAudioEnabled() {
|
||||
when(mBluetoothA2dp.isOptionalCodecsEnabled(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
|
||||
|
||||
when(mBluetoothA2dp.isOptionalCodecsEnabled(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
|
||||
|
||||
// If we don't have a stored pref for whether optional codecs should be enabled or not,
|
||||
// then isHighQualityAudioEnabled() should return true or false based on whether optional
|
||||
// codecs are supported. If the device is connected then we should ask it directly, but if
|
||||
// the device isn't connected then rely on the stored pref about such support.
|
||||
when(mBluetoothA2dp.isOptionalCodecsEnabled(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
|
||||
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
|
||||
BluetoothProfile.STATE_DISCONNECTED);
|
||||
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
|
||||
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(mDevice)).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
|
||||
|
||||
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
|
||||
when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
|
||||
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
|
||||
when(status.getCodecConfig()).thenReturn(config);
|
||||
when(config.isMandatoryCodec()).thenReturn(false);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
|
||||
when(config.isMandatoryCodec()).thenReturn(true);
|
||||
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
|
||||
}
|
||||
|
||||
// Strings to use in fake resource lookups.
|
||||
private static String KNOWN_CODEC_LABEL = "Use high quality audio: %1$s";
|
||||
private static String UNKNOWN_CODEC_LABEL = "Use high quality audio";
|
||||
private static String[] CODEC_NAMES =
|
||||
new String[]{"Default", "SBC", "AAC", "aptX", "aptX HD", "LDAC"};
|
||||
|
||||
/**
|
||||
* Helper for setting up several tests of getHighQualityAudioOptionLabel
|
||||
*/
|
||||
private void setupLabelTest() {
|
||||
// SettingsLib doesn't have string resource lookup working for robotests, so fake our own
|
||||
// string loading.
|
||||
when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality),
|
||||
any(String.class))).thenAnswer((invocation) -> {
|
||||
return String.format(KNOWN_CODEC_LABEL, invocation.getArguments()[1]);
|
||||
});
|
||||
when(mContext.getString(eq(R.string.bluetooth_profile_a2dp_high_quality_unknown_codec)))
|
||||
.thenReturn(UNKNOWN_CODEC_LABEL);
|
||||
|
||||
final Resources res = mock(Resources.class);
|
||||
when(mContext.getResources()).thenReturn(res);
|
||||
when(res.getStringArray(eq(R.array.bluetooth_a2dp_codec_titles)))
|
||||
.thenReturn(CODEC_NAMES);
|
||||
|
||||
// Most tests want to simulate optional codecs being supported by the device, so do that
|
||||
// by default here.
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLableCodecsNotSupported() {
|
||||
setupLabelTest();
|
||||
when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
|
||||
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
|
||||
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLabelDeviceDisconnected() {
|
||||
setupLabelTest();
|
||||
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
|
||||
BluetoothProfile.STATE_DISCONNECTED);
|
||||
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLabelDeviceConnectedButNotHighQualityCodec() {
|
||||
setupLabelTest();
|
||||
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
|
||||
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
|
||||
List<BluetoothCodecConfig> configs = Arrays.asList(config);
|
||||
when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
|
||||
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
|
||||
|
||||
when(config.isMandatoryCodec()).thenReturn(true);
|
||||
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLabelDeviceConnectedWithHighQualityCodec() {
|
||||
setupLabelTest();
|
||||
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
|
||||
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
|
||||
List<BluetoothCodecConfig> configs = Arrays.asList(config);
|
||||
when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
|
||||
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
|
||||
|
||||
when(config.isMandatoryCodec()).thenReturn(false);
|
||||
when(config.getCodecType()).thenReturn(4);
|
||||
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(
|
||||
String.format(KNOWN_CODEC_LABEL, "LDAC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setActiveDevice_returnTrue() {
|
||||
assertThat(mProfile.setActiveDevice(null)).isTrue();
|
||||
assertThat(mProfile.setActiveDevice(mDevice)).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothA2dpSink;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class A2dpSinkProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothA2dpSink mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private A2dpSinkProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new A2dpSinkProfile(RuntimeEnvironment.application,
|
||||
mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class BluetoothEventManagerTest {
|
||||
|
||||
private static final String DEVICE_NAME = "test_device_name";
|
||||
|
||||
@Mock
|
||||
private LocalBluetoothAdapter mLocalAdapter;
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
||||
@Mock
|
||||
private BluetoothCallback mBluetoothCallback;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
@Mock
|
||||
private HeadsetProfile mHfpProfile;
|
||||
@Mock
|
||||
private A2dpProfile mA2dpProfile;
|
||||
@Mock
|
||||
private HearingAidProfile mHearingAidProfile;
|
||||
@Mock
|
||||
private LeAudioProfile mLeAudioProfile;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice1;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice2;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice3;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mLocalProfileManager;
|
||||
@Mock
|
||||
private BluetoothUtils.ErrorListener mErrorListener;
|
||||
|
||||
private Context mContext;
|
||||
private Intent mIntent;
|
||||
private BluetoothEventManager mBluetoothEventManager;
|
||||
private CachedBluetoothDevice mCachedDevice1;
|
||||
private CachedBluetoothDevice mCachedDevice2;
|
||||
private CachedBluetoothDevice mCachedDevice3;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
|
||||
mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
|
||||
mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
|
||||
when(mHfpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mA2dpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
|
||||
when(mLeAudioProfile.isProfileReady()).thenReturn(true);
|
||||
mCachedDevice1 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1);
|
||||
mCachedDevice2 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2);
|
||||
mCachedDevice3 = new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3);
|
||||
BluetoothUtils.setErrorListener(mErrorListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ifUserHandleIsNull_registerReceiverIsCalled() {
|
||||
Context mockContext = mock(Context.class);
|
||||
BluetoothEventManager eventManager =
|
||||
new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
|
||||
/* handler= */ null, /* userHandle= */ null);
|
||||
|
||||
verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
|
||||
eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ifUserHandleSpecified_registerReceiverAsUserIsCalled() {
|
||||
Context mockContext = mock(Context.class);
|
||||
BluetoothEventManager eventManager =
|
||||
new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
|
||||
/* handler= */ null, UserHandle.ALL);
|
||||
|
||||
verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
|
||||
any(IntentFilter.class), eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Intent ACTION_AUDIO_STATE_CHANGED should dispatch to callback.
|
||||
*/
|
||||
@Test
|
||||
public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback).onAudioModeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intent ACTION_PHONE_STATE_CHANGED should dispatch to callback.
|
||||
*/
|
||||
@Test
|
||||
public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback).onAudioModeChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* dispatchProfileConnectionStateChanged should dispatch to onProfileConnectionStateChanged
|
||||
* callback.
|
||||
*/
|
||||
@Test
|
||||
public void dispatchProfileConnectionStateChanged_registerCallback_shouldDispatchCallback() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
|
||||
mBluetoothEventManager.dispatchProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
|
||||
verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_DISCONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() {
|
||||
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback, never()).onAclConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_DISCONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() {
|
||||
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback, never()).onAclConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() {
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mBluetoothCallback, never()).onAclConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothAdapter.STATE_CONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify onActiveDeviceChanged().
|
||||
*/
|
||||
@Test
|
||||
public void dispatchActiveDeviceChanged_connectedDevices_activeDeviceChanged() {
|
||||
final List<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
|
||||
cachedDevices.add(mCachedDevice1);
|
||||
cachedDevices.add(mCachedDevice2);
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
|
||||
|
||||
// Connect both devices for A2DP and HFP
|
||||
mCachedDevice1.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice2.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice1.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice2.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
// Verify that both devices are connected and none is Active
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
|
||||
// The first device is active for A2DP, the second device is active for HFP
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice1, BluetoothProfile.A2DP);
|
||||
mBluetoothEventManager
|
||||
.dispatchActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEADSET);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
|
||||
// The first device is active for A2DP and HFP
|
||||
mBluetoothEventManager
|
||||
.dispatchActiveDeviceChanged(mCachedDevice1, BluetoothProfile.HEADSET);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
|
||||
// The second device is active for A2DP and HFP
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice2, BluetoothProfile.A2DP);
|
||||
mBluetoothEventManager
|
||||
.dispatchActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEADSET);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
|
||||
// No active device for A2DP
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(null, BluetoothProfile.A2DP);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
|
||||
// No active device for HFP
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(null, BluetoothProfile.HEADSET);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchActiveDeviceChanged_connectedMemberDevices_activeDeviceChanged() {
|
||||
final List<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
|
||||
cachedDevices.add(mCachedDevice1);
|
||||
cachedDevices.add(mCachedDevice2);
|
||||
|
||||
int group1 = 1;
|
||||
when(mDevice3.getAddress()).thenReturn("testAddress3");
|
||||
mCachedDevice1.setGroupId(group1);
|
||||
mCachedDevice3.setGroupId(group1);
|
||||
mCachedDevice1.addMemberDevice(mCachedDevice3);
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
|
||||
|
||||
// Connect device1 and device3 for LE and device2 for A2DP and HFP
|
||||
mCachedDevice1.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice3.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice2.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice2.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
// Verify that both devices are connected and none is Active
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice3.isActiveDevice(BluetoothProfile.LE_AUDIO)).isFalse();
|
||||
|
||||
// The member device is active.
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice3,
|
||||
BluetoothProfile.LE_AUDIO);
|
||||
|
||||
// The main device is active since the member is active.
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify onActiveDeviceChanged() with A2DP and Hearing Aid.
|
||||
*/
|
||||
@Test
|
||||
public void dispatchActiveDeviceChanged_withA2dpAndHearingAid() {
|
||||
final List<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
|
||||
cachedDevices.add(mCachedDevice1);
|
||||
cachedDevices.add(mCachedDevice2);
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
|
||||
|
||||
// Connect device1 for A2DP and HFP and device2 for Hearing Aid
|
||||
mCachedDevice1.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice1.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
mCachedDevice2.onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
// Verify that both devices are connected and none is Active
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
|
||||
// The first device is active for A2DP and HFP
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice1, BluetoothProfile.A2DP);
|
||||
mBluetoothEventManager
|
||||
.dispatchActiveDeviceChanged(mCachedDevice1, BluetoothProfile.HEADSET);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
|
||||
// The second device is active for Hearing Aid and the first device is not active
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(null, BluetoothProfile.A2DP);
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(null, BluetoothProfile.HEADSET);
|
||||
mBluetoothEventManager
|
||||
.dispatchActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEARING_AID);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue();
|
||||
|
||||
// No active device for Hearing Aid
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(null, BluetoothProfile.HEARING_AID);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
|
||||
assertThat(mCachedDevice2.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchActiveDeviceChanged_callExpectedOnActiveDeviceChanged() {
|
||||
mBluetoothEventManager.registerCallback(mBluetoothCallback);
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
|
||||
Collections.singletonList(mCachedDevice1));
|
||||
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(mCachedDevice1,
|
||||
BluetoothProfile.HEARING_AID);
|
||||
|
||||
verify(mCachedDeviceManager).onActiveDeviceChanged(mCachedDevice1);
|
||||
verify(mBluetoothCallback).onActiveDeviceChanged(mCachedDevice1,
|
||||
BluetoothProfile.HEARING_AID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchActiveDeviceChanged_activeFromSubDevice_mainCachedDeviceActive() {
|
||||
CachedBluetoothDevice subDevice = new CachedBluetoothDevice(mContext, mLocalProfileManager,
|
||||
mDevice3);
|
||||
mCachedDevice1.setSubDevice(subDevice);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
|
||||
Collections.singletonList(mCachedDevice1));
|
||||
mCachedDevice1.onProfileStateChanged(mHearingAidProfile,
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse();
|
||||
mBluetoothEventManager.dispatchActiveDeviceChanged(subDevice, BluetoothProfile.HEARING_AID);
|
||||
assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON,
|
||||
BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT);
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
|
||||
when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
|
||||
eq(R.string.bluetooth_pairing_error_message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() {
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON,
|
||||
BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN);
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
|
||||
when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
|
||||
eq(R.string.bluetooth_pairing_device_down_error_message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() {
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON,
|
||||
BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
|
||||
when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
|
||||
eq(R.string.bluetooth_pairing_rejected_error_message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() {
|
||||
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON,
|
||||
BluetoothDevice.UNBOND_REASON_AUTH_FAILED);
|
||||
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
|
||||
when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
|
||||
eq(R.string.bluetooth_pairing_pin_error_message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.settingslib.widget.AdaptiveIcon;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class BluetoothUtilsTest {
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
@Mock
|
||||
private AudioManager mAudioManager;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
private Context mContext;
|
||||
private static final String STRING_METADATA = "string_metadata";
|
||||
private static final String BOOL_METADATA = "true";
|
||||
private static final String INT_METADATA = "25";
|
||||
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
|
||||
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
|
||||
private static final String CONTROL_METADATA =
|
||||
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
|
||||
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
|
||||
private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBtClassDrawableWithDescription_typePhone_returnPhoneDrawable() {
|
||||
when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
|
||||
BluetoothClass.Device.Major.PHONE);
|
||||
final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
|
||||
mContext, mCachedBluetoothDevice);
|
||||
|
||||
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_phone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBtClassDrawableWithDescription_typeComputer_returnComputerDrawable() {
|
||||
when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
|
||||
BluetoothClass.Device.Major.COMPUTER);
|
||||
final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
|
||||
mContext, mCachedBluetoothDevice);
|
||||
|
||||
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() {
|
||||
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
|
||||
|
||||
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb");
|
||||
|
||||
assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription(
|
||||
RuntimeEnvironment.application,
|
||||
mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStringMetaData_hasMetaData_getCorrectMetaData() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).thenReturn(
|
||||
STRING_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.getStringMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).isEqualTo(STRING_METADATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIntMetaData_hasMetaData_getCorrectMetaData() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
|
||||
INT_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
|
||||
.isEqualTo(Integer.parseInt(INT_METADATA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIntMetaData_invalidMetaData_getErrorCode() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(null);
|
||||
|
||||
assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
|
||||
.isEqualTo(BluetoothUtils.META_INT_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBooleanMetaData_hasMetaData_getCorrectMetaData() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
|
||||
BOOL_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.getBooleanMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUriMetaData_hasMetaData_getCorrectMetaData() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(
|
||||
STRING_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_MAIN_ICON)).isEqualTo(Uri.parse(STRING_METADATA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUriMetaData_nullMetaData_getNullUri() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
|
||||
|
||||
assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
|
||||
BluetoothDevice.METADATA_MAIN_ICON)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getControlUriMetaData_hasMetaData_returnsCorrectMetaData() {
|
||||
when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn(
|
||||
CONTROL_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.getControlUriMetaData(mBluetoothDevice)).isEqualTo(
|
||||
STRING_METADATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_untetheredHeadset_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
|
||||
BOOL_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_deviceTypeUntetheredHeadset_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_deviceTypeWatch_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_deviceTypeStylus_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_deviceTypeDefault_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedDetailsHeader_noMetadata_returnFalse() {
|
||||
assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
|
||||
BOOL_METADATA.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() {
|
||||
when(mBluetoothDevice.getMetadata(
|
||||
BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
|
||||
BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
|
||||
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
|
||||
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnFalse() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
|
||||
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailableMediaBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnTrue() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
|
||||
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedHfpDevice_returnTrue() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
|
||||
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConnectedBluetoothDevice_isConnectedLeAudioDevice_returnFalse() {
|
||||
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConnectedBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnTrue() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
|
||||
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConnectedBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnFalse() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
|
||||
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConnectedBluetoothDevice_isHeadset_isConnectedHfpDevice_returnFalse() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
|
||||
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(true);
|
||||
|
||||
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConnectedBluetoothDevice_isNotConnected_returnFalse() {
|
||||
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
|
||||
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mBluetoothDevice.isConnected()).thenReturn(false);
|
||||
|
||||
assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
|
||||
mAudioManager)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
|
||||
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
|
||||
null);
|
||||
|
||||
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
|
||||
mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
|
||||
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
|
||||
FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
|
||||
|
||||
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
|
||||
mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
|
||||
throws Exception {
|
||||
final String exclusiveManagerName =
|
||||
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
|
||||
FAKE_EXCLUSIVE_MANAGER_NAME);
|
||||
|
||||
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
|
||||
exclusiveManagerName.getBytes());
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
|
||||
exclusiveManagerName, 0);
|
||||
|
||||
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
|
||||
mBluetoothDevice)).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
|
||||
throws Exception {
|
||||
final String exclusiveManagerName =
|
||||
BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
|
||||
FAKE_EXCLUSIVE_MANAGER_NAME);
|
||||
|
||||
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
|
||||
exclusiveManagerName.getBytes());
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
|
||||
|
||||
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
|
||||
mBluetoothDevice)).isEqualTo(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothCsipSetCoordinator;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CachedBluetoothDeviceManagerTest {
|
||||
private final static String DEVICE_NAME_1 = "TestName_1";
|
||||
private final static String DEVICE_NAME_2 = "TestName_2";
|
||||
private final static String DEVICE_NAME_3 = "TestName_3";
|
||||
private final static String DEVICE_ALIAS_1 = "TestAlias_1";
|
||||
private final static String DEVICE_ALIAS_2 = "TestAlias_2";
|
||||
private final static String DEVICE_ALIAS_3 = "TestAlias_3";
|
||||
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
|
||||
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
|
||||
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
|
||||
private final static long HISYNCID1 = 10;
|
||||
private final static long HISYNCID2 = 11;
|
||||
private final static Map<Integer, ParcelUuid> CAP_GROUP1 =
|
||||
Map.of(1, BluetoothUuid.CAP);
|
||||
private final static Map<Integer, ParcelUuid> CAP_GROUP2 =
|
||||
Map.of(2, BluetoothUuid.CAP);
|
||||
private final BluetoothClass DEVICE_CLASS_1 =
|
||||
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
|
||||
private final BluetoothClass DEVICE_CLASS_2 =
|
||||
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mLocalProfileManager;
|
||||
@Mock
|
||||
private LocalBluetoothManager mLocalBluetoothManager;
|
||||
@Mock
|
||||
private BluetoothEventManager mBluetoothEventManager;
|
||||
@Mock
|
||||
private HeadsetProfile mHfpProfile;
|
||||
@Mock
|
||||
private A2dpProfile mA2dpProfile;
|
||||
@Mock
|
||||
private PanProfile mPanProfile;
|
||||
@Mock
|
||||
private HearingAidProfile mHearingAidProfile;
|
||||
@Mock
|
||||
private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice1;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice2;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice3;
|
||||
private CachedBluetoothDevice mCachedDevice1;
|
||||
private CachedBluetoothDevice mCachedDevice2;
|
||||
private CachedBluetoothDevice mCachedDevice3;
|
||||
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
||||
private HearingAidDeviceManager mHearingAidDeviceManager;
|
||||
private Context mContext;
|
||||
|
||||
private BluetoothClass createBtClass(int deviceClass) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeInt(deviceClass);
|
||||
p.setDataPosition(0); // reset position of parcel before passing to constructor
|
||||
|
||||
BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
|
||||
p.recycle();
|
||||
return bluetoothClass;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
|
||||
when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
|
||||
when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
|
||||
when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
|
||||
when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
|
||||
when(mDevice3.getName()).thenReturn(DEVICE_NAME_3);
|
||||
when(mDevice1.getAlias()).thenReturn(DEVICE_ALIAS_1);
|
||||
when(mDevice2.getAlias()).thenReturn(DEVICE_ALIAS_2);
|
||||
when(mDevice3.getAlias()).thenReturn(DEVICE_ALIAS_3);
|
||||
when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1);
|
||||
when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
|
||||
when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
|
||||
|
||||
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
|
||||
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
|
||||
when(mHfpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mA2dpProfile.isProfileReady()).thenReturn(true);
|
||||
when(mPanProfile.isProfileReady()).thenReturn(true);
|
||||
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
|
||||
when(mCsipSetCoordinatorProfile.isProfileReady())
|
||||
.thenReturn(true);
|
||||
doAnswer((invocation) -> mHearingAidProfile).
|
||||
when(mLocalProfileManager).getHearingAidProfile();
|
||||
doAnswer((invocation) -> mCsipSetCoordinatorProfile)
|
||||
.when(mLocalProfileManager).getCsipSetCoordinatorProfile();
|
||||
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
|
||||
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
|
||||
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
|
||||
mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice().
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_validCachedDevices_devicesAdded() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice2).isNotNull();
|
||||
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).contains(cachedDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.findDevice(mDevice1)).isEqualTo(cachedDevice1);
|
||||
assertThat(mCachedDeviceManager.findDevice(mDevice2)).isEqualTo(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getSubDevice(), new device has the same HiSyncId.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_sameHiSyncId_validSubDevice() {
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
assertThat(cachedDevice1.getSubDevice()).isEqualTo(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getSubDevice(), new device has the different HiSyncId.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_differentHiSyncId_validSubDevice() {
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
assertThat(cachedDevice1.getSubDevice()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice(), new device has the same HiSyncId.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_sameHiSyncId_validCachedDevices_mainDevicesAdded_subDevicesNotAdded() {
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).doesNotContain(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice(), new device has the different HiSyncId.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_differentHiSyncId_validCachedDevices_bothAdded() {
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).contains(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice(), the duplicated device should not added.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_addDuplicatedDevice_duplicateDeviceShouldNotAdded() {
|
||||
final CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
final CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice2).isNotNull();
|
||||
final CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice3).isNotNull();
|
||||
final CachedBluetoothDevice cachedDevice4 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice4).isNotNull();
|
||||
|
||||
final Collection<CachedBluetoothDevice> devices =
|
||||
mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify findDevice(), new device has the same HiSyncId.
|
||||
*/
|
||||
@Test
|
||||
public void findDevice_sameHiSyncId_foundBothDevice() {
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.findDevice(mDevice1)).isEqualTo(cachedDevice1);
|
||||
assertThat(mCachedDeviceManager.findDevice(mDevice2)).isEqualTo(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getName().
|
||||
*/
|
||||
@Test
|
||||
public void getName_validCachedDevice_nameFound() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
assertThat(mCachedDeviceManager.getName(mDevice1)).isEqualTo(DEVICE_ALIAS_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify onDeviceNameUpdated().
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceNameUpdated_validName_nameUpdated() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
assertThat(cachedDevice1.getName()).isEqualTo(DEVICE_ALIAS_1);
|
||||
|
||||
final String newAliasName = "NewAliasName";
|
||||
when(mDevice1.getAlias()).thenReturn(newAliasName);
|
||||
mCachedDeviceManager.onDeviceNameUpdated(mDevice1);
|
||||
assertThat(cachedDevice1.getName()).isEqualTo(newAliasName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify clearNonBondedDevices().
|
||||
*/
|
||||
@Test
|
||||
public void clearNonBondedDevices_bondedAndNonBondedDevices_nonBondedDevicesCleared() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice2).isNotNull();
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
|
||||
mCachedDeviceManager.clearNonBondedDevices();
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).contains(cachedDevice2);
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
mCachedDeviceManager.clearNonBondedDevices();
|
||||
devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).doesNotContain(cachedDevice2);
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
mCachedDeviceManager.clearNonBondedDevices();
|
||||
devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).doesNotContain(cachedDevice1);
|
||||
assertThat(devices).doesNotContain(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify clearNonBondedDevices() for hearing aids sub device.
|
||||
*/
|
||||
@Test
|
||||
public void clearNonBondedDevices_nonBondedSubDevice() {
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
cachedDevice1.setSubDevice(cachedDevice2);
|
||||
|
||||
assertThat(cachedDevice1.getSubDevice()).isEqualTo(cachedDevice2);
|
||||
mCachedDeviceManager.clearNonBondedDevices();
|
||||
|
||||
assertThat(cachedDevice1.getSubDevice()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify OnDeviceUnpaired() for main hearing Aid device unpair.
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceUnpaired_unpairHearingAidMainDevice() {
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
cachedDevice1.setHearingAidInfo(
|
||||
new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
|
||||
cachedDevice2.setHearingAidInfo(
|
||||
new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
|
||||
cachedDevice1.setSubDevice(cachedDevice2);
|
||||
|
||||
// Call onDeviceUnpaired for the one in mCachedDevices.
|
||||
mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
|
||||
verify(mDevice1).removeBond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify OnDeviceUnpaired() for sub hearing Aid device unpair.
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceUnpaired_unpairSubDevice() {
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
cachedDevice1.setSubDevice(cachedDevice2);
|
||||
|
||||
// Call onDeviceUnpaired for the one in mCachedDevices.
|
||||
mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
|
||||
verify(mDevice2).removeBond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getSubDeviceSummary() for disconnected sub device
|
||||
*/
|
||||
@Test
|
||||
public void getSubDeviceSummary_SubDeviceDisconnected() {
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
mCachedDeviceManager.getSubDeviceSummary(mCachedDevice1);
|
||||
|
||||
verify(mCachedDevice2, never()).getConnectionSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getSubDeviceSummary() for connected sub device
|
||||
*/
|
||||
@Test
|
||||
public void getSubDeviceSummary_SubDeviceConnected() {
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
mCachedDeviceManager.getSubDeviceSummary(mCachedDevice1);
|
||||
|
||||
verify(mCachedDevice2).getConnectionSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify isSubDevice_validSubDevice().
|
||||
*/
|
||||
@Test
|
||||
public void isSubDevice_validSubDevice() {
|
||||
doReturn(HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1);
|
||||
mCachedDeviceManager.addDevice(mDevice1);
|
||||
|
||||
// Both device are not sub device in default value.
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
|
||||
|
||||
// Add Device-2 as sub device of Device-1 with same HiSyncId.
|
||||
doReturn(HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2);
|
||||
mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
// Verify Device-2 is sub device, but Device-1 is not.
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify updateHearingAidsDevices().
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidDevices_directToHearingAidDeviceManager() {
|
||||
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
|
||||
mCachedDeviceManager.mCachedDevices));
|
||||
mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
|
||||
mCachedDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
verify(mHearingAidDeviceManager).updateHearingAidsDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify onDeviceDisappeared().
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceDisappeared_deviceBondedUnbonded_unbondedDeviceDisappeared() {
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isFalse();
|
||||
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getMemberDevice(), new device has the same group id.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_sameGroupId_validMemberDevice() {
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice1);
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice2);
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice3);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
|
||||
|
||||
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
|
||||
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify getMemberDevice(), new device has the different group id.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_differentGroupId_validMemberDevice() {
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice1);
|
||||
doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
assertThat(cachedDevice1.getMemberDevice()).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice(), new device has the same group id.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_sameGroupId_validCachedDevices_mainDevicesAdded_memberDevicesNotAdded() {
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice1);
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).doesNotContain(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify addDevice(), new device has the different group id.
|
||||
*/
|
||||
@Test
|
||||
public void addDevice_differentGroupId_validCachedDevices_bothAdded() {
|
||||
doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice1);
|
||||
doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
|
||||
.getGroupUuidMapByDevice(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
|
||||
Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
|
||||
assertThat(devices).contains(cachedDevice1);
|
||||
assertThat(devices).contains(cachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify clearNonBondedDevices() for csip set member device.
|
||||
*/
|
||||
@Test
|
||||
public void clearNonBondedDevices_nonBondedMemberDevice() {
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
|
||||
cachedDevice1.addMemberDevice(cachedDevice2);
|
||||
cachedDevice1.addMemberDevice(cachedDevice3);
|
||||
|
||||
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
|
||||
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
|
||||
mCachedDeviceManager.clearNonBondedDevices();
|
||||
|
||||
assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
|
||||
assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify OnDeviceUnpaired() for csip device unpair.
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceUnpaired_unpairCsipMainDevice() {
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
cachedDevice1.setGroupId(1);
|
||||
cachedDevice2.setGroupId(1);
|
||||
cachedDevice1.addMemberDevice(cachedDevice2);
|
||||
|
||||
// Call onDeviceUnpaired for the one in mCachedDevices.
|
||||
mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
|
||||
|
||||
verify(mDevice2).removeBond();
|
||||
assertThat(cachedDevice1.getGroupId()).isEqualTo(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
|
||||
assertThat(cachedDevice2.getGroupId()).isEqualTo(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify OnDeviceUnpaired() for csip device unpair.
|
||||
*/
|
||||
@Test
|
||||
public void onDeviceUnpaired_unpairCsipSubDevice() {
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
cachedDevice1.setGroupId(1);
|
||||
cachedDevice2.setGroupId(1);
|
||||
cachedDevice1.addMemberDevice(cachedDevice2);
|
||||
|
||||
// Call onDeviceUnpaired for the one in mCachedDevices.
|
||||
mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
|
||||
|
||||
verify(mDevice1).removeBond();
|
||||
assertThat(cachedDevice2.getGroupId()).isEqualTo(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify isSubDevice_validSubDevice().
|
||||
*/
|
||||
@Test
|
||||
public void isSubDevice_validMemberDevice() {
|
||||
doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
|
||||
mCachedDeviceManager.addDevice(mDevice1);
|
||||
|
||||
// Both device are not sub device in default value.
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
|
||||
|
||||
// Add Device-2 as device with Device-1 with the same group id, and add Device-3 with
|
||||
// the different group id.
|
||||
doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
|
||||
doReturn(CAP_GROUP2).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
|
||||
mCachedDeviceManager.addDevice(mDevice2);
|
||||
mCachedDeviceManager.addDevice(mDevice3);
|
||||
|
||||
// Verify Device-2 is sub device, but Device-1, and Device-3 is not.
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
|
||||
assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() {
|
||||
doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
assertThat(cachedDevice1).isNotNull();
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
|
||||
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
|
||||
assertThat(cachedDevice2).isNotNull();
|
||||
|
||||
int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
|
||||
assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
|
||||
mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId);
|
||||
|
||||
verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
|
||||
verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() {
|
||||
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
|
||||
mCachedDeviceManager.mCachedDevices));
|
||||
doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any());
|
||||
mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
|
||||
cachedDevice1.setHearingAidInfo(
|
||||
new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build());
|
||||
|
||||
mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1);
|
||||
|
||||
verify(mHearingAidDeviceManager).onActiveDeviceChanged(cachedDevice1);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothCsipSetCoordinator;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CsipDeviceManagerTest {
|
||||
private final static String DEVICE_NAME_1 = "TestName_1";
|
||||
private final static String DEVICE_NAME_2 = "TestName_2";
|
||||
private final static String DEVICE_NAME_3 = "TestName_3";
|
||||
private final static String DEVICE_ALIAS_1 = "TestAlias_1";
|
||||
private final static String DEVICE_ALIAS_2 = "TestAlias_2";
|
||||
private final static String DEVICE_ALIAS_3 = "TestAlias_3";
|
||||
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
|
||||
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
|
||||
private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
|
||||
private final static int GROUP1 = 1;
|
||||
private final BluetoothClass DEVICE_CLASS_1 =
|
||||
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
|
||||
private final BluetoothClass DEVICE_CLASS_2 =
|
||||
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
|
||||
|
||||
@Mock
|
||||
private LocalBluetoothManager mLocalBluetoothManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mLocalProfileManager;
|
||||
@Mock
|
||||
private BluetoothEventManager mBluetoothEventManager;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice1;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice2;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice3;
|
||||
@Mock
|
||||
private HeadsetProfile mHfpProfile;
|
||||
@Mock
|
||||
private A2dpProfile mA2dpProfile;
|
||||
@Mock
|
||||
private LeAudioProfile mLeAudioProfile;
|
||||
|
||||
private CachedBluetoothDevice mCachedDevice1;
|
||||
private CachedBluetoothDevice mCachedDevice2;
|
||||
private CachedBluetoothDevice mCachedDevice3;
|
||||
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
||||
private CsipDeviceManager mCsipDeviceManager;
|
||||
private Context mContext;
|
||||
private List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
|
||||
|
||||
private BluetoothClass createBtClass(int deviceClass) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeInt(deviceClass);
|
||||
p.setDataPosition(0); // reset position of parcel before passing to constructor
|
||||
|
||||
BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
|
||||
p.recycle();
|
||||
return bluetoothClass;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = RuntimeEnvironment.application;
|
||||
when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
|
||||
when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
|
||||
when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
|
||||
when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
|
||||
when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
|
||||
when(mDevice3.getName()).thenReturn(DEVICE_NAME_3);
|
||||
when(mDevice1.getAlias()).thenReturn(DEVICE_ALIAS_1);
|
||||
when(mDevice2.getAlias()).thenReturn(DEVICE_ALIAS_2);
|
||||
when(mDevice3.getAlias()).thenReturn(DEVICE_ALIAS_3);
|
||||
when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1);
|
||||
when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
|
||||
when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
|
||||
when(mDevice1.isConnected()).thenReturn(true);
|
||||
when(mDevice2.isConnected()).thenReturn(true);
|
||||
when(mDevice3.isConnected()).thenReturn(true);
|
||||
when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
|
||||
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
|
||||
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
|
||||
when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
|
||||
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
|
||||
when(mLocalProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
|
||||
|
||||
when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(null);
|
||||
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
|
||||
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
|
||||
|
||||
mCachedDevices = mCachedDeviceManager.mCachedDevices;
|
||||
mCsipDeviceManager = mCachedDeviceManager.mCsipDeviceManager;
|
||||
|
||||
// Setup the default for testing
|
||||
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
|
||||
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
|
||||
mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3));
|
||||
|
||||
mCachedDevice1.setGroupId(GROUP1);
|
||||
mCachedDevice2.setGroupId(GROUP1);
|
||||
mCachedDevice1.addMemberDevice(mCachedDevice2);
|
||||
mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevices.add(mCachedDevice3);
|
||||
|
||||
List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
|
||||
profiles.add(mHfpProfile);
|
||||
profiles.add(mA2dpProfile);
|
||||
profiles.add(mLeAudioProfile);
|
||||
when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(true);
|
||||
|
||||
profiles.clear();
|
||||
profiles.add(mLeAudioProfile);
|
||||
when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
|
||||
profiles.clear();
|
||||
profiles.add(mHfpProfile);
|
||||
profiles.add(mA2dpProfile);
|
||||
when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles);
|
||||
when(mCachedDevice3.isConnected()).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProfileConnectionStateChangedIfProcessed_profileIsConnecting_returnOff() {
|
||||
assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
|
||||
BluetoothProfile.STATE_CONNECTING)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProfileConnectionStateChangedIfProcessed_profileIsDisconnecting_returnOff() {
|
||||
assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
|
||||
BluetoothProfile.STATE_DISCONNECTING)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRelationshipOfGroupDevices_invalidGroupId_returnOff() {
|
||||
assertThat(mCsipDeviceManager.updateRelationshipOfGroupDevices(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupDevicesFromAllOfDevicesList_invalidGroupId_returnEmpty() {
|
||||
assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(
|
||||
BluetoothCsipSetCoordinator.GROUP_ID_INVALID).isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGroupDevicesFromAllOfDevicesList_validGroupId_returnGroupDevices() {
|
||||
List<CachedBluetoothDevice> expectedList = new ArrayList<>();
|
||||
expectedList.add(mCachedDevice1);
|
||||
expectedList.add(mCachedDevice2);
|
||||
|
||||
assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))
|
||||
.isEqualTo(expectedList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferredMainDevice_dualModeDevice_returnDualModeDevice() {
|
||||
CachedBluetoothDevice expectedDevice = mCachedDevice1;
|
||||
|
||||
assertThat(
|
||||
mCsipDeviceManager.getPreferredMainDevice(GROUP1,
|
||||
mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
|
||||
.isEqualTo(expectedDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferredMainDevice_noConnectedDualModeDevice_returnLeadDevice() {
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
when(mDevice1.isConnected()).thenReturn(false);
|
||||
when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(mDevice2);
|
||||
CachedBluetoothDevice expectedDevice = mCachedDevice2;
|
||||
|
||||
assertThat(
|
||||
mCsipDeviceManager.getPreferredMainDevice(GROUP1,
|
||||
mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
|
||||
.isEqualTo(expectedDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferredMainDevice_noConnectedDualModeDeviceNoLeadDevice_returnConnectedOne() {
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
when(mDevice1.isConnected()).thenReturn(false);
|
||||
when(mDevice2.isConnected()).thenReturn(true);
|
||||
CachedBluetoothDevice expectedDevice = mCachedDevice2;
|
||||
|
||||
assertThat(
|
||||
mCsipDeviceManager.getPreferredMainDevice(GROUP1,
|
||||
mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
|
||||
.isEqualTo(expectedDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferredMainDevice_noConnectedDevice_returnDualModeDevice() {
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
when(mDevice1.isConnected()).thenReturn(false);
|
||||
when(mDevice2.isConnected()).thenReturn(false);
|
||||
CachedBluetoothDevice expectedDevice = mCachedDevice1;
|
||||
|
||||
assertThat(
|
||||
mCsipDeviceManager.getPreferredMainDevice(GROUP1,
|
||||
mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
|
||||
.isEqualTo(expectedDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPreferredMainDevice_noConnectedDeviceNoDualMode_returnFirstOneDevice() {
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
when(mDevice1.isConnected()).thenReturn(false);
|
||||
when(mDevice2.isConnected()).thenReturn(false);
|
||||
List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
|
||||
profiles.add(mLeAudioProfile);
|
||||
when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
|
||||
CachedBluetoothDevice expectedDevice = mCachedDevice1;
|
||||
|
||||
assertThat(
|
||||
mCsipDeviceManager.getPreferredMainDevice(GROUP1,
|
||||
mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
|
||||
.isEqualTo(expectedDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMemberDevicesIntoMainDevice_noPreferredDevice_returnFalseAndNoChangeList() {
|
||||
CachedBluetoothDevice preferredDevice = null;
|
||||
List<CachedBluetoothDevice> expectedList = new ArrayList<>();
|
||||
for (CachedBluetoothDevice item : mCachedDevices) {
|
||||
expectedList.add(item);
|
||||
}
|
||||
|
||||
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
|
||||
.isFalse();
|
||||
for (CachedBluetoothDevice expectedItem : expectedList) {
|
||||
assertThat(mCachedDevices.contains(expectedItem)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndNoOtherInList_noChangeList() {
|
||||
// Condition: The preferredDevice is main and no other main device in top list
|
||||
// Expected Result: return false and the list is no changed
|
||||
CachedBluetoothDevice preferredDevice = mCachedDevice1;
|
||||
List<CachedBluetoothDevice> expectedList = new ArrayList<>();
|
||||
for (CachedBluetoothDevice item : mCachedDevices) {
|
||||
expectedList.add(item);
|
||||
}
|
||||
|
||||
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
|
||||
.isFalse();
|
||||
for (CachedBluetoothDevice expectedItem : expectedList) {
|
||||
assertThat(mCachedDevices.contains(expectedItem)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
|
||||
// Condition: The preferredDevice is main and there is another main device in top list
|
||||
// Expected Result: return true and there is the preferredDevice in top list
|
||||
CachedBluetoothDevice preferredDevice = mCachedDevice1;
|
||||
mCachedDevice1.getMemberDevice().clear();
|
||||
mCachedDevices.clear();
|
||||
mCachedDevices.add(preferredDevice);
|
||||
mCachedDevices.add(mCachedDevice2);
|
||||
mCachedDevices.add(mCachedDevice3);
|
||||
|
||||
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
|
||||
.isTrue();
|
||||
assertThat(mCachedDevices.contains(preferredDevice)).isTrue();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
|
||||
assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMember_returnTrue() {
|
||||
// Condition: The preferredDevice is member
|
||||
// Expected Result: return true and there is the preferredDevice in top list
|
||||
CachedBluetoothDevice preferredDevice = mCachedDevice2;
|
||||
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
|
||||
|
||||
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
|
||||
.isTrue();
|
||||
// expected main is mCachedDevice1 which is the main of preferredDevice, since system
|
||||
// switch the relationship between preferredDevice and the main of preferredDevice
|
||||
assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
|
||||
assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
|
||||
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
|
||||
// Condition: The preferredDevice is member and there are two main device in top list
|
||||
// Expected Result: return true and there is the preferredDevice in top list
|
||||
CachedBluetoothDevice preferredDevice = mCachedDevice2;
|
||||
BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
|
||||
mCachedDevice3.setGroupId(GROUP1);
|
||||
|
||||
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
|
||||
.isTrue();
|
||||
// expected main is mCachedDevice1 which is the main of preferredDevice, since system
|
||||
// switch the relationship between preferredDevice and the main of preferredDevice
|
||||
assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevices.contains(mCachedDevice3)).isFalse();
|
||||
assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
|
||||
assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
|
||||
assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
|
||||
assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.bluetooth;
|
||||
|
||||
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
|
||||
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHapClient;
|
||||
import android.bluetooth.BluetoothHapPresetInfo;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class HapClientProfileTest {
|
||||
|
||||
private static final int TEST_GROUP_ID = 1;
|
||||
private static final int TEST_PRESET_INDEX = 1;
|
||||
private static final String TEST_DEVICE_NAME = "test_device";
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
@Mock
|
||||
private BluetoothHapClient mService;
|
||||
@Mock
|
||||
private BluetoothHapPresetInfo mPresetInfo;
|
||||
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private HapClientProfile mProfile;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
|
||||
final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
|
||||
final ShadowBluetoothAdapter shadowBluetoothAdapter =
|
||||
Shadow.extract(bluetoothManager.getAdapter());
|
||||
mServiceListener = shadowBluetoothAdapter.getServiceListener();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_isProfileReady() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isTrue();
|
||||
verify(mProfileManager).callServiceConnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceDisconnected_isProfileNotReady() {
|
||||
mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isFalse();
|
||||
verify(mProfileManager).callServiceDisconnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_returnCorrectConnectionState() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionState(mBluetoothDevice))
|
||||
.thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
|
||||
.isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_connectionPolicyAllowed_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
|
||||
assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_connectionPolicyForbidden_returnFalse() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
|
||||
assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionPolicy_returnCorrectConnectionPolicy() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
|
||||
assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
|
||||
.isEqualTo(CONNECTION_POLICY_ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectedDevices_returnCorrectList() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
int[] connectedStates = new int[] {
|
||||
BluetoothProfile.STATE_CONNECTED,
|
||||
BluetoothProfile.STATE_CONNECTING,
|
||||
BluetoothProfile.STATE_DISCONNECTING};
|
||||
List<BluetoothDevice> connectedList = Arrays.asList(
|
||||
mBluetoothDevice,
|
||||
mBluetoothDevice,
|
||||
mBluetoothDevice);
|
||||
when(mService.getDevicesMatchingConnectionStates(connectedStates))
|
||||
.thenReturn(connectedList);
|
||||
|
||||
assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectableDevices_returnCorrectList() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
int[] connectableStates = new int[] {
|
||||
BluetoothProfile.STATE_DISCONNECTED,
|
||||
BluetoothProfile.STATE_CONNECTED,
|
||||
BluetoothProfile.STATE_CONNECTING,
|
||||
BluetoothProfile.STATE_DISCONNECTING};
|
||||
List<BluetoothDevice> connectableList = Arrays.asList(
|
||||
mBluetoothDevice,
|
||||
mBluetoothDevice,
|
||||
mBluetoothDevice,
|
||||
mBluetoothDevice);
|
||||
when(mService.getDevicesMatchingConnectionStates(connectableStates))
|
||||
.thenReturn(connectableList);
|
||||
|
||||
assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify registerCallback() call is correctly delegated to {@link BluetoothHapClient} service.
|
||||
*/
|
||||
@Test
|
||||
public void registerCallback_verifyIsCalled() {
|
||||
final Executor executor = (command -> new Thread(command).start());
|
||||
final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
|
||||
@Override
|
||||
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex,
|
||||
int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetInfoChanged(@NonNull BluetoothDevice device,
|
||||
@NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
|
||||
|
||||
}
|
||||
};
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.registerCallback(executor, callback);
|
||||
|
||||
verify(mService).registerCallback(executor, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify unregisterCallback() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void unregisterCallback_verifyIsCalled() {
|
||||
final BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
|
||||
@Override
|
||||
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex,
|
||||
int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresetInfoChanged(@NonNull BluetoothDevice device,
|
||||
@NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
|
||||
|
||||
}
|
||||
};
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.unregisterCallback(callback);
|
||||
|
||||
verify(mService).unregisterCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify getHapGroup() call is correctly delegated to {@link BluetoothHapClient} service
|
||||
* and return correct value.
|
||||
*/
|
||||
@Test
|
||||
public void getHapGroup_verifyIsCalledAndReturnCorrectValue() {
|
||||
when(mService.getHapGroup(mBluetoothDevice)).thenReturn(TEST_GROUP_ID);
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
final int groupId = mProfile.getHapGroup(mBluetoothDevice);
|
||||
|
||||
verify(mService).getHapGroup(mBluetoothDevice);
|
||||
assertThat(groupId).isEqualTo(TEST_GROUP_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify getActivePresetIndex() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service and return correct index.
|
||||
*/
|
||||
@Test
|
||||
public void getActivePresetIndex_verifyIsCalledAndReturnCorrectValue() {
|
||||
when(mService.getActivePresetIndex(mBluetoothDevice)).thenReturn(TEST_PRESET_INDEX);
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
final int activeIndex = mProfile.getActivePresetIndex(mBluetoothDevice);
|
||||
|
||||
verify(mService).getActivePresetIndex(mBluetoothDevice);
|
||||
assertThat(activeIndex).isEqualTo(TEST_PRESET_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify getActivePresetInfo() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service and return correct object.
|
||||
*/
|
||||
@Test
|
||||
public void getActivePresetInfo_verifyIsCalledAndReturnCorrectObject() {
|
||||
when(mService.getActivePresetInfo(mBluetoothDevice)).thenReturn(mPresetInfo);
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
final BluetoothHapPresetInfo activeInfo = mProfile.getActivePresetInfo(mBluetoothDevice);
|
||||
|
||||
verify(mService).getActivePresetInfo(mBluetoothDevice);
|
||||
assertThat(activeInfo).isEqualTo(mPresetInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify selectPreset() call is correctly delegated to {@link BluetoothHapClient} service.
|
||||
*/
|
||||
@Test
|
||||
public void selectPreset_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
|
||||
|
||||
verify(mService).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify selectPresetForGroup() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void selectPresetForGroup_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX);
|
||||
|
||||
verify(mService).selectPresetForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify switchToNextPreset() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void switchToNextPreset_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.switchToNextPreset(mBluetoothDevice);
|
||||
|
||||
verify(mService).switchToNextPreset(mBluetoothDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify switchToNextPresetForGroup() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void switchToNextPresetForGroup_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.switchToNextPresetForGroup(TEST_GROUP_ID);
|
||||
|
||||
verify(mService).switchToNextPresetForGroup(TEST_GROUP_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify switchToPreviousPreset() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void switchToPreviousPreset_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.switchToPreviousPreset(mBluetoothDevice);
|
||||
|
||||
verify(mService).switchToPreviousPreset(mBluetoothDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify switchToPreviousPresetForGroup() call is correctly delegated to
|
||||
* {@link BluetoothHapClient} service.
|
||||
*/
|
||||
@Test
|
||||
public void switchToPreviousPresetForGroup_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.switchToPreviousPresetForGroup(TEST_GROUP_ID);
|
||||
|
||||
verify(mService).switchToPreviousPresetForGroup(TEST_GROUP_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify getPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service and
|
||||
* return correct object.
|
||||
*/
|
||||
@Test
|
||||
public void getPresetInfo_verifyIsCalledAndReturnCorrectObject() {
|
||||
when(mService.getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX)).thenReturn(mPresetInfo);
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
final BluetoothHapPresetInfo info = mProfile.getPresetInfo(mBluetoothDevice,
|
||||
TEST_PRESET_INDEX);
|
||||
|
||||
verify(mService).getPresetInfo(mBluetoothDevice, TEST_PRESET_INDEX);
|
||||
assertThat(info).isEqualTo(mPresetInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify getAllPresetInfo() call is correctly delegated to {@link BluetoothHapClient} service
|
||||
* and return correct list.
|
||||
*/
|
||||
@Test
|
||||
public void getAllPresetInfo_verifyIsCalledAndReturnCorrectList() {
|
||||
final List<BluetoothHapPresetInfo> testList = Arrays.asList(mPresetInfo, mPresetInfo);
|
||||
when(mService.getAllPresetInfo(mBluetoothDevice)).thenReturn(testList);
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
final List<BluetoothHapPresetInfo> infoList = mProfile.getAllPresetInfo(mBluetoothDevice);
|
||||
|
||||
verify(mService).getAllPresetInfo(mBluetoothDevice);
|
||||
assertThat(infoList.size()).isEqualTo(testList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify setPresetName() call is correctly delegated to {@link BluetoothHapClient} service.
|
||||
*/
|
||||
@Test
|
||||
public void setPresetName_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
|
||||
|
||||
verify(mService).setPresetName(mBluetoothDevice, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify setPresetNameForGroup() call is correctly delegated to {@link BluetoothHapClient}
|
||||
* service.
|
||||
*/
|
||||
@Test
|
||||
public void setPresetNameForGroup_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
|
||||
|
||||
mProfile.setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
|
||||
|
||||
verify(mService).setPresetNameForGroup(TEST_GROUP_ID, TEST_PRESET_INDEX, TEST_DEVICE_NAME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.android.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class HeadsetProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothHeadset mService;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private HeadsetProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context context = spy(RuntimeEnvironment.application);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
|
||||
mProfile = new HeadsetProfile(context, mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadsetProfile_shouldReturnAudioState() {
|
||||
when(mService.getAudioState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
|
||||
assertThat(mProfile.getAudioState(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
|
||||
|
||||
when(mService.getAudioState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
|
||||
assertThat(mProfile.getAudioState(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothHeadset.STATE_AUDIO_CONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setActiveDevice_returnTrue() {
|
||||
assertThat(mProfile.setActiveDevice(null)).isTrue();
|
||||
assertThat(mProfile.setActiveDevice(mBluetoothDevice)).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.media.audiopolicy.AudioProductStrategy;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Tests for {@link HearingAidAudioRoutingHelper}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HearingAidAudioRoutingHelperTest {
|
||||
|
||||
@Rule
|
||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Spy
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
|
||||
private static final String NOT_EXPECT_DEVICE_ADDRESS = "11:B2:B2:B2:B2:B2";
|
||||
|
||||
@Mock
|
||||
private AudioProductStrategy mAudioStrategy;
|
||||
@Spy
|
||||
private AudioManager mAudioManager = mContext.getSystemService(AudioManager.class);
|
||||
@Mock
|
||||
private AudioDeviceInfo mAudioDeviceInfo;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mSubCachedBluetoothDevice;
|
||||
private AudioDeviceAttributes mHearingDeviceAttribute;
|
||||
private HearingAidAudioRoutingHelper mHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
doReturn(mAudioManager).when(mContext).getSystemService(AudioManager.class);
|
||||
when(mAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
|
||||
when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
|
||||
new AudioDeviceInfo[]{mAudioDeviceInfo});
|
||||
doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
|
||||
any(AudioProductStrategy.class));
|
||||
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
|
||||
AudioManager.STREAM_MUSIC))
|
||||
.thenReturn((new AudioAttributes.Builder()).build());
|
||||
|
||||
mHearingDeviceAttribute = new AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT,
|
||||
AudioDeviceInfo.TYPE_HEARING_AID,
|
||||
TEST_DEVICE_ADDRESS);
|
||||
mHelper = spy(new HearingAidAudioRoutingHelper(mContext));
|
||||
doReturn(List.of(mAudioStrategy)).when(mHelper).getAudioProductStrategies();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
|
||||
when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
|
||||
mHearingDeviceAttribute);
|
||||
|
||||
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
|
||||
mHearingDeviceAttribute,
|
||||
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
|
||||
|
||||
verify(mAudioManager, atLeastOnce()).removePreferredDeviceForStrategy(mAudioStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() {
|
||||
when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
|
||||
|
||||
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
|
||||
mHearingDeviceAttribute,
|
||||
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
|
||||
|
||||
verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
|
||||
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
|
||||
mHearingDeviceAttribute,
|
||||
HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE);
|
||||
|
||||
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
|
||||
mHearingDeviceAttribute);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferredDeviceRoutingStrategies_valueDeviceSpeaker_callSetStrategy() {
|
||||
final AudioDeviceAttributes speakerDevice = new AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
|
||||
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
|
||||
mHearingDeviceAttribute,
|
||||
HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER);
|
||||
|
||||
verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
|
||||
speakerDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMatchedHearingDeviceAttributes_mainHearingDevice_equalAddress() {
|
||||
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
|
||||
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
|
||||
mCachedBluetoothDevice).getAddress();
|
||||
|
||||
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMatchedHearingDeviceAttributes_subHearingDevice_equalAddress() {
|
||||
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
|
||||
when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
|
||||
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
|
||||
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
|
||||
mCachedBluetoothDevice).getAddress();
|
||||
|
||||
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMatchedHearingDeviceAttributes_memberHearingDevice_equalAddress() {
|
||||
when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
final Set<CachedBluetoothDevice> memberDevices = new HashSet<CachedBluetoothDevice>();
|
||||
memberDevices.add(mSubCachedBluetoothDevice);
|
||||
when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
|
||||
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberDevices);
|
||||
|
||||
final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
|
||||
mCachedBluetoothDevice).getAddress();
|
||||
|
||||
assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
|
||||
import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
|
||||
import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
|
||||
|
||||
import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
|
||||
import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
|
||||
import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceMode.MODE_BINAURAL;
|
||||
import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceSide.SIDE_RIGHT;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.media.audiopolicy.AudioProductStrategy;
|
||||
import android.os.Parcel;
|
||||
import android.util.FeatureFlagUtils;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HearingAidDeviceManagerTest {
|
||||
@Rule
|
||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
private final static long HISYNCID1 = 10;
|
||||
private final static long HISYNCID2 = 11;
|
||||
private final static String DEVICE_NAME_1 = "TestName_1";
|
||||
private final static String DEVICE_NAME_2 = "TestName_2";
|
||||
private final static String DEVICE_ALIAS_1 = "TestAlias_1";
|
||||
private final static String DEVICE_ALIAS_2 = "TestAlias_2";
|
||||
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
|
||||
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
|
||||
private final BluetoothClass DEVICE_CLASS =
|
||||
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private CachedBluetoothDevice mCachedDevice1;
|
||||
private CachedBluetoothDevice mCachedDevice2;
|
||||
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
||||
private HearingAidDeviceManager mHearingAidDeviceManager;
|
||||
private AudioDeviceAttributes mHearingDeviceAttribute;
|
||||
@Spy
|
||||
private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mLocalProfileManager;
|
||||
@Mock
|
||||
private LocalBluetoothManager mLocalBluetoothManager;
|
||||
@Mock
|
||||
private BluetoothEventManager mBluetoothEventManager;
|
||||
@Mock
|
||||
private HearingAidProfile mHearingAidProfile;
|
||||
@Mock
|
||||
private LeAudioProfile mLeAudioProfile;
|
||||
@Mock
|
||||
private HapClientProfile mHapClientProfile;
|
||||
@Mock
|
||||
private AudioProductStrategy mAudioStrategy;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice1;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice2;
|
||||
|
||||
|
||||
private BluetoothClass createBtClass(int deviceClass) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeInt(deviceClass);
|
||||
p.setDataPosition(0); // reset position of parcel before passing to constructor
|
||||
|
||||
BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
|
||||
p.recycle();
|
||||
return bluetoothClass;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true);
|
||||
when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
|
||||
when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
|
||||
when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
|
||||
when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
|
||||
when(mDevice1.getAlias()).thenReturn(DEVICE_ALIAS_1);
|
||||
when(mDevice2.getAlias()).thenReturn(DEVICE_ALIAS_2);
|
||||
when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS);
|
||||
when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS);
|
||||
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
|
||||
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
|
||||
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
|
||||
when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
|
||||
when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
|
||||
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
|
||||
AudioManager.STREAM_MUSIC))
|
||||
.thenReturn((new AudioAttributes.Builder()).build());
|
||||
doReturn(List.of(mAudioStrategy)).when(mHelper).getSupportedStrategies(any(int[].class));
|
||||
|
||||
mHearingDeviceAttribute = new AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT,
|
||||
AudioDeviceInfo.TYPE_HEARING_AID,
|
||||
DEVICE_ADDRESS_1);
|
||||
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
|
||||
mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
|
||||
mCachedDeviceManager.mCachedDevices, mHelper));
|
||||
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
|
||||
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) ASHA hearing aid
|
||||
* 2) Valid HiSyncId
|
||||
* Result:
|
||||
* Set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_asha_validHiSyncId_setHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
|
||||
when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
|
||||
|
||||
assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
|
||||
|
||||
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
|
||||
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
|
||||
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
|
||||
HearingAidInfo.DeviceMode.MODE_BINAURAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) ASHA hearing aid
|
||||
* 2) Invalid HiSyncId
|
||||
* Result:
|
||||
* Do not set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_asha_invalidHiSyncId_notToSetHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
|
||||
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
|
||||
|
||||
verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) ASHA hearing aid
|
||||
* 2) Invalid HiSyncId
|
||||
* 3) ASHA uuid scan filter
|
||||
* Result:
|
||||
* Set an empty hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_asha_scanFilterNotNull_setEmptyHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
|
||||
final ScanFilter scanFilter = new ScanFilter.Builder()
|
||||
.setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
|
||||
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
|
||||
|
||||
verify(mCachedDevice1).setHearingAidInfo(new HearingAidInfo.Builder().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Asha hearing aid
|
||||
* 2) Invalid HiSyncId
|
||||
* 3) Random scan filter
|
||||
* Result:
|
||||
* Do not set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_asha_randomScanFilter_notToSetHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
|
||||
final ScanFilter scanFilter = new ScanFilter.Builder().build();
|
||||
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
|
||||
|
||||
verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) LeAudio hearing aid
|
||||
* 2) Valid audio location and device type
|
||||
* Result:
|
||||
* Set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_leAudio_validInfo_setHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
|
||||
when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
|
||||
when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
|
||||
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
|
||||
|
||||
verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
|
||||
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
|
||||
HearingAidInfo.DeviceMode.MODE_BINAURAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test initHearingAidDeviceIfNeeded
|
||||
*
|
||||
* Conditions:
|
||||
* 1) LeAudio hearing aid
|
||||
* 2) Invalid audio location and device type
|
||||
* Result:
|
||||
* Do not set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
|
||||
when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
|
||||
when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
|
||||
|
||||
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
|
||||
|
||||
verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device
|
||||
*/
|
||||
@Test
|
||||
public void setSubDeviceIfNeeded_sameHiSyncId_setSubDevice() {
|
||||
mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
|
||||
mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
|
||||
mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setSubDeviceIfNeeded, a device with different HiSyncId will not be set as sub device
|
||||
*/
|
||||
@Test
|
||||
public void setSubDeviceIfNeeded_differentHiSyncId_notSetSubDevice() {
|
||||
mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
|
||||
mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID2));
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
|
||||
mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDevice1.getSubDevice()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Two ASHA hearing aids with the same HiSyncId
|
||||
* 2) First paired devices is connected
|
||||
* 3) Second paired device is disconnected
|
||||
* Result:
|
||||
* First paired device would be set as main device and second paired device will be set
|
||||
* as sub device and removed from CachedDevices list.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_firstPairedDevicesConnected_verifySubDevice() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(true);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isNull();
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Two ASHA hearing aids with the same HiSyncId
|
||||
* 2) First paired devices is disconnected
|
||||
* 3) Second paired device is connected
|
||||
* Result:
|
||||
* Second paired device would be set as main device and first paired device will be set
|
||||
* as sub device and removed from CachedDevices list.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_secondPairedDeviceConnected_verifySubDevice() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice1)).isTrue();
|
||||
assertThat(mCachedDevice2.getSubDevice()).isNull();
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice1)).isFalse();
|
||||
assertThat(mCachedDevice2.getSubDevice()).isEqualTo(mCachedDevice1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Two ASHA hearing aids with the same HiSyncId
|
||||
* 2) First paired devices is connected
|
||||
* 3) Second paired device is connected
|
||||
* Result:
|
||||
* First paired device would be set as main device and second paired device will be set
|
||||
* as sub device and removed from CachedDevices list.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_bothConnected_verifySubDevice() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(true);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isNull();
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Two ASHA hearing aids with the same HiSyncId
|
||||
* Result:
|
||||
* Dispatch device removed callback
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_dispatchDeviceRemovedCallback() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) Two ASHA hearing aids with invalid HiSyncId
|
||||
* Result:
|
||||
* Do nothing
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_invalidHiSyncId_doNothing() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HI_SYNC_ID_INVALID);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) ASHA hearing aids
|
||||
* 2) Valid HiSync Id
|
||||
* Result:
|
||||
* Set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_asha_validHiSyncId_setHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
|
||||
when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
|
||||
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
|
||||
HearingAidInfo.DeviceMode.MODE_BINAURAL);
|
||||
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
|
||||
HearingAidInfo.DeviceSide.SIDE_RIGHT);
|
||||
verify(mHearingAidDeviceManager).onHiSyncIdChanged(HISYNCID1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) LeAudio hearing aid
|
||||
* 2) Valid audio location and device type
|
||||
* Result:
|
||||
* Set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_leAudio_validInfo_setHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
|
||||
when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
|
||||
when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
|
||||
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
|
||||
HearingAidInfo.DeviceMode.MODE_BINAURAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateHearingAidsDevices
|
||||
*
|
||||
* Conditions:
|
||||
* 1) LeAudio hearing aid
|
||||
* 2) Invalid audio location and device type
|
||||
* Result:
|
||||
* Do not set hearing aid info to the device.
|
||||
*/
|
||||
@Test
|
||||
public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
|
||||
when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
|
||||
when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
|
||||
when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
|
||||
mHearingAidDeviceManager.updateHearingAidsDevices();
|
||||
|
||||
verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When first hearing aid device is connected, to process it same as other generic devices.
|
||||
* No need to process it.
|
||||
*/
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_connected_singleDevice_returnFalse() {
|
||||
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice1, BluetoothProfile.STATE_CONNECTED)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When a new hearing aid device is connected, to set it as sub device by onHiSyncIdChanged().
|
||||
* And, to verify new device is not in CachedDevices list.
|
||||
*/
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_connected_newDevice_verifySubDevice() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(true);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isTrue();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isNull();
|
||||
mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
assertThat(mCachedDeviceManager.mCachedDevices.contains(mCachedDevice2)).isFalse();
|
||||
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
|
||||
verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When sub device is disconnected, do nothing and return False for main device connected event
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
onProfileConnectionStateChanged_connected_mainDevice_subDeviceDisconnected_returnFalse() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice1, BluetoothProfile.STATE_CONNECTED)).isFalse();
|
||||
verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When main device is connected, do main device refresh() for sub device connected event
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
onProfileConnectionStateChanged_connected_subDevice_mainDeviceConnected_verifyRefresh() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(true);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice2, BluetoothProfile.STATE_CONNECTED)).isTrue();
|
||||
verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong());
|
||||
verify(mCachedDevice1).refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When main device is disconnected, to verify switch() result for sub device connected
|
||||
* event
|
||||
*/
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_connected_subDevice_mainDeviceDisconnected_switch()
|
||||
{
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice1.isConnected()).thenReturn(false);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice1);
|
||||
assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice2);
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice2, BluetoothProfile.STATE_CONNECTED)).isTrue();
|
||||
|
||||
assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2);
|
||||
assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1);
|
||||
verify(mHearingAidDeviceManager).onHiSyncIdChanged(anyLong());
|
||||
verify(mCachedDevice1).refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When sub device is connected, to verify switch() result for main device disconnected
|
||||
* event
|
||||
*/
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceConnected_switch()
|
||||
{
|
||||
mCachedDevice1.setHearingAidInfo(getLeftAshaHearingAidInfo(HISYNCID1));
|
||||
mCachedDevice2.setHearingAidInfo(getRightAshaHearingAidInfo(HISYNCID1));
|
||||
when(mCachedDevice2.isConnected()).thenReturn(true);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice1);
|
||||
assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice2);
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice1, BluetoothProfile.STATE_DISCONNECTED)).isTrue();
|
||||
|
||||
assertThat(mCachedDevice1.mDevice).isEqualTo(mDevice2);
|
||||
assertThat(mCachedDevice2.mDevice).isEqualTo(mDevice1);
|
||||
assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
|
||||
assertThat(mCachedDevice2.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
|
||||
verify(mCachedDevice1).refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* When sub device is disconnected, do nothing and return False for main device disconnected
|
||||
* event
|
||||
*/
|
||||
@Test
|
||||
public void
|
||||
onProfileConnectionStateChanged_disconnected_mainDevice_subDeviceDisconnected_returnFalse() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.isConnected()).thenReturn(false);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice1, BluetoothProfile.STATE_DISCONNECTED)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test onProfileConnectionStateChangedIfProcessed.
|
||||
* Refresh main device UI for sub device disconnected event
|
||||
*/
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_disconnected_subDevice_verifyRefresh() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(
|
||||
mCachedDevice2, BluetoothProfile.STATE_DISCONNECTED)).isTrue();
|
||||
verify(mCachedDevice1).refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_connected_callSetStrategies() {
|
||||
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
|
||||
mHearingDeviceAttribute);
|
||||
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
|
||||
doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(),
|
||||
eq(mHearingDeviceAttribute), anyInt());
|
||||
|
||||
mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
|
||||
|
||||
verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
|
||||
eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
|
||||
when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
|
||||
mHearingDeviceAttribute);
|
||||
when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
|
||||
doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
|
||||
anyInt());
|
||||
|
||||
mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
|
||||
|
||||
verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
|
||||
eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
|
||||
eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMainDevice() {
|
||||
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
|
||||
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
|
||||
mCachedDevice1.setSubDevice(mCachedDevice2);
|
||||
|
||||
assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).
|
||||
isEqualTo(mCachedDevice1);
|
||||
}
|
||||
|
||||
private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
|
||||
return new HearingAidInfo.Builder()
|
||||
.setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
|
||||
.setHiSyncId(hiSyncId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private HearingAidInfo getRightAshaHearingAidInfo(long hiSyncId) {
|
||||
return new HearingAidInfo.Builder()
|
||||
.setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
|
||||
.setHiSyncId(hiSyncId)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHearingAid;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class HearingAidProfileTest {
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothHearingAid mService;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private HearingAidProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context context = spy(RuntimeEnvironment.application);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
|
||||
mProfile = new HearingAidProfile(context, mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setActiveDevice_returnTrue() {
|
||||
assertThat(mProfile.setActiveDevice(null)).isTrue();
|
||||
assertThat(mProfile.setActiveDevice(mBluetoothDevice)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_isProfileReady() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HEARING_AID, mService);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isTrue();
|
||||
verify(mProfileManager).callServiceConnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceDisconnected_profileNotReady() {
|
||||
mServiceListener.onServiceDisconnected(BluetoothProfile.HEARING_AID);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isFalse();
|
||||
verify(mProfileManager).callServiceDisconnectedListeners();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.bluetooth;
|
||||
|
||||
import static com.android.settingslib.bluetooth.HearingAidStatsLogUtils.CONNECTED_HISTORY_EXPIRED_DAY;
|
||||
import static com.android.settingslib.bluetooth.HearingAidStatsLogUtils.PAIRED_HISTORY_EXPIRED_DAY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HearingAidStatsLogUtilsTest {
|
||||
|
||||
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
|
||||
private static final int TEST_HISTORY_TYPE =
|
||||
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED;
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
|
||||
@Test
|
||||
public void setBondEntryForDevice_addsEntryToDeviceAddressToBondEntryMap() {
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
|
||||
HearingAidStatsLogUtils.setBondEntryForDevice(
|
||||
FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH,
|
||||
mCachedBluetoothDevice);
|
||||
|
||||
final HashMap<String, Integer> map =
|
||||
HearingAidStatsLogUtils.getDeviceAddressToBondEntryMap();
|
||||
assertThat(map.containsKey(TEST_DEVICE_ADDRESS)).isTrue();
|
||||
assertThat(map.get(TEST_DEVICE_ADDRESS)).isEqualTo(
|
||||
FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logHearingAidInfo_removesEntryFromDeviceAddressToBondEntryMap() {
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
|
||||
HearingAidStatsLogUtils.setBondEntryForDevice(
|
||||
FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH,
|
||||
mCachedBluetoothDevice);
|
||||
HearingAidStatsLogUtils.logHearingAidInfo(mCachedBluetoothDevice);
|
||||
|
||||
final HashMap<String, Integer> map =
|
||||
HearingAidStatsLogUtils.getDeviceAddressToBondEntryMap();
|
||||
assertThat(map.containsKey(TEST_DEVICE_ADDRESS)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCurrentTimeToHistory_addNewData() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
final long lastData = todayStartOfDay - TimeUnit.DAYS.toMillis(6);
|
||||
HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, lastData);
|
||||
|
||||
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
|
||||
|
||||
LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE);
|
||||
assertThat(history).isNotNull();
|
||||
assertThat(history.size()).isEqualTo(2);
|
||||
}
|
||||
@Test
|
||||
public void addCurrentTimeToHistory_skipSameDateData() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, todayStartOfDay);
|
||||
|
||||
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
|
||||
|
||||
LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE);
|
||||
assertThat(history).isNotNull();
|
||||
assertThat(history.size()).isEqualTo(1);
|
||||
assertThat(history.getFirst()).isEqualTo(todayStartOfDay);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCurrentTimeToHistory_cleanUpExpiredData() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
final long expiredData = todayStartOfDay - TimeUnit.DAYS.toMillis(6) - 1;
|
||||
HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, expiredData);
|
||||
|
||||
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
|
||||
|
||||
LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE);
|
||||
assertThat(history).isNotNull();
|
||||
assertThat(history.size()).isEqualTo(1);
|
||||
assertThat(history.getFirst()).isNotEqualTo(expiredData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserCategory_hearingAidsUser() {
|
||||
prepareHearingAidsUserHistory();
|
||||
|
||||
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
|
||||
HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserCategory_newHearingAidsUser() {
|
||||
prepareHearingAidsUserHistory();
|
||||
prepareNewUserHistory();
|
||||
|
||||
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
|
||||
HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_AIDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserCategory_hearingDevicesUser() {
|
||||
prepareHearingDevicesUserHistory();
|
||||
|
||||
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
|
||||
HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserCategory_newHearingDevicesUser() {
|
||||
prepareHearingDevicesUserHistory();
|
||||
prepareNewUserHistory();
|
||||
|
||||
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
|
||||
HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
|
||||
}
|
||||
|
||||
private long convertToStartOfDayTime(long timestamp) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
LocalDate date = Instant.ofEpochMilli(timestamp).atZone(zoneId).toLocalDate();
|
||||
return date.atStartOfDay(zoneId).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
private void prepareHearingAidsUserHistory() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
|
||||
final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
|
||||
HearingAidStatsLogUtils.addToHistory(mContext,
|
||||
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareHearingDevicesUserHistory() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
|
||||
final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
|
||||
HearingAidStatsLogUtils.addToHistory(mContext,
|
||||
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareNewUserHistory() {
|
||||
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
|
||||
final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(PAIRED_HISTORY_EXPIRED_DAY - 1);
|
||||
HearingAidStatsLogUtils.addToHistory(mContext,
|
||||
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
|
||||
HearingAidStatsLogUtils.addToHistory(mContext,
|
||||
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadsetClient;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class HfpClientProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothHeadsetClient mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private HfpClientProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new HfpClientProfile(RuntimeEnvironment.application,
|
||||
mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHidDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class HidDeviceProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothHidDevice mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private HidDeviceProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new HidDeviceProfile(RuntimeEnvironment.application,
|
||||
mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothA2dp;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothHearingAid;
|
||||
import android.bluetooth.BluetoothPan;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class LocalBluetoothProfileManagerTest {
|
||||
private final static long HISYNCID = 10;
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private BluetoothEventManager mEventManager;
|
||||
@Mock
|
||||
private BluetoothDevice mDevice;
|
||||
@Mock
|
||||
private CachedBluetoothDevice mCachedBluetoothDevice;
|
||||
|
||||
private Context mContext;
|
||||
private Intent mIntent;
|
||||
private LocalBluetoothAdapter mLocalBluetoothAdapter;
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mLocalBluetoothAdapter = LocalBluetoothAdapter.getInstance();
|
||||
mEventManager = spy(new BluetoothEventManager(mLocalBluetoothAdapter, mDeviceManager,
|
||||
mContext, /* handler= */ null, /* userHandle= */ null));
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
when(mDeviceManager.findDevice(mDevice)).thenReturn(mCachedBluetoothDevice);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
|
||||
mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
|
||||
mDeviceManager, mEventManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify HID and HID Device profiles are not null without running updateUuids()
|
||||
*/
|
||||
@Test
|
||||
public void constructor_initiateHidAndHidDeviceProfile() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.HID_HOST, BluetoothProfile.HID_DEVICE}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
assertThat(mProfileManager.getHidProfile()).isNotNull();
|
||||
assertThat(mProfileManager.getHidDeviceProfile()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_doNotUpdateProfiles() {
|
||||
mProfileManager = spy(new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
|
||||
mDeviceManager, mEventManager));
|
||||
|
||||
verify(mProfileManager, never()).updateLocalProfiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify updateLocalProfiles() for a local A2DP source adds A2dpProfile
|
||||
*/
|
||||
@Test
|
||||
public void updateLocalProfiles_addA2dpToLocalProfiles() {
|
||||
mProfileManager.updateLocalProfiles();
|
||||
assertThat(mProfileManager.getA2dpProfile()).isNull();
|
||||
assertThat(mProfileManager.getHeadsetProfile()).isNull();
|
||||
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.A2DP}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
assertThat(mProfileManager.getA2dpProfile()).isNotNull();
|
||||
assertThat(mProfileManager.getHeadsetProfile()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify updateProfiles() for a remote HID device updates profiles and removedProfiles
|
||||
*/
|
||||
@Test
|
||||
public void updateProfiles_addHidProfileForRemoteDevice() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.HID_HOST}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
ParcelUuid[] uuids = new ParcelUuid[]{BluetoothUuid.HID};
|
||||
ParcelUuid[] localUuids = new ParcelUuid[]{};
|
||||
List<LocalBluetoothProfile> profiles = new ArrayList<>();
|
||||
List<LocalBluetoothProfile> removedProfiles = new ArrayList<>();
|
||||
|
||||
mProfileManager.updateProfiles(uuids, localUuids, profiles, removedProfiles, false,
|
||||
mDevice);
|
||||
|
||||
assertThat(mProfileManager.getHidProfile()).isNotNull();
|
||||
assertThat(profiles.contains(mProfileManager.getHidProfile())).isTrue();
|
||||
assertThat(removedProfiles.contains(mProfileManager.getHidProfile())).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED with uuid intent will dispatch to
|
||||
* profile connection state changed callback
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receiveA2dpConnectionStateChanged_shouldDispatchCallback() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.A2DP}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
mIntent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mEventManager).dispatchProfileConnectionStateChanged(
|
||||
mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED with uuid intent will dispatch to
|
||||
* profile connection state changed callback
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receiveHeadsetConnectionStateChanged_shouldDispatchCallback() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.HEADSET}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
mIntent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mEventManager).dispatchProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEADSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED with uuid intent will dispatch to
|
||||
* CachedBluetoothDeviceManager method
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.HEARING_AID}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
when(mCachedBluetoothDevice.getHiSyncId()).thenReturn(HISYNCID);
|
||||
|
||||
mIntent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mDeviceManager).onProfileConnectionStateChangedIfProcessed(mCachedBluetoothDevice,
|
||||
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothPan.ACTION_CONNECTION_STATE_CHANGED intent with uuid will dispatch to
|
||||
* profile connection state changed callback
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receivePanConnectionStateChanged_shouldNotDispatchCallback() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.PAN}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
mIntent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mEventManager).dispatchProfileConnectionStateChanged(
|
||||
any(CachedBluetoothDevice.class), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothPan.ACTION_CONNECTION_STATE_CHANGED intent without uuids will not dispatch to
|
||||
* handler and refresh CachedBluetoothDevice
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receivePanConnectionStateChangedWithoutProfile_shouldNotRefresh
|
||||
() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(null);
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
mIntent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mCachedBluetoothDevice, never()).refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify BluetoothPan.ACTION_CONNECTION_STATE_CHANGED intent with uuids will dispatch to
|
||||
* handler and refresh CachedBluetoothDevice
|
||||
*/
|
||||
@Test
|
||||
public void stateChangedHandler_receivePanConnectionStateChangedWithProfile_shouldRefresh() {
|
||||
mShadowBluetoothAdapter.setSupportedProfiles(generateList(
|
||||
new int[] {BluetoothProfile.PAN}));
|
||||
mProfileManager.updateLocalProfiles();
|
||||
|
||||
mIntent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
|
||||
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING);
|
||||
mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
mContext.sendBroadcast(mIntent);
|
||||
|
||||
verify(mCachedBluetoothDevice).refresh();
|
||||
}
|
||||
|
||||
private List<Integer> generateList(int[] profiles) {
|
||||
if (profiles == null) {
|
||||
return null;
|
||||
}
|
||||
final List<Integer> profileList = new ArrayList<>(profiles.length);
|
||||
for (int profile : profiles) {
|
||||
profileList.add(profile);
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothMapClient;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class MapClientProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothMapClient mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private MapClientProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new MapClientProfile(RuntimeEnvironment.application,
|
||||
mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.MAP_CLIENT, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothPbapClient;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = ShadowBluetoothAdapter.class)
|
||||
public class PbapClientProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothPbapClient mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private PbapClientProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new PbapClientProfile(RuntimeEnvironment.application,
|
||||
mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.PBAP_CLIENT, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.bluetooth;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothSap;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class SapProfileTest {
|
||||
|
||||
@Mock
|
||||
private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock
|
||||
private BluetoothSap mService;
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private SapProfile mProfile;
|
||||
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||
mProfile = new SapProfile(RuntimeEnvironment.application, mDeviceManager, mProfileManager);
|
||||
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.SAP, mService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_shouldReturnConnectionState() {
|
||||
when(mService.getConnectionState(mBluetoothDevice)).
|
||||
thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice)).
|
||||
isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.settingslib.bluetooth;
|
||||
|
||||
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
|
||||
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothVolumeControl;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadow.api.Shadow;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
||||
public class VolumeControlProfileTest {
|
||||
|
||||
private static final int TEST_VOLUME_OFFSET = 10;
|
||||
private static final int TEST_VOLUME_VALUE = 10;
|
||||
|
||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Mock private CachedBluetoothDeviceManager mDeviceManager;
|
||||
@Mock private LocalBluetoothProfileManager mProfileManager;
|
||||
@Mock private BluetoothDevice mBluetoothDevice;
|
||||
@Mock private BluetoothVolumeControl mService;
|
||||
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private BluetoothProfile.ServiceListener mServiceListener;
|
||||
private VolumeControlProfile mProfile;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mProfile = new VolumeControlProfile(mContext, mDeviceManager, mProfileManager);
|
||||
final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
|
||||
final ShadowBluetoothAdapter shadowBluetoothAdapter =
|
||||
Shadow.extract(bluetoothManager.getAdapter());
|
||||
mServiceListener = shadowBluetoothAdapter.getServiceListener();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceConnected_isProfileReady() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isTrue();
|
||||
verify(mProfileManager).callServiceConnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onServiceDisconnected_isProfileNotReady() {
|
||||
mServiceListener.onServiceDisconnected(BluetoothProfile.VOLUME_CONTROL);
|
||||
|
||||
assertThat(mProfile.isProfileReady()).isFalse();
|
||||
verify(mProfileManager).callServiceDisconnectedListeners();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionStatus_returnCorrectConnectionState() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionState(mBluetoothDevice))
|
||||
.thenReturn(BluetoothProfile.STATE_CONNECTED);
|
||||
|
||||
assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
|
||||
.isEqualTo(BluetoothProfile.STATE_CONNECTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_connectionPolicyAllowed_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
|
||||
assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_connectionPolicyForbidden_returnFalse() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
|
||||
assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionPolicy_returnCorrectConnectionPolicy() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
|
||||
assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
|
||||
.isEqualTo(CONNECTION_POLICY_ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.getConnectionPolicy(mBluetoothDevice))
|
||||
.thenReturn(CONNECTION_POLICY_FORBIDDEN);
|
||||
when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectedDevices_returnCorrectList() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
int[] connectedStates =
|
||||
new int[] {
|
||||
BluetoothProfile.STATE_CONNECTED,
|
||||
BluetoothProfile.STATE_CONNECTING,
|
||||
BluetoothProfile.STATE_DISCONNECTING
|
||||
};
|
||||
List<BluetoothDevice> connectedList =
|
||||
Arrays.asList(mBluetoothDevice, mBluetoothDevice, mBluetoothDevice);
|
||||
when(mService.getDevicesMatchingConnectionStates(connectedStates))
|
||||
.thenReturn(connectedList);
|
||||
|
||||
assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerCallback_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
|
||||
final Executor executor = (command -> new Thread(command).start());
|
||||
final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
|
||||
mProfile.registerCallback(executor, callback);
|
||||
|
||||
verify(mService).registerCallback(executor, callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterCallback_verifyIsCalled() {
|
||||
final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
|
||||
mProfile.unregisterCallback(callback);
|
||||
|
||||
verify(mService).unregisterCallback(callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVolumeOffset_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
|
||||
mProfile.setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
|
||||
|
||||
verify(mService).setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeviceVolume_verifyIsCalled() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
|
||||
mProfile.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
|
||||
|
||||
verify(mService)
|
||||
.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
|
||||
|
||||
final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
|
||||
|
||||
verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
|
||||
assertThat(available).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isVolumeOffsetAvailable_verifyIsCalledAndReturnFalse() {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
|
||||
when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(false);
|
||||
|
||||
final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
|
||||
|
||||
verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
|
||||
assertThat(available).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.collapsingtoolbar.widget;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settingslib.collapsingtoolbar.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Tests for {@link CollapsingCoordinatorLayout}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CollapsingCoordinatorLayoutTest {
|
||||
private static final String TEXT_HELLO_WORLD = "Hello World!";
|
||||
private static final String TEST_TITLE = "RENO NAKAMURA";
|
||||
|
||||
private TestActivity mActivity;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mActivity = Robolectric.buildActivity(TestActivity.class).create().get();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreate_childViewsNumberShouldBeTwo() {
|
||||
CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
|
||||
|
||||
assertThat(layout.getChildCount()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
|
||||
CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
|
||||
View contentFrameView = layout.findViewById(R.id.content_frame);
|
||||
|
||||
TextView textView = contentFrameView.findViewById(com.android.settingslib.robotests.R.id.text_hello_world);
|
||||
|
||||
assertThat(textView).isNotNull();
|
||||
assertThat(textView.getText().toString()).isEqualTo(TEXT_HELLO_WORLD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initSettingsStyleToolBar_assignedTitle() {
|
||||
CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
|
||||
|
||||
layout.initSettingsStyleToolBar(mActivity, TEST_TITLE);
|
||||
|
||||
assertThat(layout.getCollapsingToolbarLayout().getTitle().toString()).isEqualTo(TEST_TITLE);
|
||||
}
|
||||
|
||||
public static class TestActivity extends Activity {
|
||||
private CollapsingCoordinatorLayout mCollapsingCoordinatorLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTheme(android.R.style.Theme_Light_NoTitleBar);
|
||||
setContentView(com.android.settingslib.robotests.R.layout.collapsing_test_layout);
|
||||
mCollapsingCoordinatorLayout = findViewById(com.android.settingslib.robotests.R.id.id_collapsing_test);
|
||||
}
|
||||
|
||||
public CollapsingCoordinatorLayout getCollapsingCoordinatorLayout() {
|
||||
return mCollapsingCoordinatorLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.connectivity;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ConnectivitySubsystemsRecoveryManagerTest {
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Spy
|
||||
Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Spy
|
||||
Handler mMainHandler = ApplicationProvider.getApplicationContext().getMainThreadHandler();
|
||||
@Mock
|
||||
PackageManager mPackageManager;
|
||||
|
||||
ConnectivitySubsystemsRecoveryManager mConnectivitySubsystemsRecoveryManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTrackingWifiRestart_hasNoWifiFeature_shouldNotCrash() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(false);
|
||||
mConnectivitySubsystemsRecoveryManager =
|
||||
new ConnectivitySubsystemsRecoveryManager(mContext, mMainHandler);
|
||||
|
||||
mConnectivitySubsystemsRecoveryManager.startTrackingWifiRestart();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopTrackingWifiRestart_hasNoWifiFeature_shouldNotCrash() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(false);
|
||||
mConnectivitySubsystemsRecoveryManager =
|
||||
new ConnectivitySubsystemsRecoveryManager(mContext, mMainHandler);
|
||||
|
||||
mConnectivitySubsystemsRecoveryManager.stopTrackingWifiRestart();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTrackingTelephonyRestart_hasNoTelephonyFeature_shouldNotCrash() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
|
||||
mConnectivitySubsystemsRecoveryManager =
|
||||
new ConnectivitySubsystemsRecoveryManager(mContext, mMainHandler);
|
||||
|
||||
mConnectivitySubsystemsRecoveryManager.startTrackingTelephonyRestart();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopTrackingTelephonyRestart_hasNoTelephonyFeature_shouldNotCrash() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
|
||||
mConnectivitySubsystemsRecoveryManager =
|
||||
new ConnectivitySubsystemsRecoveryManager(mContext, mMainHandler);
|
||||
|
||||
mConnectivitySubsystemsRecoveryManager.stopTrackingTelephonyRestart();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
package com.android.settingslib.core;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AbstractPreferenceControllerTest {
|
||||
|
||||
private static final String KEY_PREF = "test_pref";
|
||||
|
||||
@Mock
|
||||
private PreferenceScreen mScreen;
|
||||
|
||||
private Context mContext;
|
||||
private Preference mPreference;
|
||||
private TestPrefController mTestPrefController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPreference = new Preference(mContext);
|
||||
mPreference.setKey(KEY_PREF);
|
||||
when(mScreen.findPreference(KEY_PREF)).thenReturn(mPreference);
|
||||
mTestPrefController = new TestPrefController(mContext, KEY_PREF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPref_ifAvailable() {
|
||||
mTestPrefController.isAvailable = true;
|
||||
|
||||
mTestPrefController.displayPreference(mScreen);
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayPref_noKey_shouldDoNothing() {
|
||||
mTestPrefController.isAvailable = true;
|
||||
|
||||
mTestPrefController.displayPreference(mScreen);
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVisible_prefIsVisible_shouldSetToVisible() {
|
||||
mTestPrefController.setVisible(mScreen, KEY_PREF, true /* visible */);
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVisible_prefNotVisible_shouldSetToInvisible() {
|
||||
mTestPrefController.setVisible(mScreen, KEY_PREF, false /* visible */);
|
||||
|
||||
assertThat(mPreference.isVisible()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doNotDisplayPref_ifNotAvailable() {
|
||||
mTestPrefController.isAvailable = false;
|
||||
|
||||
mTestPrefController.displayPreference(mScreen);
|
||||
|
||||
assertThat(mPreference.isVisible()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateState_hasSummary_shouldSetSummary() {
|
||||
mTestPrefController.updateState(mPreference);
|
||||
|
||||
assertThat(mPreference.getSummary()).isEqualTo(TestPrefController.TEST_SUMMARY);
|
||||
}
|
||||
|
||||
private static class TestPrefController extends AbstractPreferenceController {
|
||||
private static final CharSequence TEST_SUMMARY = "Test";
|
||||
|
||||
public boolean isAvailable;
|
||||
private final String mPrefKey;
|
||||
|
||||
TestPrefController(Context context, String key) {
|
||||
super(context);
|
||||
mPrefKey = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlePreferenceTreeClick(Preference preference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return mPrefKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return TEST_SUMMARY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.settingslib.core.instrumentation;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MetricsFeatureProviderTest {
|
||||
@Mock
|
||||
private LogWriter mLogWriter;
|
||||
|
||||
private Context mContext;
|
||||
private MetricsFeatureProvider mProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mProvider = new MetricsFeatureProvider();
|
||||
List<LogWriter> writers = new ArrayList<>();
|
||||
writers.add(mLogWriter);
|
||||
ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logClickedPreference_preferenceEmpty_shouldNotLog() {
|
||||
final boolean loggable = mProvider.logClickedPreference(null /* preference */,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isFalse();
|
||||
verifyNoMoreInteractions(mLogWriter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logClickedPreference_preferenceHasKey_shouldLog() {
|
||||
final String key = "abc";
|
||||
final Preference preference = new Preference(mContext);
|
||||
preference.setKey(key);
|
||||
|
||||
final boolean loggable = mProvider.logClickedPreference(preference,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logClickedPreference_preferenceHasIntent_shouldLog() {
|
||||
final Preference preference = new Preference(mContext);
|
||||
final Intent intent = new Intent(Intent.ACTION_ASSIST);
|
||||
preference.setIntent(intent);
|
||||
|
||||
final boolean loggable = mProvider.logClickedPreference(preference,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logClickedPreference_preferenceHasFragment_shouldLog() {
|
||||
final Preference preference = new Preference(mContext);
|
||||
final String fragment = "com.android.settings.tts.TextToSpeechSettings";
|
||||
preference.setFragment(fragment);
|
||||
|
||||
final boolean loggable = mProvider.logClickedPreference(preference,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, fragment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logStartedIntent_intentEmpty_shouldNotLog() {
|
||||
final boolean loggable = mProvider.logStartedIntent(null /* intent */,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isFalse();
|
||||
verifyNoMoreInteractions(mLogWriter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logStartedIntent_intentHasNoComponent_shouldLog() {
|
||||
final Intent intent = new Intent(Intent.ACTION_ASSIST);
|
||||
|
||||
final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logStartedIntent_intentIsExternal_shouldLog() {
|
||||
final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
|
||||
|
||||
final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logStartedIntentWithProfile_isPersonalProfile_shouldTagPersonal() {
|
||||
final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
|
||||
|
||||
final boolean loggable = mProvider.logStartedIntentWithProfile(intent,
|
||||
MetricsEvent.SETTINGS_GESTURES, false);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/personal");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logStartedIntentWithProfile_isWorkProfile_shouldTagWork() {
|
||||
final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
|
||||
|
||||
final boolean loggable = mProvider.logStartedIntentWithProfile(intent,
|
||||
MetricsEvent.SETTINGS_GESTURES, true);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/work");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAttribution_noActivity_shouldReturnUnknown() {
|
||||
assertThat(mProvider.getAttribution(null /* activity */))
|
||||
.isEqualTo(SettingsEnums.PAGE_UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void getAttribution_notSet_shouldReturnUnknown() {
|
||||
final Activity activity = Robolectric.setupActivity(Activity.class);
|
||||
|
||||
assertThat(mProvider.getAttribution(activity))
|
||||
.isEqualTo(SettingsEnums.PAGE_UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAttribution_set_shouldReturnAttribution() {
|
||||
final Intent intent = new Intent()
|
||||
.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 100);
|
||||
|
||||
final Activity activity = Robolectric.buildActivity(Activity.class, intent).create().get();
|
||||
|
||||
assertThat(mProvider.getAttribution(activity)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logSettingsTileClick_hasKey_shouldLog() {
|
||||
final String key = "abc";
|
||||
final boolean loggable = mProvider.logSettingsTileClick(key,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logSettingsTileClick_keyEmpty_shouldNotLog() {
|
||||
final String key = "";
|
||||
boolean loggable = mProvider.logSettingsTileClick(key,
|
||||
MetricsEvent.SETTINGS_GESTURES);
|
||||
|
||||
assertThat(loggable).isFalse();
|
||||
verifyNoMoreInteractions(mLogWriter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
|
||||
final String key = "abc";
|
||||
final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
|
||||
MetricsEvent.SETTINGS_GESTURES, false);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
|
||||
final String key = "abc";
|
||||
final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
|
||||
MetricsEvent.SETTINGS_GESTURES, true);
|
||||
|
||||
assertThat(loggable).isTrue();
|
||||
verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.settingslib.core.instrumentation;
|
||||
|
||||
import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE;
|
||||
import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.jank.Cuj.CujType;
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.settingslib.testutils.OverpoweredReflectionHelper;
|
||||
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.annotation.Resetter;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowInteractionJankMonitor.class, SettingsJankMonitorTest.ShadowBuilder.class})
|
||||
public class SettingsJankMonitorTest {
|
||||
private static final String TEST_KEY = "key";
|
||||
|
||||
@Rule
|
||||
public MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private View mView;
|
||||
|
||||
@Mock
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
@Mock
|
||||
private PreferenceGroupAdapter mPreferenceGroupAdapter;
|
||||
|
||||
@Mock
|
||||
private SwitchPreference mSwitchPreference;
|
||||
|
||||
@Mock
|
||||
private ScheduledExecutorService mScheduledExecutorService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowInteractionJankMonitor.reset();
|
||||
when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
|
||||
OverpoweredReflectionHelper
|
||||
.setStaticField(SettingsJankMonitor.class,
|
||||
"scheduledExecutorService",
|
||||
mScheduledExecutorService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectToggleJank() {
|
||||
SettingsJankMonitor.detectToggleJank(TEST_KEY, mView);
|
||||
|
||||
verifyToggleJankMonitored();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectSwitchPreferenceClickJank() {
|
||||
int adapterPosition = 7;
|
||||
when(mRecyclerView.getAdapter()).thenReturn(mPreferenceGroupAdapter);
|
||||
when(mPreferenceGroupAdapter.getPreferenceAdapterPosition(mSwitchPreference))
|
||||
.thenReturn(adapterPosition);
|
||||
when(mRecyclerView.findViewHolderForAdapterPosition(adapterPosition))
|
||||
.thenReturn(new RecyclerView.ViewHolder(mView) {
|
||||
});
|
||||
when(mSwitchPreference.getKey()).thenReturn(TEST_KEY);
|
||||
|
||||
SettingsJankMonitor.detectSwitchPreferenceClickJank(mRecyclerView, mSwitchPreference);
|
||||
|
||||
verifyToggleJankMonitored();
|
||||
}
|
||||
|
||||
private void verifyToggleJankMonitored() {
|
||||
verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).begin(ShadowBuilder.sBuilder);
|
||||
assertThat(ShadowBuilder.sView).isSameInstanceAs(mView);
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mScheduledExecutorService).schedule(runnableCaptor.capture(),
|
||||
eq(MONITORED_ANIMATION_DURATION_MS), eq(TimeUnit.MILLISECONDS));
|
||||
runnableCaptor.getValue().run();
|
||||
verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).end(CUJ_SETTINGS_TOGGLE);
|
||||
}
|
||||
|
||||
@Implements(InteractionJankMonitor.Configuration.Builder.class)
|
||||
static class ShadowBuilder {
|
||||
private static InteractionJankMonitor.Configuration.Builder sBuilder;
|
||||
private static View sView;
|
||||
|
||||
@Resetter
|
||||
public static void reset() {
|
||||
sBuilder = null;
|
||||
sView = null;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public static InteractionJankMonitor.Configuration.Builder withView(
|
||||
@CujType int cuj, @NonNull View view) {
|
||||
assertThat(cuj).isEqualTo(CUJ_SETTINGS_TOGGLE);
|
||||
sView = view;
|
||||
sBuilder = mock(InteractionJankMonitor.Configuration.Builder.class);
|
||||
when(sBuilder.setTag(TEST_KEY)).thenReturn(sBuilder);
|
||||
return sBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.settingslib.core.instrumentation;
|
||||
|
||||
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class SharedPreferenceLoggerTest {
|
||||
|
||||
private static final String TEST_TAG = "tag";
|
||||
private static final String TEST_KEY = "key";
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private MetricsFeatureProvider mMetricsFeature;
|
||||
private SharedPreferencesLogger mSharedPrefLogger;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putInt_shouldNotLogInitialPut() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
editor.putInt(TEST_KEY, 1);
|
||||
editor.putInt(TEST_KEY, 1);
|
||||
editor.putInt(TEST_KEY, 1);
|
||||
editor.putInt(TEST_KEY, 2);
|
||||
editor.putInt(TEST_KEY, 2);
|
||||
editor.putInt(TEST_KEY, 2);
|
||||
editor.putInt(TEST_KEY, 2);
|
||||
|
||||
verify(mMetricsFeature, times(6)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
|
||||
eq(TEST_KEY),
|
||||
anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putBoolean_shouldNotLogInitialPut() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
editor.putBoolean(TEST_KEY, true);
|
||||
editor.putBoolean(TEST_KEY, true);
|
||||
editor.putBoolean(TEST_KEY, false);
|
||||
editor.putBoolean(TEST_KEY, false);
|
||||
editor.putBoolean(TEST_KEY, false);
|
||||
|
||||
|
||||
verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
|
||||
TEST_KEY,
|
||||
1);
|
||||
verify(mMetricsFeature, times(3)).changed(SettingsEnums.PAGE_UNKNOWN,
|
||||
TEST_KEY,
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putLong_shouldNotLogInitialPut() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, 2);
|
||||
|
||||
verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
|
||||
eq(TEST_KEY),
|
||||
anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putLong_biggerThanIntMax_shouldLogIntMax() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
final long veryBigNumber = 500L + Integer.MAX_VALUE;
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, veryBigNumber);
|
||||
|
||||
verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
|
||||
TEST_KEY,
|
||||
Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putLong_smallerThanIntMin_shouldLogIntMin() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
final long veryNegativeNumber = -500L + Integer.MIN_VALUE;
|
||||
editor.putLong(TEST_KEY, 1);
|
||||
editor.putLong(TEST_KEY, veryNegativeNumber);
|
||||
|
||||
verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
|
||||
TEST_KEY, Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putFloat_shouldNotLogInitialPut() {
|
||||
final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
|
||||
editor.putFloat(TEST_KEY, 1);
|
||||
editor.putFloat(TEST_KEY, 1);
|
||||
editor.putFloat(TEST_KEY, 1);
|
||||
editor.putFloat(TEST_KEY, 1);
|
||||
editor.putFloat(TEST_KEY, 2);
|
||||
|
||||
verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
|
||||
eq(TEST_KEY),
|
||||
anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logPackage_shouldUseLogPackageApi() {
|
||||
mSharedPrefLogger.logPackageName("key", "com.android.settings");
|
||||
verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
|
||||
ACTION_SETTINGS_PREFERENCE_CHANGE,
|
||||
SettingsEnums.PAGE_UNKNOWN,
|
||||
"key:com.android.settings",
|
||||
0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putString_shouldNotLogInitialPut() {
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "1");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "2");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "62");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "0");
|
||||
|
||||
verify(mMetricsFeature, times(3)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
|
||||
eq(TEST_KEY),
|
||||
anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putString_shouldNotLogAnyNonIntegers() {
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "string");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "not an int");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "1.234f");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "4.2");
|
||||
mSharedPrefLogger.logValue(TEST_KEY, "3.0");
|
||||
|
||||
verify(mMetricsFeature, times(0)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
|
||||
eq(TEST_KEY),
|
||||
anyInt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.settingslib.core.instrumentation;
|
||||
|
||||
import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VisibilityLoggerMixinTest {
|
||||
|
||||
@Mock
|
||||
private MetricsFeatureProvider mMetricsFeature;
|
||||
|
||||
private VisibilityLoggerMixin mMixin;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLogVisibleOnResume() {
|
||||
mMixin.onResume();
|
||||
|
||||
verify(mMetricsFeature, times(1))
|
||||
.visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN),
|
||||
eq(TestInstrumentable.TEST_METRIC), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLogVisibleWithSource() {
|
||||
final Intent sourceIntent = new Intent()
|
||||
.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
|
||||
MetricsProto.MetricsEvent.SETTINGS_GESTURES);
|
||||
final Activity activity = mock(Activity.class);
|
||||
when(activity.getIntent()).thenReturn(sourceIntent);
|
||||
mMixin.setSourceMetricsCategory(activity);
|
||||
mMixin.onResume();
|
||||
|
||||
verify(mMetricsFeature, times(1))
|
||||
.visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES),
|
||||
eq(TestInstrumentable.TEST_METRIC), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLogHideOnPause() {
|
||||
mMixin.onPause();
|
||||
|
||||
verify(mMetricsFeature, times(1))
|
||||
.hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotLogIfMetricsFeatureIsNull() {
|
||||
mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, null);
|
||||
mMixin.onResume();
|
||||
mMixin.onPause();
|
||||
|
||||
verify(mMetricsFeature, never())
|
||||
.hidden(nullable(Context.class), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotLogIfMetricsCategoryIsUnknown() {
|
||||
mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature);
|
||||
|
||||
mMixin.onResume();
|
||||
mMixin.onPause();
|
||||
|
||||
verify(mMetricsFeature, never())
|
||||
.hidden(nullable(Context.class), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void activityShouldBecomeVisibleAndHide() {
|
||||
ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
|
||||
TestActivity testActivity = ac.get();
|
||||
MockitoAnnotations.initMocks(testActivity);
|
||||
ac.create().start().resume();
|
||||
verify(testActivity.mMetricsFeatureProvider, times(1)).visible(any(), anyInt(), anyInt(),
|
||||
anyInt());
|
||||
ac.pause().stop().destroy();
|
||||
verify(testActivity.mMetricsFeatureProvider, times(1)).hidden(any(), anyInt(), anyInt());
|
||||
}
|
||||
|
||||
public static class TestActivity extends FragmentActivity {
|
||||
@Mock
|
||||
MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
VisibilityLoggerMixin mixin = new VisibilityLoggerMixin(
|
||||
TestInstrumentable.TEST_METRIC, mMetricsFeatureProvider);
|
||||
getLifecycle().addObserver(mixin);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
private final class TestInstrumentable implements Instrumentable {
|
||||
|
||||
private static final int TEST_METRIC = 12345;
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return TEST_METRIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.settingslib.core.lifecycle;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import static com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin.SECURE_OVERLAY_SETTINGS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public class HideNonSystemOverlayMixinTest {
|
||||
|
||||
private ActivityController<TestActivity> mActivityController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mActivityController = Robolectric.buildActivity(TestActivity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startActivity_shouldHideNonSystemOverlay() {
|
||||
mActivityController.setup();
|
||||
TestActivity activity = mActivityController.get();
|
||||
|
||||
// Activity start: HIDE_NON_SYSTEM_OVERLAY should be set.
|
||||
final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
|
||||
assertThat(attrs.privateFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
|
||||
.isNotEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopActivity_shouldUnhideNonSystemOverlay() {
|
||||
mActivityController.setup().stop();
|
||||
TestActivity activity = mActivityController.get();
|
||||
|
||||
final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
|
||||
assertThat(attrs.privateFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
|
||||
.isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_isAllowedOverlaySettings_returnFalse() {
|
||||
mActivityController.setup();
|
||||
final TestActivity activity = mActivityController.get();
|
||||
Settings.Secure.putInt(activity.getContentResolver(),
|
||||
SECURE_OVERLAY_SETTINGS, 1);
|
||||
|
||||
assertThat(new HideNonSystemOverlayMixin(activity).isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnabled_isNotAllowedOverlaySettings_returnTrue() {
|
||||
mActivityController.setup();
|
||||
TestActivity activity = mActivityController.get();
|
||||
Settings.Secure.putInt(activity.getContentResolver(),
|
||||
SECURE_OVERLAY_SETTINGS, 0);
|
||||
|
||||
assertThat(new HideNonSystemOverlayMixin(activity).isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
public static class TestActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTheme(androidx.appcompat.R.style.Theme_AppCompat);
|
||||
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package com.android.settingslib.core.lifecycle;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.Event.ON_START;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import com.android.settingslib.core.lifecycle.events.OnAttach;
|
||||
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
|
||||
import com.android.settingslib.core.lifecycle.events.OnDestroy;
|
||||
import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;
|
||||
import com.android.settingslib.core.lifecycle.events.OnPause;
|
||||
import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu;
|
||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ActivityController;
|
||||
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class LifecycleTest {
|
||||
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private Lifecycle mLifecycle;
|
||||
|
||||
public static class TestDialogFragment extends ObservableDialogFragment {
|
||||
|
||||
final TestObserver mFragObserver;
|
||||
|
||||
private TestDialogFragment() {
|
||||
mFragObserver = new TestObserver();
|
||||
mLifecycle.addObserver(mFragObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestFragment extends ObservableFragment {
|
||||
|
||||
final TestObserver mFragObserver;
|
||||
|
||||
public TestFragment() {
|
||||
mFragObserver = new TestObserver();
|
||||
getSettingsLifecycle().addObserver(mFragObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestActivity extends ObservableActivity {
|
||||
|
||||
final TestObserver mActObserver;
|
||||
|
||||
public TestActivity() {
|
||||
mActObserver = new TestObserver();
|
||||
getSettingsLifecycle().addObserver(mActObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LinearLayout view = new LinearLayout(this);
|
||||
view.setId(1);
|
||||
|
||||
setContentView(view);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestObserver implements LifecycleObserver, OnAttach, OnStart, OnResume,
|
||||
OnPause, OnStop, OnDestroy, OnCreateOptionsMenu, OnPrepareOptionsMenu,
|
||||
OnOptionsItemSelected {
|
||||
|
||||
boolean mOnAttachObserved;
|
||||
boolean mOnStartObserved;
|
||||
boolean mOnResumeObserved;
|
||||
boolean mOnPauseObserved;
|
||||
boolean mOnStopObserved;
|
||||
boolean mOnDestroyObserved;
|
||||
boolean mOnCreateOptionsMenuObserved;
|
||||
boolean mOnPrepareOptionsMenuObserved;
|
||||
boolean mOnOptionsItemSelectedObserved;
|
||||
|
||||
@Override
|
||||
public void onAttach() {
|
||||
mOnAttachObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
mOnStartObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
mOnPauseObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
mOnResumeObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mOnStopObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mOnDestroyObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
mOnCreateOptionsMenuObserved = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
mOnOptionsItemSelectedObserved = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
mOnPrepareOptionsMenuObserved = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mLifecycleOwner = () -> mLifecycle;
|
||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/188888268")
|
||||
public void runThroughActivityLifecycles_shouldObserveEverything() {
|
||||
ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
|
||||
TestActivity activity = ac.setup().get();
|
||||
|
||||
assertThat(activity.mActObserver.mOnStartObserved).isTrue();
|
||||
assertThat(activity.mActObserver.mOnResumeObserved).isTrue();
|
||||
activity.onCreateOptionsMenu(null);
|
||||
assertThat(activity.mActObserver.mOnCreateOptionsMenuObserved).isTrue();
|
||||
activity.onPrepareOptionsMenu(null);
|
||||
assertThat(activity.mActObserver.mOnPrepareOptionsMenuObserved).isTrue();
|
||||
activity.onOptionsItemSelected(null);
|
||||
assertThat(activity.mActObserver.mOnOptionsItemSelectedObserved).isTrue();
|
||||
ac.pause();
|
||||
assertThat(activity.mActObserver.mOnPauseObserved).isTrue();
|
||||
ac.stop();
|
||||
assertThat(activity.mActObserver.mOnStopObserved).isTrue();
|
||||
ac.destroy();
|
||||
assertThat(activity.mActObserver.mOnDestroyObserved).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/188888268")
|
||||
public void runThroughDialogFragmentLifecycles_shouldObserveEverything() {
|
||||
final TestDialogFragment fragment = new TestDialogFragment();
|
||||
FragmentController.setupFragment(fragment);
|
||||
|
||||
fragment.onCreateOptionsMenu(null, null);
|
||||
fragment.onPrepareOptionsMenu(null);
|
||||
fragment.onOptionsItemSelected(null);
|
||||
assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
|
||||
|
||||
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
|
||||
fragment.onPause();
|
||||
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
|
||||
fragment.onStop();
|
||||
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
|
||||
fragment.onDestroy();
|
||||
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/188888268")
|
||||
public void runThroughFragmentLifecycles_shouldObserveEverything() {
|
||||
final TestFragment fragment = new TestFragment();
|
||||
FragmentController.setupFragment(fragment);
|
||||
|
||||
fragment.onCreateOptionsMenu(null, null);
|
||||
fragment.onPrepareOptionsMenu(null);
|
||||
fragment.onOptionsItemSelected(null);
|
||||
assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
|
||||
|
||||
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
|
||||
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
|
||||
fragment.onPause();
|
||||
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
|
||||
fragment.onStop();
|
||||
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
|
||||
fragment.onDestroy();
|
||||
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObserverDuringObserve_shoudNotCrash() {
|
||||
mLifecycle.addObserver(new OnStartObserver(mLifecycle));
|
||||
mLifecycle.handleLifecycleEvent(ON_START);
|
||||
}
|
||||
|
||||
private static class OptionItemAccepter implements LifecycleObserver, OnOptionsItemSelected {
|
||||
private boolean mWasCalled = false;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
mWasCalled = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/188888268")
|
||||
public void onOptionItemSelectedShortCircuitsIfAnObserverHandlesTheMenuItem() {
|
||||
final TestFragment fragment = new TestFragment();
|
||||
FragmentController.setupFragment(fragment);
|
||||
|
||||
final OptionItemAccepter accepter = new OptionItemAccepter();
|
||||
fragment.getLifecycle().addObserver(accepter);
|
||||
|
||||
|
||||
fragment.onCreateOptionsMenu(null, null);
|
||||
fragment.onPrepareOptionsMenu(null);
|
||||
fragment.onOptionsItemSelected(null);
|
||||
|
||||
assertThat(accepter.mWasCalled).isFalse();
|
||||
}
|
||||
|
||||
private class OnStartObserver implements LifecycleObserver, OnStart {
|
||||
|
||||
private final Lifecycle mLifecycle;
|
||||
|
||||
private OnStartObserver(Lifecycle lifecycle) {
|
||||
mLifecycle = lifecycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
mLifecycle.addObserver(new LifecycleObserver() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user