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",
|
"BiometricsSharedLib",
|
||||||
"SystemUIUnfoldLib",
|
"SystemUIUnfoldLib",
|
||||||
"WifiTrackerLib",
|
"WifiTrackerLib",
|
||||||
|
"android.companion.flags-aconfig-java",
|
||||||
|
"android.companion.virtualdevice.flags-aconfig-java",
|
||||||
"android.hardware.biometrics.flags-aconfig-java",
|
"android.hardware.biometrics.flags-aconfig-java",
|
||||||
"android.hardware.dumpstate-V1-java",
|
"android.hardware.dumpstate-V1-java",
|
||||||
"android.hardware.dumpstate-V1.0-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.GET_BACKGROUND_INSTALLED_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
|
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
|
||||||
<uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
|
<uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".SettingsApplication"
|
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>
|
<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. -->
|
<!-- 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>
|
<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] -->
|
<!-- 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>
|
<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] -->
|
<!-- Summary for the panel of add Wi-Fi network from APP [CHAR LIMIT=NONE] -->
|
||||||
|
@@ -17,7 +17,8 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0"
|
||||||
|
android:tint="?android:attr/colorControlNormal">
|
||||||
<path
|
<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: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"
|
android:fillType="evenOdd"
|
||||||
|
@@ -13290,6 +13290,21 @@
|
|||||||
<!-- Label for button to not allow grant the permission for remote devices. [CHAR_LIMIT=50] -->
|
<!-- 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>
|
<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] -->
|
<!-- Title for UWB preference [CHAR_LIMIT=60] -->
|
||||||
<string name="uwb_settings_title">Ultra-Wideband (UWB)</string>
|
<string name="uwb_settings_title">Ultra-Wideband (UWB)</string>
|
||||||
|
|
||||||
|
@@ -74,6 +74,13 @@
|
|||||||
android:summary="@string/summary_placeholder"
|
android:summary="@string/summary_placeholder"
|
||||||
android:title="@string/print_settings" />
|
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
|
<com.android.settingslib.RestrictedSwitchPreference
|
||||||
android:key="uwb_settings"
|
android:key="uwb_settings"
|
||||||
android:order="100"
|
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 android.provider.SearchIndexableResource;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.connecteddevice.virtual.VirtualDeviceListController;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
import com.android.settings.print.PrintSettingPreferenceController;
|
import com.android.settings.print.PrintSettingPreferenceController;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
@@ -74,6 +75,7 @@ public class AdvancedConnectedDeviceDashboardFragment extends DashboardFragment
|
|||||||
getSettingsLifecycle().addObserver(uwbPreferenceController);
|
getSettingsLifecycle().addObserver(uwbPreferenceController);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
use(VirtualDeviceListController.class).setFragment(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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