Merge "VDM Settings" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
18fe317f1d
@@ -79,6 +79,8 @@ android_library {
|
||||
"BiometricsSharedLib",
|
||||
"SystemUIUnfoldLib",
|
||||
"WifiTrackerLib",
|
||||
"android.companion.flags-aconfig-java",
|
||||
"android.companion.virtualdevice.flags-aconfig-java",
|
||||
"android.hardware.biometrics.flags-aconfig-java",
|
||||
"android.hardware.dumpstate-V1-java",
|
||||
"android.hardware.dumpstate-V1.0-java",
|
||||
|
@@ -148,6 +148,7 @@
|
||||
<uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
|
||||
<uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
|
||||
<uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
|
||||
|
||||
<application
|
||||
android:name=".SettingsApplication"
|
||||
|
@@ -457,6 +457,18 @@
|
||||
<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>
|
||||
<!-- Virtual device details. The body of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_forget_dialog_body" product="default"><xliff:g id="device_name">%1$s</xliff:g> will no longer be connected to this phone. If you continue, some apps and app streaming may stop working.</string>
|
||||
<!-- Virtual device details. The body of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_forget_dialog_body" product="tablet"><xliff:g id="device_name">%1$s</xliff:g> will no longer be connected to this tablet. If you continue, some apps and app streaming may stop working.</string>
|
||||
<!-- Virtual device details. The body of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_forget_dialog_body" product="device"><xliff:g id="device_name">%1$s</xliff:g> will no longer be connected to this device. If you continue, some apps and app streaming may stop working.</string>
|
||||
<!-- Virtual device details. Footer [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_details_footer_title" product="default">If you forget <xliff:g id="device_name">%1$s</xliff:g>, it will no longer be associated to this phone. Some permissions will be removed and some apps and app streaming may stop working.</string>
|
||||
<!-- Virtual device details. Footer [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_details_footer_title" product="tablet">If you forget <xliff:g id="device_name">%1$s</xliff:g>, it will no longer be associated to this tablet. Some permissions will be removed and some apps and app streaming may stop working.</string>
|
||||
<!-- Virtual device details. Footer [CHAR LIMIT=none] -->
|
||||
<string name="virtual_device_details_footer_title" product="device">If you forget <xliff:g id="device_name">%1$s</xliff:g>, it will no longer be associated to this device. Some permissions will be removed and some apps and app streaming may stop working.</string>
|
||||
<!-- Description of Secure NFC in the 1st level settings screen. [CHAR LIMIT=NONE] -->
|
||||
<string name="nfc_secure_toggle_summary" product="default">Allow NFC use only when screen is unlocked</string>
|
||||
<!-- Summary for the panel of add Wi-Fi network from APP [CHAR LIMIT=NONE] -->
|
||||
|
@@ -17,7 +17,8 @@
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M3,6.003H21V4.003H3C1.9,4.003 1,4.903 1,6.003V18.003C1,19.103 1.9,20.003 3,20.003H7V18.003H3V6.003ZM13.002,12.001H9.002V13.781C8.392,14.331 8.002,15.111 8.002,16.001C8.002,16.891 8.392,17.671 9.002,18.221V20.001H13.002V18.221C13.612,17.671 14.002,16.881 14.002,16.001C14.002,15.121 13.612,14.331 13.002,13.781V12.001ZM9.501,16.003C9.501,16.833 10.171,17.503 11.001,17.503C11.831,17.503 12.501,16.833 12.501,16.003C12.501,15.173 11.831,14.503 11.001,14.503C10.171,14.503 9.501,15.173 9.501,16.003ZM22,8.004H16C15.5,8.004 15,8.504 15,9.004V19.004C15,19.504 15.5,20.004 16,20.004H22C22.5,20.004 23,19.504 23,19.004V9.004C23,8.504 22.5,8.004 22,8.004ZM17.002,18H21.002V10H17.002V18Z"
|
||||
android:fillType="evenOdd"
|
||||
|
@@ -13290,6 +13290,21 @@
|
||||
<!-- Label for button to not allow grant the permission for remote devices. [CHAR_LIMIT=50] -->
|
||||
<string name="request_manage_bluetooth_permission_dont_allow">Don\u2019t allow</string>
|
||||
|
||||
<!-- Summary for a connected Virtual Device [CHAR LIMIT=50] -->
|
||||
<string name="virtual_device_connected">Connected</string>
|
||||
<!-- Summary for a disconnected Virtual Device [CHAR LIMIT=50] -->
|
||||
<string name="virtual_device_disconnected">Disconnected</string>
|
||||
<!-- The name of virtual device if there's no display name available. [CHAR LIMIT=60] -->
|
||||
<string name="virtual_device_unknown">Unknown device</string>
|
||||
<!-- Title for virtual device details page [CHAR LIMIT=50] -->
|
||||
<string name="virtual_device_details_title">Device details</string>
|
||||
<!-- The title of a confirmation dialog for forgetting a virtual device. [CHAR LIMIT=30] -->
|
||||
<string name="virtual_device_forget_dialog_title">Forget <xliff:g id="device_name">%1$s</xliff:g>?</string>
|
||||
<!-- In the confirmation dialog for forgetting a virtual device, this is the label on the button that will complete the forget action. -->
|
||||
<string name="virtual_device_forget_dialog_confirm_button">Forget</string>
|
||||
<!-- The title of the companion app section in the virtual device details page [CHAR LIMIT=50] -->
|
||||
<string name="virtual_device_companion_app_category">Connection managed by</string>
|
||||
|
||||
<!-- Title for UWB preference [CHAR_LIMIT=60] -->
|
||||
<string name="uwb_settings_title">Ultra-Wideband (UWB)</string>
|
||||
|
||||
|
@@ -74,6 +74,13 @@
|
||||
android:summary="@string/summary_placeholder"
|
||||
android:title="@string/print_settings" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="virtual_device_list"
|
||||
android:layout="@layout/preference_category_no_label"
|
||||
android:order="99"
|
||||
settings:controller="com.android.settings.connecteddevice.virtual.VirtualDeviceListController"
|
||||
settings:searchable="true"/>
|
||||
|
||||
<com.android.settingslib.RestrictedSwitchPreference
|
||||
android:key="uwb_settings"
|
||||
android:order="100"
|
||||
|
51
res/xml/virtual_device_details_fragment.xml
Normal file
51
res/xml/virtual_device_details_fragment.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||
android:title="@string/virtual_device_details_title">
|
||||
|
||||
<com.android.settingslib.widget.LayoutPreference
|
||||
android:key="virtual_device_details_header"
|
||||
android:layout="@layout/settings_entity_header"
|
||||
android:selectable="false"
|
||||
settings:allowDividerBelow="true"
|
||||
settings:controller="com.android.settings.connecteddevice.virtual.VirtualDeviceDetailsHeaderController"/>
|
||||
|
||||
<com.android.settingslib.widget.ActionButtonsPreference
|
||||
android:key="virtual_device_action_buttons"
|
||||
settings:allowDividerBelow="true"
|
||||
settings:controller="com.android.settings.connecteddevice.virtual.VirtualDeviceDetailsButtonsController"/>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="recent_apps_category"
|
||||
android:title="@string/virtual_device_companion_app_category"
|
||||
settings:searchable="false">
|
||||
|
||||
<com.android.settingslib.widget.AppPreference
|
||||
android:key="virtual_device_companion_app"
|
||||
settings:controller="com.android.settings.connecteddevice.virtual.VirtualDeviceDetailsCompanionAppController"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<com.android.settingslib.widget.FooterPreference
|
||||
android:key="virtual_device_details_footer"
|
||||
settings:controller="com.android.settings.connecteddevice.virtual.VirtualDeviceDetailsFooterController"
|
||||
android:selectable="false"
|
||||
settings:searchable="false" />
|
||||
|
||||
</PreferenceScreen>
|
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.provider.SearchIndexableResource;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.virtual.VirtualDeviceListController;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.print.PrintSettingPreferenceController;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
@@ -74,6 +75,7 @@ public class AdvancedConnectedDeviceDashboardFragment extends DashboardFragment
|
||||
getSettingsLifecycle().addObserver(uwbPreferenceController);
|
||||
}
|
||||
}
|
||||
use(VirtualDeviceListController.class).setFragment(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
/**
|
||||
* Implements an AlertDialog for confirming that a user wishes to unpair or "forget" a paired
|
||||
* device.
|
||||
*/
|
||||
public class ForgetDeviceDialogFragment extends InstrumentedDialogFragment {
|
||||
|
||||
public static final String TAG = ForgetDeviceDialogFragment.class.getSimpleName();
|
||||
|
||||
private static final String DEVICE_ARG = "virtual_device_arg";
|
||||
|
||||
@VisibleForTesting
|
||||
CompanionDeviceManager mCompanionDeviceManager;
|
||||
@VisibleForTesting
|
||||
VirtualDeviceWrapper mDevice;
|
||||
|
||||
static ForgetDeviceDialogFragment newInstance(VirtualDeviceWrapper device) {
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(DEVICE_ARG, device);
|
||||
ForgetDeviceDialogFragment dialog = new ForgetDeviceDialogFragment();
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_VIRTUAL_DEVICE_FORGET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
|
||||
mDevice = getArguments().getParcelable(DEVICE_ARG, VirtualDeviceWrapper.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(@Nullable Bundle inState) {
|
||||
Context context = getContext();
|
||||
CharSequence deviceName = mDevice.getDeviceName(context);
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||
.setPositiveButton(R.string.virtual_device_forget_dialog_confirm_button,
|
||||
this::onForgetButtonClick)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
dialog.setTitle(
|
||||
context.getString(R.string.virtual_device_forget_dialog_title, deviceName));
|
||||
dialog.setMessage(
|
||||
context.getString(R.string.virtual_device_forget_dialog_body, deviceName));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void onForgetButtonClick(DialogInterface dialog, int which) {
|
||||
mCompanionDeviceManager.disassociate(mDevice.getAssociationInfo().getId());
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.widget.ActionButtonsPreference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** This class adds one button to "forget" (ie unpair) the device. */
|
||||
public class VirtualDeviceDetailsButtonsController extends BasePreferenceController {
|
||||
|
||||
private static final String KEY_VIRTUAL_DEVICE_ACTION_BUTTONS = "virtual_device_action_buttons";
|
||||
|
||||
@Nullable
|
||||
private PreferenceFragmentCompat mFragment;
|
||||
@Nullable
|
||||
private VirtualDeviceWrapper mDevice;
|
||||
|
||||
public VirtualDeviceDetailsButtonsController(@NonNull Context context) {
|
||||
super(context, KEY_VIRTUAL_DEVICE_ACTION_BUTTONS);
|
||||
}
|
||||
|
||||
/** One-time initialization when the controller is first created. */
|
||||
void init(@NonNull PreferenceFragmentCompat fragment, @NonNull VirtualDeviceWrapper device) {
|
||||
mFragment = fragment;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
private void onForgetButtonPressed() {
|
||||
ForgetDeviceDialogFragment fragment =
|
||||
ForgetDeviceDialogFragment.newInstance(Objects.requireNonNull(mDevice));
|
||||
fragment.show(Objects.requireNonNull(mFragment).getParentFragmentManager(),
|
||||
ForgetDeviceDialogFragment.TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
((ActionButtonsPreference) screen.findPreference(getPreferenceKey()))
|
||||
.setButton1Text(R.string.forget)
|
||||
.setButton1Icon(R.drawable.ic_settings_delete)
|
||||
.setButton1OnClickListener((view) -> onForgetButtonPressed())
|
||||
.setButton1Enabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.widget.AppPreference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** This class adds the details about the virtual device companion app. */
|
||||
public class VirtualDeviceDetailsCompanionAppController extends BasePreferenceController {
|
||||
|
||||
private static final String KEY_VIRTUAL_DEVICE_COMPANION_APP = "virtual_device_companion_app";
|
||||
|
||||
@Nullable
|
||||
private PreferenceFragmentCompat mFragment;
|
||||
@Nullable
|
||||
private String mPackageName;
|
||||
|
||||
public VirtualDeviceDetailsCompanionAppController(@NonNull Context context) {
|
||||
super(context, KEY_VIRTUAL_DEVICE_COMPANION_APP);
|
||||
}
|
||||
|
||||
/** One-time initialization when the controller is first created. */
|
||||
void init(@NonNull PreferenceFragmentCompat fragment, @NonNull String packageName) {
|
||||
mFragment = fragment;
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
ApplicationsState applicationsState = ApplicationsState.getInstance(
|
||||
(Application) mContext.getApplicationContext());
|
||||
final ApplicationsState.AppEntry appEntry = applicationsState.getEntry(
|
||||
mPackageName, UserHandle.myUserId());
|
||||
|
||||
final AppPreference preference = screen.findPreference(getPreferenceKey());
|
||||
|
||||
preference.setTitle(appEntry.label);
|
||||
preference.setIcon(Utils.getBadgedIcon(mContext, appEntry.info));
|
||||
preference.setOnPreferenceClickListener(pref -> {
|
||||
startAppInfoSettings(Objects.requireNonNull(mPackageName), appEntry.info.uid,
|
||||
Objects.requireNonNull(mFragment), /* request= */ 1001,
|
||||
getMetricsCategory());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
|
||||
/** Adds footer text on the virtual device details page. */
|
||||
public class VirtualDeviceDetailsFooterController extends BasePreferenceController {
|
||||
|
||||
private static final String KEY_VIRTUAL_DEVICE_FOOTER = "virtual_device_details_footer";
|
||||
|
||||
@Nullable
|
||||
private CharSequence mDeviceName;
|
||||
|
||||
public VirtualDeviceDetailsFooterController(@NonNull Context context) {
|
||||
super(context, KEY_VIRTUAL_DEVICE_FOOTER);
|
||||
}
|
||||
|
||||
/** One-time initialization when the controller is first created. */
|
||||
void init(@NonNull CharSequence deviceName) {
|
||||
mDeviceName = deviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
Preference preference = screen.findPreference(getPreferenceKey());
|
||||
preference.setTitle(mContext.getString(R.string.virtual_device_details_footer_title,
|
||||
mDeviceName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
|
||||
/**
|
||||
* Dedicated screen displaying the information for a single virtual device to the user and allowing
|
||||
* them to manage that device.
|
||||
*/
|
||||
public class VirtualDeviceDetailsFragment extends DashboardFragment {
|
||||
|
||||
private static final String TAG = VirtualDeviceDetailsFragment.class.getSimpleName();
|
||||
|
||||
static final String DEVICE_ARG = "virtual_device_arg";
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
VirtualDeviceWrapper device =
|
||||
getArguments().getParcelable(DEVICE_ARG, VirtualDeviceWrapper.class);
|
||||
|
||||
use(VirtualDeviceDetailsHeaderController.class).init(device);
|
||||
use(VirtualDeviceDetailsButtonsController.class).init(this, device);
|
||||
use(VirtualDeviceDetailsCompanionAppController.class)
|
||||
.init(this, device.getAssociationInfo().getPackageName());
|
||||
use(VirtualDeviceDetailsFooterController.class).init(device.getDeviceName(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.VIRTUAL_DEVICE_DETAILS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.virtual_device_details_fragment;
|
||||
}
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.companion.virtual.VirtualDevice;
|
||||
import android.companion.virtual.VirtualDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** This class adds a header for a virtual device with a heading and icon. */
|
||||
public class VirtualDeviceDetailsHeaderController extends BasePreferenceController implements
|
||||
LifecycleObserver, VirtualDeviceManager.VirtualDeviceListener {
|
||||
|
||||
private static final String KEY_VIRTUAL_DEVICE_DETAILS_HEADER = "virtual_device_details_header";
|
||||
|
||||
@Nullable
|
||||
private final VirtualDeviceManager mVirtualDeviceManager;
|
||||
@Nullable
|
||||
private VirtualDeviceWrapper mDevice;
|
||||
@Nullable
|
||||
private TextView mSummaryView;
|
||||
|
||||
private final Executor mExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public VirtualDeviceDetailsHeaderController(@NonNull Context context) {
|
||||
super(context, KEY_VIRTUAL_DEVICE_DETAILS_HEADER);
|
||||
mVirtualDeviceManager =
|
||||
Objects.requireNonNull(context.getSystemService(VirtualDeviceManager.class));
|
||||
}
|
||||
|
||||
/** One-time initialization when the controller is first created. */
|
||||
void init(@NonNull VirtualDeviceWrapper device) {
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
void onStart() {
|
||||
if (mVirtualDeviceManager != null) {
|
||||
mVirtualDeviceManager.registerVirtualDeviceListener(mExecutor, this);
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
void onStop() {
|
||||
if (mVirtualDeviceManager != null) {
|
||||
mVirtualDeviceManager.unregisterVirtualDeviceListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualDeviceCreated(int deviceId) {
|
||||
VirtualDevice device =
|
||||
Objects.requireNonNull(mVirtualDeviceManager).getVirtualDevice(deviceId);
|
||||
if (mDevice != null && device != null
|
||||
&& mDevice.getPersistentDeviceId().equals(device.getPersistentDeviceId())) {
|
||||
mDevice.setDeviceId(deviceId);
|
||||
mContext.getMainExecutor().execute(this::updateSummary);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualDeviceClosed(int deviceId) {
|
||||
if (mDevice != null && deviceId == mDevice.getDeviceId()) {
|
||||
mDevice.setDeviceId(Context.DEVICE_ID_INVALID);
|
||||
mContext.getMainExecutor().execute(this::updateSummary);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
LayoutPreference headerPreference = screen.findPreference(getPreferenceKey());
|
||||
View view = headerPreference.findViewById(R.id.entity_header);
|
||||
TextView titleView = view.findViewById(R.id.entity_header_title);
|
||||
ImageView iconView = headerPreference.findViewById(R.id.entity_header_icon);
|
||||
mSummaryView = view.findViewById(R.id.entity_header_summary);
|
||||
updateSummary();
|
||||
if (mDevice != null) {
|
||||
titleView.setText(mDevice.getDeviceName(mContext));
|
||||
Icon deviceIcon = android.companion.Flags.associationDeviceIcon()
|
||||
? mDevice.getAssociationInfo().getDeviceIcon() : null;
|
||||
if (deviceIcon == null) {
|
||||
iconView.setImageResource(R.drawable.ic_devices_other);
|
||||
} else {
|
||||
iconView.setImageIcon(deviceIcon);
|
||||
}
|
||||
}
|
||||
iconView.setContentDescription("Icon for device");
|
||||
}
|
||||
|
||||
private void updateSummary() {
|
||||
if (mSummaryView != null && mDevice != null) {
|
||||
mSummaryView.setText(mDevice.getDeviceId() != Context.DEVICE_ID_INVALID
|
||||
? R.string.virtual_device_connected : R.string.virtual_device_disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Displays the list of all virtual devices. */
|
||||
public class VirtualDeviceListController extends BasePreferenceController
|
||||
implements LifecycleObserver, VirtualDeviceUpdater.DeviceListener {
|
||||
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
|
||||
@VisibleForTesting
|
||||
VirtualDeviceUpdater mVirtualDeviceUpdater;
|
||||
@VisibleForTesting
|
||||
ArrayMap<String, Preference> mPreferences = new ArrayMap<>();
|
||||
@Nullable
|
||||
private PreferenceGroup mPreferenceGroup;
|
||||
@Nullable
|
||||
private DashboardFragment mFragment;
|
||||
|
||||
public VirtualDeviceListController(@NonNull Context context, @NonNull String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
mVirtualDeviceUpdater = new VirtualDeviceUpdater(context, this);
|
||||
}
|
||||
|
||||
public void setFragment(@NonNull DashboardFragment fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
void onStart() {
|
||||
if (isAvailable()) {
|
||||
mVirtualDeviceUpdater.registerListener();
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
void onStop() {
|
||||
if (isAvailable()) {
|
||||
mVirtualDeviceUpdater.unregisterListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreferenceGroup = screen.findPreference(getPreferenceKey());
|
||||
if (isAvailable()) {
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAdded(@NonNull VirtualDeviceWrapper device) {
|
||||
Preference preference = new Preference(mContext);
|
||||
CharSequence deviceName = device.getDeviceName(mContext);
|
||||
preference.setTitle(deviceName);
|
||||
preference.setKey(device.getPersistentDeviceId() + "_" + deviceName);
|
||||
final CharSequence title = preference.getTitle();
|
||||
|
||||
Icon deviceIcon = android.companion.Flags.associationDeviceIcon()
|
||||
? device.getAssociationInfo().getDeviceIcon() : null;
|
||||
if (deviceIcon == null) {
|
||||
preference.setIcon(R.drawable.ic_devices_other);
|
||||
} else {
|
||||
preference.setIcon(deviceIcon.loadDrawable(mContext));
|
||||
}
|
||||
if (device.getDeviceId() != Context.DEVICE_ID_INVALID) {
|
||||
preference.setSummary(R.string.virtual_device_connected);
|
||||
} else {
|
||||
preference.setSummary(R.string.virtual_device_disconnected);
|
||||
}
|
||||
|
||||
preference.setOnPreferenceClickListener((Preference p) -> {
|
||||
mMetricsFeatureProvider.logClickedPreference(p, getMetricsCategory());
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(VirtualDeviceDetailsFragment.DEVICE_ARG, device);
|
||||
if (mFragment != null) {
|
||||
new SubSettingLauncher(mFragment.getContext())
|
||||
.setDestination(VirtualDeviceDetailsFragment.class.getName())
|
||||
.setTitleText(title)
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(getMetricsCategory())
|
||||
.launch();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
mPreferences.put(device.getPersistentDeviceId(), preference);
|
||||
if (mPreferenceGroup != null) {
|
||||
mContext.getMainExecutor().execute(() ->
|
||||
Objects.requireNonNull(mPreferenceGroup).addPreference(preference));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(@NonNull VirtualDeviceWrapper device) {
|
||||
Preference preference = mPreferences.remove(device.getPersistentDeviceId());
|
||||
if (mPreferenceGroup != null) {
|
||||
mContext.getMainExecutor().execute(() ->
|
||||
Objects.requireNonNull(mPreferenceGroup).removePreference(preference));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceChanged(@NonNull VirtualDeviceWrapper device) {
|
||||
Preference preference = mPreferences.get(device.getPersistentDeviceId());
|
||||
if (preference != null) {
|
||||
int summaryResId = device.getDeviceId() != Context.DEVICE_ID_INVALID
|
||||
? R.string.virtual_device_connected : R.string.virtual_device_disconnected;
|
||||
mContext.getMainExecutor().execute(() ->
|
||||
Objects.requireNonNull(preference).setSummary(summaryResId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDynamicRawDataToIndex(@NonNull List<SearchIndexableRaw> rawData) {
|
||||
if (!isAvailable()) {
|
||||
return;
|
||||
}
|
||||
Collection<VirtualDeviceWrapper> devices = mVirtualDeviceUpdater.loadDevices();
|
||||
for (VirtualDeviceWrapper device : devices) {
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
|
||||
String deviceName = device.getDeviceName(mContext).toString();
|
||||
data.key = device.getPersistentDeviceId() + "_" + deviceName;
|
||||
data.title = deviceName;
|
||||
data.summaryOn = mContext.getString(R.string.connected_device_connections_title);
|
||||
rawData.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
if (!mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_enableVirtualDeviceManager)) {
|
||||
return UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
if (!android.companion.virtualdevice.flags.Flags.vdmSettings()) {
|
||||
return CONDITIONALLY_UNAVAILABLE;
|
||||
}
|
||||
return AVAILABLE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.android.settingslib.drawer.TileUtils.IA_SETTINGS_ACTION;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.companion.virtual.VirtualDevice;
|
||||
import android.companion.virtual.VirtualDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** Maintains a collection of all virtual devices and propagates any changes to its listener. */
|
||||
class VirtualDeviceUpdater implements VirtualDeviceManager.VirtualDeviceListener {
|
||||
|
||||
private static final String CDM_PERSISTENT_DEVICE_ID_PREFIX = "companion:";
|
||||
|
||||
// TODO(b/384400670): Detect these packages via PackageManager instead of hardcoding them.
|
||||
private static final ImmutableSet<String> IGNORED_PACKAGES =
|
||||
ImmutableSet.of("com.google.ambient.streaming");
|
||||
|
||||
private final VirtualDeviceManager mVirtualDeviceManager;
|
||||
private final CompanionDeviceManager mCompanionDeviceManager;
|
||||
private final PackageManager mPackageManager;
|
||||
private final DeviceListener mDeviceListener;
|
||||
private final Executor mBackgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// Up-to-date list of active and inactive devices, keyed by persistent device id.
|
||||
@VisibleForTesting
|
||||
ArrayMap<String, VirtualDeviceWrapper> mDevices = new ArrayMap<>();
|
||||
|
||||
interface DeviceListener {
|
||||
void onDeviceAdded(@NonNull VirtualDeviceWrapper device);
|
||||
void onDeviceRemoved(@NonNull VirtualDeviceWrapper device);
|
||||
void onDeviceChanged(@NonNull VirtualDeviceWrapper device);
|
||||
}
|
||||
|
||||
VirtualDeviceUpdater(Context context, DeviceListener deviceListener) {
|
||||
mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
|
||||
mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
|
||||
mPackageManager = context.getPackageManager();
|
||||
mDeviceListener = deviceListener;
|
||||
}
|
||||
|
||||
void registerListener() {
|
||||
mVirtualDeviceManager.registerVirtualDeviceListener(mBackgroundExecutor, this);
|
||||
mBackgroundExecutor.execute(this::loadDevices);
|
||||
}
|
||||
|
||||
void unregisterListener() {
|
||||
mVirtualDeviceManager.unregisterVirtualDeviceListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualDeviceCreated(int deviceId) {
|
||||
loadDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualDeviceClosed(int deviceId) {
|
||||
loadDevices();
|
||||
}
|
||||
|
||||
Collection<VirtualDeviceWrapper> loadDevices() {
|
||||
final Set<String> persistentDeviceIds = mVirtualDeviceManager.getAllPersistentDeviceIds();
|
||||
final Set<String> deviceIdsToRemove = new ArraySet<>();
|
||||
for (String persistentDeviceId : mDevices.keySet()) {
|
||||
if (!persistentDeviceIds.contains(persistentDeviceId)) {
|
||||
deviceIdsToRemove.add(persistentDeviceId);
|
||||
}
|
||||
}
|
||||
for (String persistentDeviceId : deviceIdsToRemove) {
|
||||
mDeviceListener.onDeviceRemoved(mDevices.remove(persistentDeviceId));
|
||||
}
|
||||
|
||||
if (!persistentDeviceIds.isEmpty()) {
|
||||
for (VirtualDevice device : mVirtualDeviceManager.getVirtualDevices()) {
|
||||
String persistentDeviceId = device.getPersistentDeviceId();
|
||||
persistentDeviceIds.remove(persistentDeviceId);
|
||||
addOrUpdateDevice(persistentDeviceId, device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
for (String persistentDeviceId : persistentDeviceIds) {
|
||||
addOrUpdateDevice(persistentDeviceId, Context.DEVICE_ID_INVALID);
|
||||
}
|
||||
|
||||
return mDevices.values();
|
||||
}
|
||||
|
||||
private void addOrUpdateDevice(String persistentDeviceId, int deviceId) {
|
||||
VirtualDeviceWrapper device = mDevices.get(persistentDeviceId);
|
||||
if (device == null) {
|
||||
AssociationInfo associationInfo = getAssociationInfo(persistentDeviceId);
|
||||
if (associationInfo == null) {
|
||||
return;
|
||||
}
|
||||
device = new VirtualDeviceWrapper(associationInfo, persistentDeviceId, deviceId);
|
||||
mDevices.put(persistentDeviceId, device);
|
||||
mDeviceListener.onDeviceAdded(device);
|
||||
}
|
||||
if (device.getDeviceId() != deviceId) {
|
||||
device.setDeviceId(deviceId);
|
||||
mDeviceListener.onDeviceChanged(device);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AssociationInfo getAssociationInfo(String persistentDeviceId) {
|
||||
if (persistentDeviceId == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualDeviceWrapper device = mDevices.get(persistentDeviceId);
|
||||
if (device != null) {
|
||||
return device.getAssociationInfo();
|
||||
}
|
||||
if (!persistentDeviceId.startsWith(CDM_PERSISTENT_DEVICE_ID_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
final int associationId = Integer.parseInt(
|
||||
persistentDeviceId.replaceFirst(CDM_PERSISTENT_DEVICE_ID_PREFIX, ""));
|
||||
final List<AssociationInfo> associations =
|
||||
mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL);
|
||||
final AssociationInfo associationInfo = associations.stream()
|
||||
.filter(a -> a.getId() == associationId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (associationInfo == null) {
|
||||
return null;
|
||||
}
|
||||
if (shouldExcludePackageFromSettings(associationInfo.getPackageName())) {
|
||||
return null;
|
||||
}
|
||||
return associationInfo;
|
||||
}
|
||||
|
||||
// Some packages already inject custom settings entries that allow the users to manage the
|
||||
// virtual devices and the companion associations, so they should be ignored from the generic
|
||||
// settings page.
|
||||
private boolean shouldExcludePackageFromSettings(String packageName) {
|
||||
if (packageName == null || IGNORED_PACKAGES.contains(packageName)) {
|
||||
return true;
|
||||
}
|
||||
final Intent intent = new Intent(IA_SETTINGS_ACTION);
|
||||
intent.setPackage(packageName);
|
||||
return intent.resolveActivity(mPackageManager) != null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** Parcelable representing a virtual device along with its association properties. */
|
||||
class VirtualDeviceWrapper implements Parcelable {
|
||||
|
||||
/** The CDM Association for this device. */
|
||||
@NonNull
|
||||
private final AssociationInfo mAssociationInfo;
|
||||
/** The unique VDM identifier for the device, persisted even when the device is inactive. */
|
||||
@NonNull
|
||||
private final String mPersistentDeviceId;
|
||||
/** The identifier for the device if it's active, Context.DEVICE_ID_INVALID otherwise. */
|
||||
private int mDeviceId;
|
||||
|
||||
VirtualDeviceWrapper(@NonNull AssociationInfo associationInfo,
|
||||
@NonNull String persistentDeviceId, int deviceId) {
|
||||
mAssociationInfo = associationInfo;
|
||||
mPersistentDeviceId = persistentDeviceId;
|
||||
mDeviceId = deviceId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
AssociationInfo getAssociationInfo() {
|
||||
return mAssociationInfo;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
String getPersistentDeviceId() {
|
||||
return mPersistentDeviceId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
CharSequence getDeviceName(Context context) {
|
||||
return mAssociationInfo.getDisplayName() != null
|
||||
? mAssociationInfo.getDisplayName()
|
||||
: context.getString(R.string.virtual_device_unknown);
|
||||
}
|
||||
|
||||
int getDeviceId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
void setDeviceId(int deviceId) {
|
||||
mDeviceId = deviceId;
|
||||
}
|
||||
|
||||
private VirtualDeviceWrapper(Parcel in) {
|
||||
mAssociationInfo = in.readTypedObject(AssociationInfo.CREATOR);
|
||||
mPersistentDeviceId = in.readString8();
|
||||
mDeviceId = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeTypedObject(mAssociationInfo, flags);
|
||||
dest.writeString8(mPersistentDeviceId);
|
||||
dest.writeInt(mDeviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof VirtualDeviceWrapper that)) return false;
|
||||
return Objects.equals(mAssociationInfo, that.mAssociationInfo)
|
||||
&& Objects.equals(mPersistentDeviceId, that.mPersistentDeviceId)
|
||||
&& mDeviceId == that.mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mAssociationInfo, mPersistentDeviceId, mDeviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<VirtualDeviceWrapper> CREATOR =
|
||||
new Parcelable.Creator<>() {
|
||||
@NonNull
|
||||
public VirtualDeviceWrapper createFromParcel(@NonNull Parcel in) {
|
||||
return new VirtualDeviceWrapper(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public VirtualDeviceWrapper[] newArray(int size) {
|
||||
return new VirtualDeviceWrapper[size];
|
||||
}
|
||||
};
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
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.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
import org.robolectric.shadows.androidx.fragment.FragmentController;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowAlertDialogCompat.class})
|
||||
public class ForgetDeviceDialogFragmentTest {
|
||||
|
||||
private static final int ASSOCIATION_ID = 42;
|
||||
|
||||
@Mock
|
||||
private AssociationInfo mAssociationInfo;
|
||||
@Mock
|
||||
private CompanionDeviceManager mCompanionDeviceManager;
|
||||
private AlertDialog mDialog;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, "PersistentDeviceId", Context.DEVICE_ID_INVALID);
|
||||
ForgetDeviceDialogFragment fragment = ForgetDeviceDialogFragment.newInstance(device);
|
||||
FragmentController.setupFragment(fragment, FragmentActivity.class,
|
||||
0 /* containerViewId */, null /* bundle */);
|
||||
fragment.mDevice = device;
|
||||
fragment.mCompanionDeviceManager = mCompanionDeviceManager;
|
||||
mDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancelDialog() {
|
||||
mDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick();
|
||||
ShadowLooper.idleMainLooper();
|
||||
verify(mCompanionDeviceManager, never()).disassociate(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmDialog() {
|
||||
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
|
||||
ShadowLooper.idleMainLooper();
|
||||
verify(mCompanionDeviceManager).disassociate(ASSOCIATION_ID);
|
||||
}
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.Flags;
|
||||
import android.companion.virtual.VirtualDevice;
|
||||
import android.companion.virtual.VirtualDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
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.Shadows;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VirtualDeviceDetailsHeaderControllerTest {
|
||||
|
||||
private static final CharSequence DEVICE_NAME = "Device Name";
|
||||
private static final int DEVICE_ID = 42;
|
||||
private static final String PERSISTENT_DEVICE_ID = "PersistentDeviceId";
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Mock
|
||||
VirtualDeviceManager mVirtualDeviceManager;
|
||||
@Mock
|
||||
AssociationInfo mAssociationInfo;
|
||||
@Mock
|
||||
PreferenceScreen mScreen;
|
||||
|
||||
private VirtualDeviceWrapper mDevice;
|
||||
private VirtualDeviceDetailsHeaderController mController;
|
||||
private TextView mTitle;
|
||||
private ImageView mIcon;
|
||||
private TextView mSummary;
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
when(mContext.getSystemService(VirtualDeviceManager.class))
|
||||
.thenReturn(mVirtualDeviceManager);
|
||||
mDevice = new VirtualDeviceWrapper(mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
LayoutPreference headerPreference = new LayoutPreference(mContext,
|
||||
LayoutInflater.from(mContext).inflate(R.layout.settings_entity_header, null));
|
||||
View view = headerPreference.findViewById(R.id.entity_header);
|
||||
mTitle = view.findViewById(R.id.entity_header_title);
|
||||
mIcon = headerPreference.findViewById(R.id.entity_header_icon);
|
||||
mSummary = view.findViewById(R.id.entity_header_summary);
|
||||
when(mScreen.findPreference(any())).thenReturn(headerPreference);
|
||||
|
||||
mController = new VirtualDeviceDetailsHeaderController(mContext);
|
||||
mController.init(mDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void title_matchesDeviceName() {
|
||||
when(mAssociationInfo.getDisplayName()).thenReturn(DEVICE_NAME);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(mTitle.getText().toString()).isEqualTo(DEVICE_NAME.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
|
||||
public void icon_genericIcon() {
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(Shadows.shadowOf(mIcon.getDrawable()).getCreatedFromResId())
|
||||
.isEqualTo(R.drawable.ic_devices_other);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
|
||||
public void icon_noAssociationIcon_genericIcon() {
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(Shadows.shadowOf(mIcon.getDrawable()).getCreatedFromResId())
|
||||
.isEqualTo(R.drawable.ic_devices_other);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
|
||||
public void icon_fromAssociation() {
|
||||
Icon icon = Icon.createWithResource(mContext, R.drawable.ic_android);
|
||||
when(mAssociationInfo.getDeviceIcon()).thenReturn(icon);
|
||||
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(Shadows.shadowOf(mIcon.getDrawable()).getCreatedFromResId())
|
||||
.isEqualTo(R.drawable.ic_android);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void summary_activeDevice_changeToInactive() {
|
||||
mDevice.setDeviceId(DEVICE_ID);
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(mSummary.getText().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_connected));
|
||||
|
||||
mController.onVirtualDeviceClosed(DEVICE_ID);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mSummary.getText().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_disconnected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void summary_inactiveDevice_changeToActive() {
|
||||
mDevice.setDeviceId(Context.DEVICE_ID_INVALID);
|
||||
mController.displayPreference(mScreen);
|
||||
assertThat(mSummary.getText().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_disconnected));
|
||||
|
||||
VirtualDevice virtualDevice = mock(VirtualDevice.class);
|
||||
when(mDevice.getPersistentDeviceId()).thenReturn(PERSISTENT_DEVICE_ID);
|
||||
when(mVirtualDeviceManager.getVirtualDevice(DEVICE_ID)).thenReturn(virtualDevice);
|
||||
when(virtualDevice.getPersistentDeviceId()).thenReturn(PERSISTENT_DEVICE_ID);
|
||||
|
||||
mController.onVirtualDeviceCreated(DEVICE_ID);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mSummary.getText().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_connected));
|
||||
}
|
||||
}
|
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
|
||||
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settingslib.search.SearchIndexableRaw;
|
||||
|
||||
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.Shadows;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VirtualDeviceListControllerTest {
|
||||
|
||||
private static final String PREFERENCE_KEY = "virtual_device_list";
|
||||
|
||||
private static final CharSequence DEVICE_NAME = "Device Name";
|
||||
private static final int DEVICE_ID = 42;
|
||||
private static final String PERSISTENT_DEVICE_ID = "PersistentDeviceId";
|
||||
private static final String DEVICE_PREFERENCE_KEY = PERSISTENT_DEVICE_ID + "_" + DEVICE_NAME;
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Mock
|
||||
PreferenceManager mPreferenceManager;
|
||||
@Mock
|
||||
AssociationInfo mAssociationInfo;
|
||||
@Mock
|
||||
PreferenceScreen mScreen;
|
||||
|
||||
private VirtualDeviceWrapper mDevice;
|
||||
private VirtualDeviceListController mController;
|
||||
private PreferenceGroup mPreferenceGroup;
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
mPreferenceGroup = spy(new PreferenceScreen(mContext, null));
|
||||
when(mPreferenceManager.getSharedPreferences()).thenReturn(mock(SharedPreferences.class));
|
||||
when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager);
|
||||
when(mScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreferenceGroup);
|
||||
when(mScreen.getContext()).thenReturn(mContext);
|
||||
mController = new VirtualDeviceListController(mContext, PREFERENCE_KEY);
|
||||
DashboardFragment fragment = mock(DashboardFragment.class);
|
||||
when(fragment.getContext()).thenReturn(mContext);
|
||||
mController.setFragment(fragment);
|
||||
|
||||
when(mAssociationInfo.getDisplayName()).thenReturn(DEVICE_NAME);
|
||||
mDevice = new VirtualDeviceWrapper(mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAvailabilityStatus_vdmDisabled() {
|
||||
Resources resources = spy(mContext.getResources());
|
||||
when(mContext.getResources()).thenReturn(resources);
|
||||
when(resources.getBoolean(com.android.internal.R.bool.config_enableVirtualDeviceManager))
|
||||
.thenReturn(false);
|
||||
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(android.companion.virtualdevice.flags.Flags.FLAG_VDM_SETTINGS)
|
||||
public void getAvailabilityStatus_flagDisabled() {
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_VDM_SETTINGS)
|
||||
public void getAvailabilityStatus_available() {
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceAdded_createPreference() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onDeviceAdded(mDevice);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
|
||||
Preference preference = mPreferenceGroup.findPreference(DEVICE_PREFERENCE_KEY);
|
||||
assertThat(preference).isNotNull();
|
||||
assertThat(preference.getTitle().toString()).isEqualTo(DEVICE_NAME.toString());
|
||||
assertThat(Shadows.shadowOf(preference.getIcon()).getCreatedFromResId())
|
||||
.isEqualTo(R.drawable.ic_devices_other);
|
||||
assertThat(preference.getSummary().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_connected));
|
||||
|
||||
assertThat(preference).isEqualTo(mController.mPreferences.get(PERSISTENT_DEVICE_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceChanged_updateSummary() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onDeviceAdded(mDevice);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
mDevice.setDeviceId(Context.DEVICE_ID_INVALID);
|
||||
mController.onDeviceChanged(mDevice);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
|
||||
Preference preference = mPreferenceGroup.findPreference(DEVICE_PREFERENCE_KEY);
|
||||
assertThat(preference).isNotNull();
|
||||
assertThat(preference.getSummary().toString())
|
||||
.isEqualTo(mContext.getString(R.string.virtual_device_disconnected));
|
||||
|
||||
assertThat(preference).isEqualTo(mController.mPreferences.get(PERSISTENT_DEVICE_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceRemoved_removePreference() {
|
||||
mController.displayPreference(mScreen);
|
||||
mController.onDeviceAdded(mDevice);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
mDevice.setDeviceId(Context.DEVICE_ID_INVALID);
|
||||
mController.onDeviceRemoved(mDevice);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
|
||||
assertThat(mController.mPreferences).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateDynamicRawDataToIndex_available() {
|
||||
assumeTrue(mController.isAvailable());
|
||||
|
||||
mController.mVirtualDeviceUpdater = mock(VirtualDeviceUpdater.class);
|
||||
when(mController.mVirtualDeviceUpdater.loadDevices()).thenReturn(List.of(mDevice));
|
||||
|
||||
ArrayList<SearchIndexableRaw> searchData = new ArrayList<>();
|
||||
mController.updateDynamicRawDataToIndex(searchData);
|
||||
|
||||
assertThat(searchData).hasSize(1);
|
||||
SearchIndexableRaw data = searchData.getFirst();
|
||||
assertThat(data.key).isEqualTo(DEVICE_PREFERENCE_KEY);
|
||||
assertThat(data.title).isEqualTo(DEVICE_NAME.toString());
|
||||
assertThat(data.summaryOn)
|
||||
.isEqualTo(mContext.getString(R.string.connected_device_connections_title));
|
||||
}
|
||||
}
|
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.android.settingslib.drawer.TileUtils.IA_SETTINGS_ACTION;
|
||||
|
||||
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.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.companion.virtual.VirtualDevice;
|
||||
import android.companion.virtual.VirtualDeviceManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.core.content.pm.ApplicationInfoBuilder;
|
||||
|
||||
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.Shadows;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VirtualDeviceUpdaterTest {
|
||||
|
||||
private static final String PERSISTENT_DEVICE_ID = "companion:42";
|
||||
private static final int ASSOCIATION_ID = 42;
|
||||
private static final int DEVICE_ID = 7;
|
||||
private static final String PACKAGE_NAME = "test.package.name";
|
||||
|
||||
@Mock
|
||||
private VirtualDeviceManager mVirtualDeviceManager;
|
||||
@Mock
|
||||
private CompanionDeviceManager mCompanionDeviceManager;
|
||||
@Mock
|
||||
private VirtualDeviceUpdater.DeviceListener mDeviceListener;
|
||||
@Mock
|
||||
private AssociationInfo mAssociationInfo;
|
||||
@Mock
|
||||
private VirtualDevice mVirtualDevice;
|
||||
private ShadowPackageManager mPackageManager;
|
||||
|
||||
private VirtualDeviceUpdater mVirtualDeviceUpdater;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context context = spy(ApplicationProvider.getApplicationContext());
|
||||
when(context.getSystemService(VirtualDeviceManager.class))
|
||||
.thenReturn(mVirtualDeviceManager);
|
||||
when(context.getSystemService(CompanionDeviceManager.class))
|
||||
.thenReturn(mCompanionDeviceManager);
|
||||
mPackageManager = Shadows.shadowOf(context.getPackageManager());
|
||||
mVirtualDeviceUpdater = new VirtualDeviceUpdater(context, mDeviceListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_noDevices() {
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_noAssociationInfo() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_invalidAssociationId() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{"NotACompanionPersistentId"}));
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_noMatchingAssociationId() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID + 1);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_excludePackageFromSettings() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
final ApplicationInfo appInfo =
|
||||
ApplicationInfoBuilder.newBuilder().setPackageName(PACKAGE_NAME).build();
|
||||
final ActivityInfo activityInfo = new ActivityInfo();
|
||||
activityInfo.packageName = PACKAGE_NAME;
|
||||
activityInfo.name = PACKAGE_NAME;
|
||||
activityInfo.applicationInfo = appInfo;
|
||||
final ResolveInfo resolveInfo = new ResolveInfo();
|
||||
resolveInfo.activityInfo = activityInfo;
|
||||
final Intent intent = new Intent(IA_SETTINGS_ACTION);
|
||||
intent.setPackage(PACKAGE_NAME);
|
||||
mPackageManager.addResolveInfoForIntent(intent, resolveInfo);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_newDevice_inactive() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, Context.DEVICE_ID_INVALID);
|
||||
verify(mDeviceListener).onDeviceAdded(device);
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_newDevice_active() {
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mVirtualDeviceManager.getVirtualDevices())
|
||||
.thenReturn(List.of(mVirtualDevice));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
when(mVirtualDevice.getDeviceId()).thenReturn(DEVICE_ID);
|
||||
when(mVirtualDevice.getPersistentDeviceId()).thenReturn(PERSISTENT_DEVICE_ID);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
verify(mDeviceListener).onDeviceAdded(device);
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_removeDevice() {
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
mVirtualDeviceUpdater.mDevices.put(PERSISTENT_DEVICE_ID, device);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verify(mDeviceListener).onDeviceRemoved(device);
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_noChanges_activeDevice() {
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
mVirtualDeviceUpdater.mDevices.put(PERSISTENT_DEVICE_ID, device);
|
||||
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mVirtualDeviceManager.getVirtualDevices())
|
||||
.thenReturn(List.of(mVirtualDevice));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
when(mVirtualDevice.getDeviceId()).thenReturn(DEVICE_ID);
|
||||
when(mVirtualDevice.getPersistentDeviceId()).thenReturn(PERSISTENT_DEVICE_ID);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_noChanges_inactiveDevice() {
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, Context.DEVICE_ID_INVALID);
|
||||
mVirtualDeviceUpdater.mDevices.put(PERSISTENT_DEVICE_ID, device);
|
||||
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_deviceChange_activeToInactive() {
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, DEVICE_ID);
|
||||
mVirtualDeviceUpdater.mDevices.put(PERSISTENT_DEVICE_ID, device);
|
||||
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
|
||||
device.setDeviceId(Context.DEVICE_ID_INVALID);
|
||||
verify(mDeviceListener).onDeviceChanged(device);
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadDevices_deviceChange_inactiveToActive() {
|
||||
VirtualDeviceWrapper device = new VirtualDeviceWrapper(
|
||||
mAssociationInfo, PERSISTENT_DEVICE_ID, Context.DEVICE_ID_INVALID);
|
||||
mVirtualDeviceUpdater.mDevices.put(PERSISTENT_DEVICE_ID, device);
|
||||
|
||||
when(mVirtualDeviceManager.getAllPersistentDeviceIds())
|
||||
.thenReturn(new ArraySet<>(new String[]{PERSISTENT_DEVICE_ID}));
|
||||
when(mVirtualDeviceManager.getVirtualDevices())
|
||||
.thenReturn(List.of(mVirtualDevice));
|
||||
when(mCompanionDeviceManager.getAllAssociations(UserHandle.USER_ALL))
|
||||
.thenReturn(List.of(mAssociationInfo));
|
||||
when(mAssociationInfo.getId()).thenReturn(ASSOCIATION_ID);
|
||||
when(mAssociationInfo.getPackageName()).thenReturn(PACKAGE_NAME);
|
||||
when(mVirtualDevice.getDeviceId()).thenReturn(DEVICE_ID);
|
||||
when(mVirtualDevice.getPersistentDeviceId()).thenReturn(PERSISTENT_DEVICE_ID);
|
||||
|
||||
|
||||
mVirtualDeviceUpdater.loadDevices();
|
||||
|
||||
device.setDeviceId(DEVICE_ID);
|
||||
verify(mDeviceListener).onDeviceChanged(device);
|
||||
verifyNoMoreInteractions(mDeviceListener);
|
||||
assertThat(mVirtualDeviceUpdater.mDevices).containsExactly(PERSISTENT_DEVICE_ID, device);
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.settings.connecteddevice.virtual;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.companion.AssociationInfo;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settings.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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VirtualDeviceWrapperTest {
|
||||
|
||||
private static final String PERSISTENT_DEVICE_ID = "PersistentDeviceIdForTest";
|
||||
private static final String DEVICE_NAME = "DEVICE NAME";
|
||||
|
||||
@Mock
|
||||
private AssociationInfo mAssociationInfo;
|
||||
@Mock
|
||||
private Context mContext;
|
||||
private VirtualDeviceWrapper mVirtualDeviceWrapper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mVirtualDeviceWrapper = new VirtualDeviceWrapper(mAssociationInfo, PERSISTENT_DEVICE_ID,
|
||||
Context.DEVICE_ID_INVALID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeviceId() {
|
||||
assertThat(mVirtualDeviceWrapper.getDeviceId()).isEqualTo(Context.DEVICE_ID_INVALID);
|
||||
mVirtualDeviceWrapper.setDeviceId(42);
|
||||
assertThat(mVirtualDeviceWrapper.getDeviceId()).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDisplayName_fromAssociationInfo() {
|
||||
when(mAssociationInfo.getDisplayName()).thenReturn(DEVICE_NAME);
|
||||
assertThat(mVirtualDeviceWrapper.getDeviceName(mContext).toString()).isEqualTo(DEVICE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDisplayName_fromResources() {
|
||||
when(mAssociationInfo.getDisplayName()).thenReturn(null);
|
||||
when(mContext.getString(R.string.virtual_device_unknown)).thenReturn(DEVICE_NAME);
|
||||
assertThat(mVirtualDeviceWrapper.getDeviceName(mContext).toString()).isEqualTo(DEVICE_NAME);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user