Companion Device App showing in Settings

Added a feature that users are able to see/remove the apps' associations
in Settings/Connected devices.
It will display the icon of the associations app, app's name and a button
that users are able to remove the associations. Also it will pop up a
dialog alerting user before remove the associations.

Screenshot:
https://screenshot.googleplex.com/APSRhW2retYmAAK

Bug: 165951651
Test: Manually Test

Change-Id: Iccaeaf516e8a78d4ef33415c1c2d7379139ec88c
This commit is contained in:
Eugene Susla
2020-11-10 11:54:39 -08:00
committed by Evan Chen
parent 83b061d3da
commit 7dc2624b51
10 changed files with 629 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:contentDescription="@string/remove_association_button"
android:id="@+id/remove_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_clear"
android:background="@android:color/transparent"/>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="16dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
</LinearLayout>

View File

@@ -205,6 +205,9 @@
<dimen name="bluetooth_pairing_edittext_padding">21dp</dimen>
<dimen name="bluetooth_checkbox_padding">16dp</dimen>
<!-- CompanionAppWidgetPreferencce Padding -->
<dimen name="bluetooth_companion_app_widget">20dp</dimen>
<!-- WiFi Preferences -->
<dimen name="wifi_divider_height">1px</dimen>
<dimen name="wifi_ap_band_checkbox_padding">16dp</dimen>

View File

@@ -1817,6 +1817,10 @@
<string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g id="address">%1$s</xliff:g></string>
<!-- Bluetooth device details. The title of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=60] -->
<string name="bluetooth_unpair_dialog_title">Forget device?</string>
<!-- Content Description for companion device app associations removal button [CHAR LIMIT=28]-->
<string name="remove_association_button">Remove association</string>
<!-- Bluetooth device details companion apps. The title of a confirmation dialog for remove an app. [CHAR LIMIT=60] -->
<string name="bluetooth_companion_app_remove_association_dialog_title">Disconnect App?</string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
<string name="bluetooth_unpair_dialog_body" product="default">Your phone will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
@@ -1824,12 +1828,16 @@
<string name="bluetooth_unpair_dialog_body" product="tablet">Your tablet will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
<string name="bluetooth_unpair_dialog_body" product="device">Your device will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
<!-- Bluetooth device details companion apps. The body of confirmation dialog for remove association. [CHAR LIMIT=60] -->
<string name="bluetooth_companion_app_body"><xliff:g id="app_name" example="App Name">%1$s</xliff:g> app will no longer connect to your <xliff:g id="device_name" example="Device Name">%2$s</xliff:g></string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=NONE] -->
<string name="bluetooth_untethered_unpair_dialog_body"><xliff:g id="device_name" example="Jack's headphone">%1$s</xliff:g> will no longer be paired with any device linked to this account</string>
<!-- Bluetooth device details. In the confirmation dialog for unpairing a paired device, this is the label on the button that will complete the unpairing action. -->
<string name="bluetooth_unpair_dialog_forget_confirm_button">Forget device</string>
<!-- Bluetooth device details companion apps. In the confirmation dialog for removing an associated app, this is the label on the button that will complete the disassociate action. [CHAR LIMIT=80] -->
<string name = "bluetooth_companion_app_remove_association_confirm_button">Disconnect app</string>
<!-- Bluetooth settings. The title of the screen to pick which profiles to connect to on the device. For example, headphones may have both A2DP and headset, this allows the users to choose which one they want to connect to. -->
<string name="bluetooth_connect_specific_profiles_title">Connect to\u2026</string>

View File

@@ -44,6 +44,9 @@
settings:allowDividerBelow="true"
settings:allowDividerAbove="true"/>
<PreferenceCategory
android:key="device_companion_apps"/>
<PreferenceCategory
android:key="bluetooth_profiles"/>

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static com.android.internal.util.CollectionUtils.filter;
import android.companion.Association;
import android.companion.CompanionDeviceManager;
import android.companion.ICompanionDeviceManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.google.common.base.Objects;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This class adds Companion Device app rows to launch the app or remove the associations
*/
public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsController {
public static final String KEY_DEVICE_COMPANION_APPS = "device_companion_apps";
private static final String LOG_TAG = "BTCompanionController";
private CachedBluetoothDevice mCachedDevice;
@VisibleForTesting
PreferenceCategory mProfilesContainer;
@VisibleForTesting
CompanionDeviceManager mCompanionDeviceManager;
@VisibleForTesting
PackageManager mPackageManager;
public BluetoothDetailsCompanionAppsController(Context context,
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
mCachedDevice = device;
mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
mPackageManager = context.getPackageManager();
lifecycle.addObserver(this);
}
@Override
protected void init(PreferenceScreen screen) {
mProfilesContainer = screen.findPreference(getPreferenceKey());
mProfilesContainer.setLayoutResource(R.layout.preference_companion_app);
}
private List<Association> getAssociations(String address) {
return filter(
mCompanionDeviceManager.getAllAssociations(),
a -> Objects.equal(address, a.getDeviceMacAddress()));
}
private static void removePreference(PreferenceCategory container, String packageName) {
Preference preference = container.findPreference(packageName);
if (preference != null) {
container.removePreference(preference);
}
}
private void removeAssociationDialog(String packageName, String address,
PreferenceCategory container, CharSequence appName, Context context) {
DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
removeAssociation(packageName, address, container);
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setPositiveButton(
R.string.bluetooth_companion_app_remove_association_confirm_button,
dialogClickListener)
.setNegativeButton(android.R.string.cancel, dialogClickListener)
.setTitle(R.string.bluetooth_companion_app_remove_association_dialog_title)
.setMessage(mContext.getString(
R.string.bluetooth_companion_app_body, appName, mCachedDevice.getName()))
.show();
}
private static void removeAssociation(String packageName, String address,
PreferenceCategory container) {
try {
java.util.Objects.requireNonNull(ICompanionDeviceManager.Stub.asInterface(
ServiceManager.getService(
Context.COMPANION_DEVICE_SERVICE))).disassociate(
address, packageName);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
removePreference(container, packageName);
}
private CharSequence getAppName(String packageName) {
CharSequence appName = null;
try {
appName = mPackageManager.getApplicationLabel(
mPackageManager.getApplicationInfo(packageName, 0));
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Package Not Found", e);
}
return appName;
}
private List<String> getPreferencesNeedToShow(String address, PreferenceCategory container) {
List<String> preferencesToRemove = new ArrayList<>();
Set<String> packages = getAssociations(address)
.stream().map(Association::getPackageName)
.collect(Collectors.toSet());
for (int i = 0; i < container.getPreferenceCount(); i++) {
String preferenceKey = container.getPreference(i).getKey();
if (packages.isEmpty() || !packages.contains(preferenceKey)) {
preferencesToRemove.add(preferenceKey);
}
}
for (String preferenceName : preferencesToRemove) {
removePreference(container, preferenceName);
}
return packages.stream()
.filter(p -> container.findPreference(p) == null)
.collect(Collectors.toList());
}
/**
* Refreshes the state of the preferences for all the associations, possibly adding or
* removing preferences as needed.
*/
@Override
protected void refresh() {
updatePreferences(mContext, mCachedDevice.getAddress(), mProfilesContainer);
}
/**
* Add preferences for each association for the bluetooth device
*/
public void updatePreferences(Context context,
String address, PreferenceCategory container) {
Set<String> addedPackages = new HashSet<>();
for (String packageName : getPreferencesNeedToShow(address, container)) {
CharSequence appName = getAppName(packageName);
if (TextUtils.isEmpty(appName) || !addedPackages.add(packageName)) {
continue;
}
Drawable removeIcon = context.getResources().getDrawable(R.drawable.ic_clear);
CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
removeIcon,
v -> removeAssociationDialog(packageName, address, container, appName, context),
context
);
Drawable appIcon;
try {
appIcon = mPackageManager.getApplicationIcon(packageName);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Icon Not Found", e);
continue;
}
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
preference.setIcon(appIcon);
preference.setTitle(appName.toString());
preference.setOnPreferenceClickListener(v -> {
context.startActivity(intent);
return true;
});
preference.setKey(packageName);
preference.setVisible(true);
container.addPreference(preference);
}
}
@Override
public String getPreferenceKey() {
return KEY_DEVICE_COMPANION_APPS;
}
}

View File

@@ -185,6 +185,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
lifecycle, mManager));
controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageButton;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* A custom preference for companion device apps. Added a button for association removal
*/
public class CompanionAppWidgetPreference extends Preference {
private Drawable mWidgetIcon;
private View.OnClickListener mWidgetListener;
private int mImageButtonPadding;
public CompanionAppWidgetPreference(Drawable widgetIcon, View.OnClickListener widgetListener,
Context context) {
super(context);
mWidgetIcon = widgetIcon;
mWidgetListener = widgetListener;
mImageButtonPadding = context.getResources().getDimensionPixelSize(
R.dimen.bluetooth_companion_app_widget);
setWidgetLayoutResource(R.layout.companion_apps_remove_button_widget);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
ImageButton imageButton = (ImageButton) holder.findViewById(R.id.remove_button);
imageButton.setPadding(
mImageButtonPadding, mImageButtonPadding, mImageButtonPadding, mImageButtonPadding);
imageButton.setColorFilter(getContext().getColor(android.R.color.darker_gray));
imageButton.setImageDrawable(mWidgetIcon);
imageButton.setOnClickListener(mWidgetListener);
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.companion.Association;
import android.companion.CompanionDeviceManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsCompanionAppsControllerTest extends
BluetoothDetailsControllerTestBase {
private static final String PACKAGE_NAME_ONE = "com.google.android.deskclock";
private static final String PACKAGE_NAME_TWO = "com.google.android.calculator";
private static final String PACKAGE_NAME_THREE = "com.google.android.GoogleCamera";
private static final CharSequence APP_NAME_ONE = "deskclock";
private static final CharSequence APP_NAME_TWO = "calculator";
private static final CharSequence APP_NAME_THREE = "GoogleCamera";
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
@Mock
private PackageManager mPackageManager;
private BluetoothDetailsCompanionAppsController mController;
private PreferenceCategory mProfiles;
private List<String> mPackages;
private List<CharSequence> mAppNames;
private List<Association> mAssociations;
@Override
public void setUp() {
super.setUp();
mPackages = Arrays.asList(PACKAGE_NAME_ONE, PACKAGE_NAME_TWO, PACKAGE_NAME_THREE);
mAppNames = Arrays.asList(APP_NAME_ONE, APP_NAME_TWO, APP_NAME_THREE);
mProfiles = spy(new PreferenceCategory(mContext));
mAssociations = new ArrayList<>();
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
setupDevice(mDeviceConfig);
mController =
new BluetoothDetailsCompanionAppsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mController.mCompanionDeviceManager = mCompanionDeviceManager;
mController.mPackageManager = mPackageManager;
mController.mProfilesContainer = mProfiles;
mProfiles.setKey(mController.getPreferenceKey());
mScreen.addPreference(mProfiles);
}
private void setupFakeLabelAndInfo(String packageName, CharSequence appName) {
ApplicationInfo appInfo = mock(ApplicationInfo.class);
try {
when(mPackageManager.getApplicationInfo(packageName, 0)).thenReturn(appInfo);
when(mPackageManager.getApplicationLabel(appInfo)).thenReturn(appName);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
private void addFakeAssociation(String packageName, CharSequence appName) {
setupFakeLabelAndInfo(packageName, appName);
Association association = new Association(
0, mCachedDevice.getAddress(), packageName, "", true, System.currentTimeMillis());
mAssociations.add(association);
showScreen(mController);
}
private Preference getPreference(int index) {
PreferenceCategory preferenceCategory = mProfiles.findPreference(
mController.getPreferenceKey());
return Objects.requireNonNull(preferenceCategory).getPreference(index);
}
private void removeAssociation(String packageName) {
mAssociations = mAssociations.stream()
.filter(a -> !a.getPackageName().equals(packageName))
.collect(Collectors.toList());
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
showScreen(mController);
}
@Test
public void addOneAssociation_preferenceShouldBeAdded() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
Preference preferenceOne = getPreference(0);
assertThat(preferenceOne.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preferenceOne.getKey()).isEqualTo(PACKAGE_NAME_ONE);
assertThat(preferenceOne.getTitle()).isEqualTo(APP_NAME_ONE.toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void removeOneAssociation_preferenceShouldBeRemoved() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void addMultipleAssociations_preferencesShouldBeAdded() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i));
Preference preference = getPreference(i);
assertThat(preference.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preference.getKey()).isEqualTo(mPackages.get(i));
assertThat(preference.getTitle()).isEqualTo(mAppNames.get(i).toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(i + 1);
}
}
@Test
public void removeMultipleAssociations_preferencesShouldBeRemoved() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i).toString());
}
for (int i = 0; i < mPackages.size(); i++) {
removeAssociation(mPackages.get(i));
assertThat(mProfiles.getPreferenceCount()).isEqualTo(mPackages.size() - i - 1);
if (i == mPackages.size() - 1) {
break;
}
assertThat(getPreference(0).getKey()).isEqualTo(mPackages.get(i + 1));
assertThat(getPreference(0).getTitle()).isEqualTo(mAppNames.get(i + 1).toString());
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.View;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class})
public class CompanionAppWidgetPreferenceTest {
private static final String TITLE_ONE = "Test Title 1";
private static final String TITLE_TWO = "Test Title 1";
private static final String KEY_ONE = "Test Key 1";
private static final String KEY_TWO = "Test Key 1";
private Context mContext;
private Drawable mWidgetIconOne;
private Drawable mWidgetIconTwo;
private Drawable mAppIconOne;
private Drawable mAppIconTwo;
@Mock
private View.OnClickListener mWidgetListenerOne;
@Mock
private View.OnClickListener mWidgetListenerTwo;
private List<CompanionAppWidgetPreference> mPreferenceContainer;
@Before
public void setUp() {
mPreferenceContainer = new ArrayList<>();
Context context = spy(RuntimeEnvironment.application.getApplicationContext());
mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
mWidgetIconOne = mock(Drawable.class);
mAppIconOne = mock(Drawable.class);
mWidgetListenerOne = mock(View.OnClickListener.class);
mWidgetIconTwo = mock(Drawable.class);
mAppIconTwo = mock(Drawable.class);
mWidgetListenerTwo = mock(View.OnClickListener.class);
}
private void setUpPreferenceContainer(Drawable widgetIcon, Drawable appIcon,
View.OnClickListener listener, String title, String key) {
CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
widgetIcon, listener, mContext);
preference.setIcon(appIcon);
preference.setTitle(title);
preference.setKey(key);
mPreferenceContainer.add(preference);
}
@Test
public void setUpPreferenceContainer_preferenceShouldBeAdded() {
setUpPreferenceContainer(
mWidgetIconOne, mAppIconOne, mWidgetListenerOne, TITLE_ONE, KEY_ONE);
assertThat(mPreferenceContainer.get(0).getIcon()).isEqualTo(mAppIconOne);
assertThat(mPreferenceContainer.get(0).getKey()).isEqualTo(KEY_ONE);
assertThat(mPreferenceContainer.get(0).getTitle()).isEqualTo(TITLE_ONE);
setUpPreferenceContainer(
mWidgetIconTwo, mAppIconTwo, mWidgetListenerTwo, TITLE_TWO, KEY_TWO);
assertThat(mPreferenceContainer.get(1).getIcon()).isEqualTo(mAppIconTwo);
assertThat(mPreferenceContainer.get(1).getKey()).isEqualTo(KEY_TWO);
assertThat(mPreferenceContainer.get(1).getTitle()).isEqualTo(TITLE_TWO);
}
}