fix: 首次提交

This commit is contained in:
2024-12-09 11:25:23 +08:00
parent d0c01071e9
commit 2c2109a5f3
4741 changed files with 290641 additions and 0 deletions

View 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",
}

View 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>

View 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>

View 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>

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
# Default reviewers for this and subdirectories.
alexflo@google.com
chrisgollner@google.com

View File

@@ -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()
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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)));
}
}
}
}
}
}

View File

@@ -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 */);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.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);
}
}

View File

@@ -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);
}
}

View File

@@ -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,
)
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1 @@
include /packages/SettingsLib/src/com/android/settingslib/volume/OWNERS

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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

View 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",
],
}

View 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>

View File

@@ -0,0 +1,2 @@
# We do not guard tests - everyone is welcomed to contribute to tests.
per-file *.java=*

View File

@@ -0,0 +1,2 @@
sdk=NEWEST_SDK
instrumentedPackages=androidx.preference

View 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",
],
}

View 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"],
)
]

View 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"
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
Unit test suite for SettingsLib using Robolectric.
```
$ croot
$ make RunSettingsLibRoboTests -j40
```

View File

@@ -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>

View 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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.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();
}
}

View File

@@ -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)));
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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