diff --git a/Android.bp b/Android.bp index c9a409c10ca..a9c3ea0f07b 100644 --- a/Android.bp +++ b/Android.bp @@ -46,7 +46,6 @@ java_library { android_library { name: "Settings-core", - platform_apis: true, defaults: [ "SettingsLib-search-defaults", "SettingsLintDefaults", diff --git a/res/values/strings.xml b/res/values/strings.xml index 746dbf51f36..5873b1abe8c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4362,6 +4362,10 @@ Pointer speed Learn touchpad gestures + + trackpad, track pad, mouse, cursor, scroll, swipe, right click, click, pointer + + right click, tap Go home diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml index 84ea5288f89..cca92a30673 100644 --- a/res/xml/trackpad_settings.xml +++ b/res/xml/trackpad_settings.xml @@ -49,7 +49,8 @@ android:summary="@string/trackpad_bottom_right_tap_summary" android:icon="@drawable/ic_trackpad_bottom_right_click" settings:controller="com.android.settings.inputmethod.TrackpadBottomPreferenceController" - android:order="30"/> + android:order="30" + settings:keywords="@string/keywords_trackpad_bottom_right_tap"/> - \ No newline at end of file + diff --git a/src/com/android/settings/ActionDisabledByAppOpsDialog.java b/src/com/android/settings/ActionDisabledByAppOpsDialog.java index 15b8503a4c8..be3558eef86 100644 --- a/src/com/android/settings/ActionDisabledByAppOpsDialog.java +++ b/src/com/android/settings/ActionDisabledByAppOpsDialog.java @@ -21,7 +21,6 @@ import android.app.AppOpsManager; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.util.Log; public class ActionDisabledByAppOpsDialog extends Activity implements DialogInterface.OnDismissListener { diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java index 0dbf05ea5da..4e9cd92d6c0 100644 --- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java @@ -43,6 +43,7 @@ import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.accessibility.AccessibilityUtils; import java.util.List; @@ -164,16 +165,9 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { if (permittedServices != null && !permittedServices.contains(packageName)) { return false; } - try { - final int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, packageName); - final boolean ecmEnabled = getContext().getResources().getBoolean( - com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); - return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - } catch (Exception e) { - // Fallback in case if app ops is not available in testing. - return true; - } + + return !RestrictedLockUtilsInternal.isEnhancedConfirmationRestricted(getContext(), + packageName, AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); } private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) { diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java index 6f21fb87a30..03bf142f9a2 100644 --- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -235,10 +235,11 @@ public class RestrictedPreferenceHelper { boolean serviceAllowed = permittedServices == null || permittedServices.contains( preference.getPackageName()); - if (android.security.Flags.extendEcmToAllSettings()) { + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { preference.checkEcmRestrictionAndSetDisabled( AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE, - preference.getPackageName(), preference.getUid()); + preference.getPackageName()); if (preference.isDisabledByEcm()) { serviceAllowed = false; } @@ -257,40 +258,39 @@ public class RestrictedPreferenceHelper { preference.setEnabled(false); } } - return; - } - - boolean appOpsAllowed; - if (serviceAllowed) { - try { - final int mode = mAppOps.noteOpNoThrow( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - preference.getUid(), preference.getPackageName()); - final boolean ecmEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); - appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - serviceAllowed = appOpsAllowed; - } catch (Exception e) { - // Allow service in case if app ops is not available in testing. - appOpsAllowed = true; - } } else { - appOpsAllowed = false; - } - if (serviceAllowed || serviceEnabled) { - preference.setEnabled(true); - } else { - // Disable accessibility service that are not permitted. - final RestrictedLockUtils.EnforcedAdmin admin = - RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( - mContext, preference.getPackageName(), UserHandle.myUserId()); - - if (admin != null) { - preference.setDisabledByAdmin(admin); - } else if (!appOpsAllowed) { - preference.setDisabledByAppOps(true); + boolean appOpsAllowed; + if (serviceAllowed) { + try { + final int mode = mAppOps.noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + preference.getUid(), preference.getPackageName()); + final boolean ecmEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); + appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; + serviceAllowed = appOpsAllowed; + } catch (Exception e) { + // Allow service in case if app ops is not available in testing. + appOpsAllowed = true; + } } else { - preference.setEnabled(false); + appOpsAllowed = false; + } + if (serviceAllowed || serviceEnabled) { + preference.setEnabled(true); + } else { + // Disable accessibility service that are not permitted. + final RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( + mContext, preference.getPackageName(), UserHandle.myUserId()); + + if (admin != null) { + preference.setDisabledByAdmin(admin); + } else if (!appOpsAllowed) { + preference.setDisabledByAppOps(true); + } else { + preference.setEnabled(false); + } } } } diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java index 6976149f4d7..e111942f751 100644 --- a/src/com/android/settings/applications/UsageAccessDetails.java +++ b/src/com/android/settings/applications/UsageAccessDetails.java @@ -174,7 +174,7 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc if (shouldEnable && !hasAccess) { mSwitchPref.checkEcmRestrictionAndSetDisabled(AppOpsManager.OPSTR_GET_USAGE_STATS, - mPackageName, mPackageInfo.applicationInfo.uid); + mPackageName); shouldEnable = !mSwitchPref.isDisabledByEcm(); } diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 82d55f3d9f8..90d733e6fe1 100644 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -24,6 +24,7 @@ import android.app.Activity; import android.app.AppOpsManager; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; +import android.app.ecm.EnhancedConfirmationManager; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; import android.content.Context; @@ -490,12 +491,23 @@ public class AppInfoDashboardFragment extends DashboardFragment return true; case ACCESS_RESTRICTED_SETTINGS: showLockScreen(getContext(), () -> { - final AppOpsManager appOpsManager = getContext().getSystemService( - AppOpsManager.class); - appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - getUid(), - getPackageName(), - AppOpsManager.MODE_ALLOWED); + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + EnhancedConfirmationManager manager = getContext().getSystemService( + EnhancedConfirmationManager.class); + try { + manager.clearRestriction(getPackageName()); + } catch (NameNotFoundException e) { + Log.e(TAG, "Exception when retrieving package:" + getPackageName(), e); + } + } else { + final AppOpsManager appOpsManager = getContext().getSystemService( + AppOpsManager.class); + appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + getUid(), + getPackageName(), + AppOpsManager.MODE_ALLOWED); + } getActivity().invalidateOptionsMenu(); final String toastString = getContext().getString( R.string.toast_allows_restricted_settings_successfully, @@ -527,14 +539,25 @@ public class AppInfoDashboardFragment extends DashboardFragment } private boolean shouldShowAccessRestrictedSettings() { - try { - final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(), - getPackageName()); - return mode == AppOpsManager.MODE_IGNORED; - } catch (Exception e) { - // Fallback in case if app ops is not available in testing. - return false; + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + try { + return getSystemService(EnhancedConfirmationManager.class) + .isClearRestrictionAllowed(getPackageName()); + } catch (NameNotFoundException e) { + Log.e(TAG, "Exception when retrieving package:" + getPackageName(), e); + return false; + } + } else { + try { + final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(), + getPackageName()); + return mode == AppOpsManager.MODE_IGNORED; + } catch (Exception e) { + // Fallback in case if app ops is not available in testing. + return false; + } } } diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java index 949577b8c14..640f21dec0e 100644 --- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java @@ -222,7 +222,7 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle pref.setOnPreferenceChangeListener((preference, newValue) -> false); pref.setSingleLineTitle(true); pref.checkEcmRestrictionAndSetDisabled(Manifest.permission.BIND_DEVICE_ADMIN, - item.getPackageName(), item.getUid()); + item.getPackageName()); } /** diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java index 00f06258ee0..70a31b8e8f0 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java @@ -24,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -42,7 +43,7 @@ public class ApprovalPreferenceController extends BasePreferenceController { private NotificationManager mNm; private PackageManager mPm; // The appOp representing this preference - private String mAppOpStr; + private String mSettingIdentifier; public ApprovalPreferenceController(Context context, String key) { super(context, key); @@ -76,8 +77,9 @@ public class ApprovalPreferenceController extends BasePreferenceController { /** * Set the associated appOp for the Setting */ - public ApprovalPreferenceController setAppOpStr(String appOpStr) { - mAppOpStr = appOpStr; + @NonNull + public ApprovalPreferenceController setSettingIdentifier(@NonNull String settingIdentifier) { + mSettingIdentifier = settingIdentifier; return this; } @@ -118,14 +120,15 @@ public class ApprovalPreferenceController extends BasePreferenceController { } }); - if (android.security.Flags.extendEcmToAllSettings()) { + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { if (!isAllowedCn && !isEnabled) { preference.setEnabled(false); } else if (isEnabled) { preference.setEnabled(true); } else { - preference.checkEcmRestrictionAndSetDisabled(mAppOpStr, - mCn.getPackageName(), mPkgInfo.applicationInfo.uid); + preference.checkEcmRestrictionAndSetDisabled(mSettingIdentifier, + mCn.getPackageName()); } } else { preference.updateState( diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index 89767ddb011..e885d5c3d17 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -103,7 +103,7 @@ public class NotificationAccessDetails extends DashboardFragment { .setCn(mComponentName) .setNm(context.getSystemService(NotificationManager.class)) .setPm(mPm) - .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS) + .setSettingIdentifier(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS) .setParent(this); use(HeaderPreferenceController.class) .setFragment(this) diff --git a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java index 4c9f813fc5e..2a5f1dbcf04 100644 --- a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java +++ b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java @@ -25,7 +25,6 @@ import android.view.View; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.preference.DropDownPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceScreen; @@ -38,6 +37,7 @@ import com.android.settings.applications.AppStateSmsPremBridge.SmsState; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.EmptyTextSettings; +import com.android.settingslib.RestrictedDropDownPreference; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.Callbacks; @@ -52,6 +52,8 @@ import java.util.ArrayList; public class PremiumSmsAccess extends EmptyTextSettings implements Callback, Callbacks, OnPreferenceChangeListener { + private static final String ECM_RESTRICTION_KEY = "android:premium_sms_access"; + private ApplicationsState mApplicationsState; private AppStateSmsPremBridge mSmsBackend; private Session mSession; @@ -205,7 +207,7 @@ public class PremiumSmsAccess extends EmptyTextSettings } - private class PremiumSmsPreference extends DropDownPreference { + private class PremiumSmsPreference extends RestrictedDropDownPreference { private final AppEntry mAppEntry; public PremiumSmsPreference(AppEntry appEntry, Context context) { @@ -224,6 +226,7 @@ public class PremiumSmsAccess extends EmptyTextSettings }); setValue(String.valueOf(getCurrentValue())); setSummary("%s"); + this.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, appEntry.info.packageName); } private int getCurrentValue() { diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index 23ba4f66379..8250f70e738 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -23,7 +23,7 @@ import android.util.Log; import androidx.preference.Preference; import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -72,6 +72,32 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) { Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile); + // If device is LE Audio, it is compatible with HFP and A2DP. + // It would show in Available Devices group if the audio sharing flag is disabled or + // the device is not in the audio sharing session. + if (cachedDevice.isConnectedLeAudioDevice()) { + boolean isAudioSharingFilterMatched = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .isAudioSharingFilterMatched(cachedDevice, mLocalManager); + if (!isAudioSharingFilterMatched) { + Log.d( + TAG, + "isFilterMatched() device : " + + cachedDevice.getName() + + ", the LE Audio profile is connected and not in sharing " + + "if broadcast enabled."); + return true; + } else { + Log.d( + TAG, + "Filter out device : " + + cachedDevice.getName() + + ", it is in audio sharing."); + return false; + } + } + // If device is Hearing Aid, it is compatible with HFP and A2DP. // It would show in Available Devices group. if (cachedDevice.isConnectedAshaHearingAidDevice()) { @@ -82,20 +108,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater + ", the Hearing Aid profile is connected."); return true; } - // If device is LE Audio, it is compatible with HFP and A2DP. - // It would show in Available Devices group if the audio sharing flag is disabled or - // the device is not in the audio sharing session. - if (cachedDevice.isConnectedLeAudioDevice()) { - if (!AudioSharingUtils.isFeatureEnabled() - || !AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalManager)) { - Log.d( - TAG, - "isFilterMatched() device : " - + cachedDevice.getName() - + ", the LE Audio profile is connected and not in sharing."); - return true; - } - } + // According to the current audio profile type, // this page will show the bluetooth device that have corresponding profile. // For example: @@ -125,13 +138,9 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference).getBluetoothDevice(); - if (AudioSharingUtils.isFeatureEnabled() - && AudioSharingUtils.isBroadcasting(mLocalBtManager)) { - if (DBG) { - Log.d(TAG, "onPreferenceClick stop broadcasting."); - } - AudioSharingUtils.stopBroadcasting(mLocalBtManager); - } + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .handleMediaDeviceOnClick(mLocalManager); return device.setActive(); } diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt index 77a80b8eb1c..3224f94a09b 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt @@ -34,8 +34,10 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup import com.android.settings.R import com.android.settings.dashboard.RestrictedDashboardFragment +import com.android.settings.flags.Flags import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.BluetoothDeviceFilter +import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -217,6 +219,14 @@ abstract class DeviceListPreferenceFragment(restrictedKey: String?) : ) return } + if (Flags.enableHideExclusivelyManagedBluetoothDevice()) { + if (cachedDevice.device.bondState == BluetoothDevice.BOND_BONDED + && BluetoothUtils.isExclusivelyManagedBluetoothDevice( + prefContext, cachedDevice.device)) { + Log.d(TAG, "Trying to create preference for a exclusively managed device") + return + } + } // Only add device preference when it's not found in the map and there's no other state // message showing in the list val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) { diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java index 27001d6e071..2798be45186 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java @@ -22,12 +22,13 @@ import android.provider.DeviceConfig; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; -import com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController; import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.dashboard.DashboardFragment; @@ -36,8 +37,12 @@ import com.android.settings.overlay.SurveyFeatureProvider; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.slices.SlicePreferenceController; import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; +import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; +import java.util.ArrayList; +import java.util.List; + @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class ConnectedDeviceDashboardFragment extends DashboardFragment { @@ -87,9 +92,6 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { + ", action : " + action); } - if (AudioSharingUtils.isFeatureEnabled()) { - use(AudioSharingDevicePreferenceController.class).init(this); - } use(AvailableMediaDeviceGroupController.class).init(this); use(ConnectedDeviceGroupController.class).init(this); use(PreviouslyConnectedDevicePreferenceController.class).init(this); @@ -112,6 +114,29 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { } } + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, /* fragment= */ this, getSettingsLifecycle()); + } + + private static List buildPreferenceControllers( + Context context, + @Nullable ConnectedDeviceDashboardFragment fragment, + @Nullable Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + if (AudioSharingUtils.isFeatureEnabled()) { + AbstractPreferenceController audioSharingController = + FeatureFactory.getFeatureFactory() + .getAudioSharingFeatureProvider() + .createAudioSharingDevicePreferenceController( + context, fragment, lifecycle); + if (audioSharingController != null) { + controllers.add(audioSharingController); + } + } + return controllers; + } + @VisibleForTesting boolean isAlwaysDiscoverable(String callingAppPackageName, String action) { return TextUtils.equals(SLICE_ACTION, action) @@ -122,5 +147,12 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { /** For Search. */ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.connected_devices); + new BaseSearchIndexProvider(R.xml.connected_devices) { + @Override + public List createPreferenceControllers( + Context context) { + return buildPreferenceControllers( + context, /* fragment= */ null, /* lifecycle= */ null); + } + }; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java new file mode 100644 index 00000000000..c71a368640c --- /dev/null +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java @@ -0,0 +1,50 @@ +/* + * 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.audiosharing; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; + +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.core.AbstractPreferenceController; + +/** Feature provider for the audio sharing related features, */ +public interface AudioSharingFeatureProvider { + + /** Create audio sharing device preference controller. */ + @Nullable + AbstractPreferenceController createAudioSharingDevicePreferenceController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle); + + /** + * Check if the device match the audio sharing filter. + * + *

The filter is used to filter device in "Media devices" section. + */ + boolean isAudioSharingFilterMatched( + @NonNull CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager); + + /** Handle preference onClick in "Media devices" section. */ + void handleMediaDeviceOnClick(LocalBluetoothManager localBtManager); +} diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java new file mode 100644 index 00000000000..05a6a6383af --- /dev/null +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java @@ -0,0 +1,49 @@ +/* + * 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.audiosharing; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; + +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.core.AbstractPreferenceController; + +public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvider { + + @Nullable + @Override + public AbstractPreferenceController createAudioSharingDevicePreferenceController( + @NonNull Context context, + @Nullable DashboardFragment fragment, + @Nullable Lifecycle lifecycle) { + return null; + } + + @Override + public boolean isAudioSharingFilterMatched( + @NonNull CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { + return false; + } + + @Override + public void handleMediaDeviceOnClick(LocalBluetoothManager localBtManager) {} +} diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java index 7a7eb8cc189..b3e66a9ce06 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java @@ -20,7 +20,9 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; @@ -36,6 +38,7 @@ import com.android.settingslib.widget.AppSwitchPreference; public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements DataSaverBackend.Listener { + private static final String ECM_SETTING_IDENTIFIER = "android:unrestricted_data_access"; private final ApplicationsState mApplicationsState; private final AppEntry mEntry; @@ -58,6 +61,7 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem mParentFragment = parentFragment; setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled( context, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); + mHelper.checkEcmRestrictionAndSetDisabled(ECM_SETTING_IDENTIFIER, entry.info.packageName); updateState(); setKey(generateKey(mEntry)); @@ -166,10 +170,24 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem return mHelper.isDisabledByAdmin(); } + @VisibleForTesting + boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public void setDisabledByAdmin(EnforcedAdmin admin) { mHelper.setDisabledByAdmin(admin); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param packageName the package to check the restriction for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String packageName) { + mHelper.checkEcmRestrictionAndSetDisabled(ECM_SETTING_IDENTIFIER, packageName); + } + // Sets UI state based on allowlist/denylist status. public void updateState() { setTitle(mEntry.label); @@ -179,7 +197,8 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem setSummary(com.android.settingslib.widget.restricted.R.string.disabled_by_admin); } else if (mDataUsageState.isDataSaverDenylisted) { setSummary(R.string.restrict_background_blocklisted); - } else { + // If disabled by ECM, the summary is set directly by the switch. + } else if (!isDisabledByEcm()) { setSummary(""); } } diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java index fd2fcda71e1..0fb30a8e8ac 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java @@ -151,6 +151,7 @@ public class UnrestrictedDataAccessPreferenceController extends BasePreferenceCo } else { preference.setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(mContext, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); + preference.checkEcmRestrictionAndSetDisabled(entry.info.packageName); preference.updateState(); } preference.setOrder(i); diff --git a/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java index 44d77a38e65..9a4b90f4044 100644 --- a/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java +++ b/src/com/android/settings/inputmethod/TrackpadTouchGestureSettings.java @@ -52,7 +52,7 @@ public class TrackpadTouchGestureSettings extends DashboardFragment { } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.trackpad_settings) { + new BaseSearchIndexProvider(R.xml.trackpad_gesture_settings) { @Override protected boolean isPageSearchEnabled(Context context) { return FeatureFlagUtils diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index cea2b4410c7..eda9d7a9704 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -38,17 +38,10 @@ import androidx.compose.ui.res.stringResource import androidx.navigation.NavType import androidx.navigation.navArgument import com.android.settings.R -import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames -import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState -import com.android.settings.network.apn.ApnTypes.APN_TYPES_OPTIONS -import com.android.settings.network.apn.ApnTypes.APN_TYPE_MMS -import com.android.settings.network.apn.ApnTypes.getApnTypeSelectedOptionsState -import com.android.settings.network.apn.ApnTypes.updateApnType import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox -import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword import com.android.settingslib.spa.widget.preference.SwitchPreference @@ -79,7 +72,7 @@ object ApnEditPageProvider : SettingsPageProvider { val uriString = arguments!!.getString(URI) val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString))) val subId = arguments.getInt(SUB_ID) - val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId) + val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId) ?: return val apnDataCur = remember { mutableStateOf(apnDataInit) } @@ -101,12 +94,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur val context = LocalContext.current val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList() val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList() - val networkTypeSelectedOptionsState = remember { - getNetworkTypeSelectedOptionsState(apnData.networkType) - } - var apnTypeSelectedOptionsState = remember { - getApnTypeSelectedOptionsState(apnData.apnType) - } + var apnTypeMmsSelected by remember { mutableStateOf(false) } val navController = LocalNavController.current var valid: String? RegularScaffold( @@ -114,11 +102,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur actions = { if (!apnData.customizedConfig.readOnlyApn) { Button(onClick = { - apnData = apnData.copy( - networkType = ApnNetworkTypes.getNetworkType( - networkTypeSelectedOptionsState - ) - ) valid = validateAndSaveApnData( apnDataInit, apnData, @@ -193,27 +176,12 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur label = stringResource(R.string.apn_server), enabled = apnData.serverEnabled ) { apnData = apnData.copy(server = it) } - SettingsExposedDropdownMenuCheckBox( - label = stringResource(R.string.apn_type), - options = APN_TYPES_OPTIONS, - selectedOptionsState = apnTypeSelectedOptionsState, - enabled = apnData.apnTypeEnabled, - errorMessage = validateAPNType( - apnData.validEnabled, apnData.apnType, - apnData.customizedConfig.readOnlyApnTypes, context - ) - ) { - val apnType = updateApnType( - apnTypeSelectedOptionsState, - apnData.customizedConfig.defaultApnTypes, - apnData.customizedConfig.readOnlyApnTypes - ) - apnTypeSelectedOptionsState = getApnTypeSelectedOptionsState(apnType) - apnData = apnData.copy( - apnType = apnType - ) - } - if (apnTypeSelectedOptionsState.contains(APN_TYPES_OPTIONS.indexOf(APN_TYPE_MMS))) { + ApnTypeCheckBox( + apnData = apnData, + onTypeChanged = { apnData = apnData.copy(apnType = it) }, + onMmsSelectedChanged = { apnTypeMmsSelected = it }, + ) + if (apnTypeMmsSelected) { SettingsOutlinedTextField( value = apnData.mmsc, label = stringResource(R.string.apn_mmsc), @@ -249,13 +217,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur selectedOptionIndex = apnData.apnRoaming, enabled = apnData.apnRoamingEnabled ) { apnData = apnData.copy(apnRoaming = it) } - SettingsExposedDropdownMenuCheckBox( - label = stringResource(R.string.network_type), - options = getNetworkTypeDisplayNames(), - selectedOptionsState = networkTypeSelectedOptionsState, - emptyVal = stringResource(R.string.network_type_unspecified), - enabled = apnData.networkTypeEnabled - ) {} + ApnNetworkTypeCheckBox(apnData) { apnData = apnData.copy(networkType = it) } SwitchPreference( object : SwitchPreferenceModel { override val title = context.resources.getString(R.string.carrier_enabled) diff --git a/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt b/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt new file mode 100644 index 00000000000..bc85f5582b7 --- /dev/null +++ b/src/com/android/settings/network/apn/ApnNetworkTypeCheckBox.kt @@ -0,0 +1,41 @@ +/* + * 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.network.apn + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox + +@Composable +fun ApnNetworkTypeCheckBox(apnData: ApnData, onNetworkTypeChanged: (Long) -> Unit) { + val options = remember { ApnNetworkTypes.getNetworkTypeOptions() } + val selectedStateMap = remember { + ApnNetworkTypes.networkTypeToSelectedStateMap(options, apnData.networkType) + } + SettingsDropdownCheckBox( + label = stringResource(R.string.network_type), + options = options, + emptyText = stringResource(R.string.network_type_unspecified), + enabled = apnData.networkTypeEnabled, + ) { + onNetworkTypeChanged( + ApnNetworkTypes.selectedStateMapToNetworkType(options, selectedStateMap) + ) + } +} diff --git a/src/com/android/settings/network/apn/ApnNetworkTypes.kt b/src/com/android/settings/network/apn/ApnNetworkTypes.kt index 0ccd33a8210..e7a93b3cb79 100644 --- a/src/com/android/settings/network/apn/ApnNetworkTypes.kt +++ b/src/com/android/settings/network/apn/ApnNetworkTypes.kt @@ -17,8 +17,9 @@ package com.android.settings.network.apn import android.telephony.TelephonyManager -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.snapshots.SnapshotStateMap +import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption object ApnNetworkTypes { private val Types = listOf( @@ -39,32 +40,40 @@ object ApnNetworkTypes { TelephonyManager.NETWORK_TYPE_NR, ) - fun getNetworkTypeDisplayNames(): List = - Types.map { TelephonyManager.getNetworkTypeName(it) } + fun getNetworkTypeOptions(): List = + Types.map { SettingsDropdownCheckOption(TelephonyManager.getNetworkTypeName(it)) } /** * Gets the selected Network type Selected Options according to network type. * @param networkType Initialized network type bitmask, often multiple network type options may * be included. */ - fun getNetworkTypeSelectedOptionsState(networkType: Long): SnapshotStateList { - val networkTypeSelectedOptionsState = mutableStateListOf() + fun networkTypeToSelectedStateMap( + options: List, + networkType: Long, + ): SnapshotStateMap { + val stateMap = mutableStateMapOf() Types.forEachIndexed { index, type -> if (networkType and TelephonyManager.getBitMaskForNetworkType(type) != 0L) { - networkTypeSelectedOptionsState.add(index) + stateMap[options[index]] = true } } - return networkTypeSelectedOptionsState + return stateMap } /** * Gets the network type according to the selected Network type Selected Options. - * @param networkTypeSelectedOptionsState the selected Network type Selected Options. + * @param stateMap the selected Network type Selected Options. */ - fun getNetworkType(networkTypeSelectedOptionsState: SnapshotStateList): Long { + fun selectedStateMapToNetworkType( + options: List, + stateMap: SnapshotStateMap, + ): Long { var networkType = 0L - networkTypeSelectedOptionsState.forEach { option -> - networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[option]) + options.forEachIndexed { index, option -> + if (stateMap[option] == true) { + networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[index]) + } } return networkType } diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index 226698310c1..2d41976b239 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -18,6 +18,7 @@ package com.android.settings.network.apn import android.content.ContentValues import android.content.Context +import android.database.Cursor import android.net.Uri import android.provider.Telephony import android.telephony.SubscriptionManager @@ -27,26 +28,7 @@ import com.android.settings.R import com.android.settingslib.utils.ThreadUtils import java.util.Locale -const val NAME_INDEX = 1 -const val APN_INDEX = 2 -const val PROXY_INDEX = 3 -const val PORT_INDEX = 4 -const val USER_INDEX = 5 -const val SERVER_INDEX = 6 -const val PASSWORD_INDEX = 7 -const val MMSC_INDEX = 8 -const val MMSPROXY_INDEX = 9 -const val MMSPORT_INDEX = 10 -const val AUTH_TYPE_INDEX = 11 -const val TYPE_INDEX = 12 -const val PROTOCOL_INDEX = 13 -const val CARRIER_ENABLED_INDEX = 14 -const val NETWORK_TYPE_INDEX = 15 -const val ROAMING_PROTOCOL_INDEX = 16 -const val EDITED_INDEX = 17 -const val USER_EDITABLE_INDEX = 18 - -val sProjection = arrayOf( +val Projection = arrayOf( Telephony.Carriers._ID, // 0 Telephony.Carriers.NAME, // 1 Telephony.Carriers.APN, // 2 @@ -68,7 +50,7 @@ val sProjection = arrayOf( Telephony.Carriers.USER_EDITABLE, // 18 ) -const val TAG = "ApnRepository" +private const val TAG = "ApnRepository" /** * Query apn related information based on uri. @@ -79,56 +61,39 @@ const val TAG = "ApnRepository" fun getApnDataFromUri(uri: Uri, context: Context): ApnData { var apnData = ApnData() val contentResolver = context.contentResolver - val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() contentResolver.query( uri, - sProjection, + Projection, null /* selection */, null /* selectionArgs */, null /* sortOrder */ ).use { cursor -> if (cursor != null && cursor.moveToFirst()) { - val name = cursor.getString(NAME_INDEX) - val apn = cursor.getString(APN_INDEX) - val proxy = cursor.getString(PROXY_INDEX) - val port = cursor.getString(PORT_INDEX) - val userName = cursor.getString(USER_INDEX) - val server = cursor.getString(SERVER_INDEX) - val passWord = cursor.getString(PASSWORD_INDEX) - val mmsc = cursor.getString(MMSC_INDEX) - val mmsProxy = cursor.getString(MMSPROXY_INDEX) - val mmsPort = cursor.getString(MMSPORT_INDEX) - val authType = cursor.getInt(AUTH_TYPE_INDEX) - val apnType = cursor.getString(TYPE_INDEX) - val apnProtocol = convertProtocol2Options(cursor.getString(PROTOCOL_INDEX), context) - val apnRoaming = - convertProtocol2Options(cursor.getString(ROAMING_PROTOCOL_INDEX), context) - val apnEnable = cursor.getInt(CARRIER_ENABLED_INDEX) == 1 - val networkType = cursor.getLong(NETWORK_TYPE_INDEX) - - val edited = cursor.getInt(EDITED_INDEX) - val userEditable = cursor.getInt(USER_EDITABLE_INDEX) - - apnData = apnData.copy( - name = name, - apn = apn, - proxy = proxy, - port = port, - userName = userName, - passWord = passWord, - server = server, - mmsc = mmsc, - mmsProxy = mmsProxy, - mmsPort = mmsPort, - authType = authType, - apnType = apnType, - apnProtocol = apnProtocolOptions.indexOf(apnProtocol), - apnRoaming = apnProtocolOptions.indexOf(apnRoaming), - apnEnable = apnEnable, - networkType = networkType, - edited = edited, - userEditable = userEditable, + apnData = ApnData( + id = cursor.getInt(Telephony.Carriers._ID), + name = cursor.getString(Telephony.Carriers.NAME), + apn = cursor.getString(Telephony.Carriers.APN), + proxy = cursor.getString(Telephony.Carriers.PROXY), + port = cursor.getString(Telephony.Carriers.PORT), + userName = cursor.getString(Telephony.Carriers.USER), + passWord = cursor.getString(Telephony.Carriers.PASSWORD), + server = cursor.getString(Telephony.Carriers.SERVER), + mmsc = cursor.getString(Telephony.Carriers.MMSC), + mmsProxy = cursor.getString(Telephony.Carriers.MMSPROXY), + mmsPort = cursor.getString(Telephony.Carriers.MMSPORT), + authType = cursor.getInt(Telephony.Carriers.AUTH_TYPE), + apnType = cursor.getString(Telephony.Carriers.TYPE), + apnProtocol = context.convertProtocol2Options( + cursor.getString(Telephony.Carriers.PROTOCOL) + ), + apnRoaming = context.convertProtocol2Options( + cursor.getString(Telephony.Carriers.ROAMING_PROTOCOL) + ), + apnEnable = cursor.getInt(Telephony.Carriers.CARRIER_ENABLED) == 1, + networkType = cursor.getLong(Telephony.Carriers.NETWORK_TYPE_BITMASK), + edited = cursor.getInt(Telephony.Carriers.EDITED_STATUS), + userEditable = cursor.getInt(Telephony.Carriers.USER_EDITABLE), ) } } @@ -138,42 +103,23 @@ fun getApnDataFromUri(uri: Uri, context: Context): ApnData { return apnData } +private fun Cursor.getString(columnName: String) = getString(getColumnIndexOrThrow(columnName)) +private fun Cursor.getInt(columnName: String) = getInt(getColumnIndexOrThrow(columnName)) +private fun Cursor.getLong(columnName: String) = getLong(getColumnIndexOrThrow(columnName)) + /** - * Returns The UI choice (e.g., "IPv4/IPv6") corresponding to the given - * raw value of the protocol preference (e.g., "IPV4V6"). If unknown, - * return null. - * - * @return UI choice + * Returns The UI choice index corresponding to the given raw value of the protocol preference + * (e.g., "IPV4V6"). + * If unknown, return -1. */ -private fun convertProtocol2Options(raw: String, context: Context): String { - val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() - val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - var uRaw = raw.uppercase(Locale.getDefault()) - uRaw = if (uRaw == "IPV4") "IP" else uRaw - val protocolIndex = apnProtocolValues.indexOf(uRaw) - return if (protocolIndex == -1) { - "" - } else { - try { - apnProtocolOptions[protocolIndex] - } catch (e: ArrayIndexOutOfBoundsException) { - "" - } - } +private fun Context.convertProtocol2Options(protocol: String): Int { + var normalizedProtocol = protocol.uppercase(Locale.getDefault()) + if (normalizedProtocol == "IPV4") normalizedProtocol = "IP" + return resources.getStringArray(R.array.apn_protocol_values).indexOf(normalizedProtocol) } -fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { - val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() - return if (protocolIndex == -1) { - "" - } else { - try { - apnProtocolValues[protocolIndex] - } catch (e: ArrayIndexOutOfBoundsException) { - "" - } - } -} +fun Context.convertOptions2Protocol(protocolIndex: Int): String = + resources.getStringArray(R.array.apn_protocol_values).getOrElse(protocolIndex) { "" } fun updateApnDataToDatabase( newApn: Boolean, @@ -183,13 +129,13 @@ fun updateApnDataToDatabase( ) { ThreadUtils.postOnBackgroundThread { if (newApn) { - // Add a new apn to the database + Log.d(TAG, "Adding an new APN to the database $uriInit $values") val newUri = context.contentResolver.insert(uriInit, values) if (newUri == null) { Log.e(TAG, "Can't add a new apn to database $uriInit") } } else { - // Update the existing apn + Log.d(TAG, "Updating an existing APN to the database $uriInit $values") context.contentResolver.update( uriInit, values, null /* where */, null /* selection Args */ ) @@ -210,9 +156,12 @@ private val NonDuplicatedKeys = setOf( ) fun isItemExist(apnData: ApnData, context: Context): String? { - val contentValueMap = apnData.getContentValueMap(context).filterKeys { it in NonDuplicatedKeys } - val list = contentValueMap.entries.toList() - val selection = list.joinToString(" AND ") { "${it.key} = ?" } + val selectionMap = apnData.getContentValueMap(context).filterKeys { it in NonDuplicatedKeys } + .mapKeys { "${it.key} = ?" } + .toMutableMap() + if (apnData.id != -1) selectionMap += "${Telephony.Carriers._ID} != ?" to apnData.id + val list = selectionMap.entries.toList() + val selection = list.joinToString(" AND ") { it.key } val selectionArgs: Array = list.map { it.value.toString() }.toTypedArray() context.contentResolver.query( Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, apnData.subId.toString()), diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt index 02e2814e749..ab16f1ca5d2 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -22,19 +22,14 @@ import android.net.Uri import android.os.Bundle import android.provider.Telephony import android.telephony.CarrierConfigManager -import android.text.TextUtils import android.util.Log -import com.android.internal.util.ArrayUtils import com.android.settings.R -import com.android.settings.network.apn.ApnTypes.APN_TYPES -import com.android.settings.network.apn.ApnTypes.APN_TYPE_ALL -import com.android.settings.network.apn.ApnTypes.APN_TYPE_EMERGENCY -import com.android.settings.network.apn.ApnTypes.APN_TYPE_IA -import com.android.settings.network.apn.ApnTypes.APN_TYPE_IMS -import com.android.settings.network.apn.ApnTypes.APN_TYPE_MCX -import java.util.Locale +import com.android.settings.network.apn.ApnTypes.getPreSelectedApnType + +private const val TAG = "ApnStatus" data class ApnData( + val id: Int = -1, val name: String = "", val apn: String = "", val proxy: String = "", @@ -86,8 +81,8 @@ data class ApnData( Telephony.Carriers.MMSPROXY to mmsProxy, Telephony.Carriers.MMSPORT to mmsPort, Telephony.Carriers.AUTH_TYPE to authType, - Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context), - Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context), + Telephony.Carriers.PROTOCOL to context.convertOptions2Protocol(apnProtocol), + Telephony.Carriers.ROAMING_PROTOCOL to context.convertOptions2Protocol(apnRoaming), Telephony.Carriers.TYPE to apnType, Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType, Telephony.Carriers.CARRIER_ENABLED to apnEnable, @@ -105,7 +100,7 @@ data class CustomizedConfig( val isAddApnAllowed: Boolean = true, val readOnlyApnTypes: List = emptyList(), val readOnlyApnFields: List = emptyList(), - val defaultApnTypes: List = emptyList(), + val defaultApnTypes: List? = null, val defaultApnProtocol: String = "", val defaultApnRoamingProtocol: String = "", ) @@ -118,19 +113,18 @@ data class CustomizedConfig( * * @return Initialized CustomizedConfig information. */ -fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData { - - val uriType = arguments.getString(URI_TYPE)!! +fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData? { + val uriType = arguments.getString(URI_TYPE) ?: return null if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) { Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit") - return ApnData() //TODO: finish + return null } var apnDataInit = when (uriType) { EDIT_URL -> getApnDataFromUri(uriInit, context) INSERT_URL -> ApnData() - else -> ApnData() //TODO: finish + else -> return null } if (uriType == INSERT_URL) { @@ -143,13 +137,18 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int apnDataInit = apnDataInit.copy(customizedConfig = getCarrierCustomizedConfig(apnDataInit, configManager)) + if (apnDataInit.newApn) { + apnDataInit = apnDataInit.copy( + apnType = getPreSelectedApnType(apnDataInit.customizedConfig) + ) + } + apnDataInit = apnDataInit.copy( apnEnableEnabled = context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled) ) // TODO: mIsCarrierIdApn - disableInit(apnDataInit) - return apnDataInit + return disableInit(apnDataInit) } /** @@ -199,53 +198,7 @@ fun validateApnData(apnData: ApnData, context: Context): String? { if (errorMsg == null) { errorMsg = isItemExist(apnData, context) } - if (errorMsg == null) { - errorMsg = validateAPNType( - true, - apnData.apnType, - apnData.customizedConfig.readOnlyApnTypes, - context - ) - } - return errorMsg -} - -private fun getUserEnteredApnType(apnType: String, readOnlyApnTypes: List): String { - // if user has not specified a type, map it to "ALL APN TYPES THAT ARE NOT READ-ONLY" - // but if user enter empty type, map it just for default - var userEnteredApnType = apnType - if (userEnteredApnType != "") userEnteredApnType = - userEnteredApnType.trim { it <= ' ' } - if (TextUtils.isEmpty(userEnteredApnType) || APN_TYPE_ALL == userEnteredApnType) { - userEnteredApnType = getEditableApnType(readOnlyApnTypes) - } - Log.d( - TAG, "getUserEnteredApnType: changed apn type to editable apn types: " - + userEnteredApnType - ) - return userEnteredApnType -} - -private fun getEditableApnType(readOnlyApnTypes: List): String { - val editableApnTypes = StringBuilder() - var first = true - for (apnType in APN_TYPES) { - // add APN type if it is not read-only and is not wild-cardable - if (!readOnlyApnTypes.contains(apnType) - && apnType != APN_TYPE_IA - && apnType != APN_TYPE_EMERGENCY - && apnType != APN_TYPE_MCX - && apnType != APN_TYPE_IMS - ) { - if (first) { - first = false - } else { - editableApnTypes.append(",") - } - editableApnTypes.append(apnType) - } - } - return editableApnTypes.toString() + return errorMsg?.apply { Log.d(TAG, "APN data not valid, reason: $this") } } /** @@ -258,6 +211,10 @@ fun getCarrierCustomizedConfig( apnInit: ApnData, configManager: CarrierConfigManager ): CustomizedConfig { + fun log(message: String) { + Log.d(TAG, "getCarrierCustomizedConfig: $message") + } + val b = configManager.getConfigForSubId( apnInit.subId, CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, @@ -270,72 +227,61 @@ fun getCarrierCustomizedConfig( val customizedConfig = CustomizedConfig( readOnlyApnTypes = b.getStringArray( CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY - )?.toList() ?: emptyList(), readOnlyApnFields = b.getStringArray( + )?.toList() ?: emptyList(), + readOnlyApnFields = b.getStringArray( CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY - )?.toList() ?: emptyList(), defaultApnTypes = b.getStringArray( + )?.toList() ?: emptyList(), + defaultApnTypes = b.getStringArray( CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY - )?.toList() ?: emptyList(), defaultApnProtocol = b.getString( + )?.toList(), + defaultApnProtocol = b.getString( CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING - ) ?: "", defaultApnRoamingProtocol = b.getString( + ) ?: "", + defaultApnRoamingProtocol = b.getString( CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING - ) ?: "", isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL) + ) ?: "", + isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL), ) - if (!ArrayUtils.isEmpty(customizedConfig.readOnlyApnTypes)) { - Log.d( - TAG, - "getCarrierCustomizedConfig: read only APN type: " + customizedConfig.readOnlyApnTypes.joinToString( - ", " - ) - ) + if (customizedConfig.readOnlyApnTypes.isNotEmpty()) { + log("read only APN type: " + customizedConfig.readOnlyApnTypes) } - if (!ArrayUtils.isEmpty(customizedConfig.defaultApnTypes)) { - Log.d( - TAG, - "getCarrierCustomizedConfig: default apn types: " + customizedConfig.defaultApnTypes.joinToString( - ", " - ) - ) + customizedConfig.defaultApnTypes?.takeIf { it.isNotEmpty() }?.let { + log("default apn types: $it") } - if (!TextUtils.isEmpty(customizedConfig.defaultApnProtocol)) { - Log.d( - TAG, - "getCarrierCustomizedConfig: default apn protocol: ${customizedConfig.defaultApnProtocol}" - ) + if (customizedConfig.defaultApnProtocol.isNotEmpty()) { + log("default apn protocol: ${customizedConfig.defaultApnProtocol}") } - if (!TextUtils.isEmpty(customizedConfig.defaultApnRoamingProtocol)) { - Log.d( - TAG, - "getCarrierCustomizedConfig: default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}" - ) + if (customizedConfig.defaultApnRoamingProtocol.isNotEmpty()) { + log("default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}") } if (!customizedConfig.isAddApnAllowed) { - Log.d(TAG, "getCarrierCustomizedConfig: not allow to add new APN") + log("not allow to add new APN") } return customizedConfig } -fun disableInit(apnDataInit: ApnData): ApnData { - var apnData = apnDataInit - val isUserEdited = apnDataInit.edited == Telephony.Carriers.USER_EDITED - Log.d(TAG, "disableInit: EDITED $isUserEdited") +private fun ApnData.isReadOnly(): Boolean { + Log.d(TAG, "isReadOnly: edited $edited") + if (edited == Telephony.Carriers.USER_EDITED) return false // if it's not a USER_EDITED apn, check if it's read-only - if (!isUserEdited && (apnDataInit.userEditable == 0 - || apnTypesMatch(apnDataInit.customizedConfig.readOnlyApnTypes, apnDataInit.apnType)) - ) { + return userEditable == 0 || + ApnTypes.isApnTypeReadOnly(apnType, customizedConfig.readOnlyApnTypes) +} + +fun disableInit(apnDataInit: ApnData): ApnData { + if (apnDataInit.isReadOnly()) { Log.d(TAG, "disableInit: read-only APN") - apnData = - apnDataInit.copy(customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true)) - apnData = disableAllFields(apnData) - } else if (!ArrayUtils.isEmpty(apnData.customizedConfig.readOnlyApnFields)) { - Log.d( - TAG, - "disableInit: mReadOnlyApnFields ${ - apnData.customizedConfig.readOnlyApnFields.joinToString(", ") - })" + val apnData = apnDataInit.copy( + customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true) ) - apnData = disableFields(apnData.customizedConfig.readOnlyApnFields, apnData) + return disableAllFields(apnData) } - return apnData + val readOnlyApnFields = apnDataInit.customizedConfig.readOnlyApnFields + if (readOnlyApnFields.isNotEmpty()) { + Log.d(TAG, "disableInit: readOnlyApnFields $readOnlyApnFields)") + return disableFields(readOnlyApnFields, apnDataInit) + } + return apnDataInit } /** @@ -402,23 +348,6 @@ private fun disableByFieldName(apnField: String, apnDataInit: ApnData): ApnData return apnData } -private fun apnTypesMatch(apnTypeList: List, apnType: String): Boolean { - val normalizeApnTypeList = apnTypeList.map(::normalizeApnType) - return hasAllApns(normalizeApnTypeList) || - apnType.split(",").map(::normalizeApnType).all { it in normalizeApnTypeList } -} - -fun hasAllApns(apnTypes: List): Boolean { - if (APN_TYPE_ALL in apnTypes) { - Log.d(TAG, "hasAllApns: true because apnTypes.contains(APN_TYPE_ALL)") - return true - } - return APN_TYPES.all { it in apnTypes } -} - -private fun normalizeApnType(apnType: String): String = - apnType.trim().lowercase(Locale.getDefault()) - fun deleteApn(uri: Uri, context: Context) { val contentResolver = context.contentResolver contentResolver.delete(uri, null, null) @@ -439,24 +368,3 @@ fun validateAPN(validEnabled: Boolean, apn: String, context: Context): String? { return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty) else null } - -fun validateAPNType( - validEnabled: Boolean, - apnType: String, - readOnlyApnTypes: List, - context: Context -): String? { - // if carrier does not allow editing certain apn types, make sure type does not include those - if (validEnabled && !ArrayUtils.isEmpty(readOnlyApnTypes) - && apnTypesMatch( - readOnlyApnTypes, - getUserEnteredApnType(apnType, readOnlyApnTypes) - ) - ) { - return String.format( - context.resources.getString(R.string.error_adding_apn_type), - readOnlyApnTypes.joinToString(", ") - ) - } - return null -} \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnTypeCheckBox.kt b/src/com/android/settings/network/apn/ApnTypeCheckBox.kt new file mode 100644 index 00000000000..4d0659c659c --- /dev/null +++ b/src/com/android/settings/network/apn/ApnTypeCheckBox.kt @@ -0,0 +1,53 @@ +/* + * 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.network.apn + +import android.telephony.data.ApnSetting +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settings.network.apn.ApnTypes.toApnType +import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox + +@Composable +fun ApnTypeCheckBox( + apnData: ApnData, + onTypeChanged: (String) -> Unit, + onMmsSelectedChanged: (Boolean) -> Unit, +) { + val context = LocalContext.current + val apnTypeOptions = remember { + ApnTypes.getOptions(context, apnData.apnType, apnData.customizedConfig.readOnlyApnTypes) + } + + fun updateMmsSelected() { + val apnTypeOptionMms = apnTypeOptions.single { it.text == ApnSetting.TYPE_MMS_STRING } + onMmsSelectedChanged(apnTypeOptionMms.selected.value) + } + LaunchedEffect(Unit) { updateMmsSelected() } + SettingsDropdownCheckBox( + label = stringResource(R.string.apn_type), + options = apnTypeOptions, + enabled = apnData.apnTypeEnabled, + ) { + onTypeChanged(apnTypeOptions.toApnType()) + updateMmsSelected() + } +} diff --git a/src/com/android/settings/network/apn/ApnTypes.kt b/src/com/android/settings/network/apn/ApnTypes.kt index d3dbe38b426..2c8fa2a51a0 100644 --- a/src/com/android/settings/network/apn/ApnTypes.kt +++ b/src/com/android/settings/network/apn/ApnTypes.kt @@ -16,128 +16,112 @@ package com.android.settings.network.apn -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.snapshots.SnapshotStateList +import android.content.Context +import android.telephony.data.ApnSetting +import android.util.Log +import android.widget.Toast +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toLowerCase +import com.android.settings.R +import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption object ApnTypes { - /** - * APN types for data connections. These are usage categories for an APN - * entry. One APN entry may support multiple APN types, eg, a single APN - * may service regular internet traffic ("default") as well as MMS-specific - * connections.

- * APN_TYPE_ALL is a special type to indicate that this APN entry can - * service all data connections. - */ - const val APN_TYPE_ALL = "*" + private const val TAG = "ApnTypes" - /** APN type for default data traffic */ - const val APN_TYPE_DEFAULT = "default" - - /** APN type for MMS traffic */ - const val APN_TYPE_MMS = "mms" - - /** APN type for SUPL assisted GPS */ - const val APN_TYPE_SUPL = "supl" - - /** APN type for DUN traffic */ - const val APN_TYPE_DUN = "dun" - - /** APN type for HiPri traffic */ - const val APN_TYPE_HIPRI = "hipri" - - /** APN type for FOTA */ - const val APN_TYPE_FOTA = "fota" - - /** APN type for IMS */ - const val APN_TYPE_IMS = "ims" - - /** APN type for CBS */ - const val APN_TYPE_CBS = "cbs" - - /** APN type for IA Initial Attach APN */ - const val APN_TYPE_IA = "ia" - - /** APN type for Emergency PDN. This is not an IA apn, but is used - * for access to carrier services in an emergency call situation. */ - const val APN_TYPE_EMERGENCY = "emergency" - - /** APN type for Mission Critical Services */ - const val APN_TYPE_MCX = "mcx" - - /** APN type for XCAP */ - const val APN_TYPE_XCAP = "xcap" - - /** APN type for VSIM */ - const val APN_TYPE_VSIM = "vsim" - - /** APN type for BIP */ - const val APN_TYPE_BIP = "bip" - - /** APN type for ENTERPRISE */ - const val APN_TYPE_ENTERPRISE = "enterprise" - - val APN_TYPES = arrayOf( - APN_TYPE_DEFAULT, - APN_TYPE_MMS, - APN_TYPE_SUPL, - APN_TYPE_DUN, - APN_TYPE_HIPRI, - APN_TYPE_FOTA, - APN_TYPE_IMS, - APN_TYPE_CBS, - APN_TYPE_IA, - APN_TYPE_EMERGENCY, - APN_TYPE_MCX, - APN_TYPE_XCAP, - APN_TYPE_VSIM, - APN_TYPE_BIP, - APN_TYPE_ENTERPRISE + private val APN_TYPES = arrayOf( + ApnSetting.TYPE_DEFAULT_STRING, + ApnSetting.TYPE_MMS_STRING, + ApnSetting.TYPE_SUPL_STRING, + ApnSetting.TYPE_DUN_STRING, + ApnSetting.TYPE_HIPRI_STRING, + ApnSetting.TYPE_FOTA_STRING, + ApnSetting.TYPE_IMS_STRING, + ApnSetting.TYPE_CBS_STRING, + ApnSetting.TYPE_IA_STRING, + ApnSetting.TYPE_EMERGENCY_STRING, + ApnSetting.TYPE_MCX_STRING, + ApnSetting.TYPE_XCAP_STRING, + ApnSetting.TYPE_VSIM_STRING, + ApnSetting.TYPE_BIP_STRING, + ApnSetting.TYPE_ENTERPRISE_STRING, ) - val APN_TYPES_OPTIONS = listOf(APN_TYPE_ALL) + APN_TYPES - - fun getApnTypeSelectedOptionsState(apnType: String): SnapshotStateList { - val apnTypeSelectedOptionsState = mutableStateListOf() - if (apnType.contains(APN_TYPE_ALL)) - APN_TYPES_OPTIONS.forEachIndexed { index, _ -> - apnTypeSelectedOptionsState.add(index) - } - else { - APN_TYPES_OPTIONS.forEachIndexed { index, type -> - if (apnType.contains(type)) { - apnTypeSelectedOptionsState.add(index) - } - } - if (apnTypeSelectedOptionsState.size == APN_TYPES.size) - apnTypeSelectedOptionsState.add(APN_TYPES_OPTIONS.indexOf(APN_TYPE_ALL)) + private fun splitToList(apnType: String): List { + val types = apnType.split(',').map { it.trim().toLowerCase(Locale.current) } + if (ApnSetting.TYPE_ALL_STRING in types || APN_TYPES.all { it in types }) { + return listOf(ApnSetting.TYPE_ALL_STRING) } - return apnTypeSelectedOptionsState + return APN_TYPES.filter { it in types } } - fun updateApnType( - apnTypeSelectedOptionsState: SnapshotStateList, - defaultApnTypes: List, - readOnlyApnTypes: List - ): String { - val apnType = apnTypeSelectedOptionsState.joinToString { APN_TYPES_OPTIONS[it] } - if (apnType.contains(APN_TYPE_ALL)) return APN_TYPE_ALL - return if (apnType == "" && defaultApnTypes.isNotEmpty()) - getEditableApnType(defaultApnTypes, readOnlyApnTypes) - else - apnType + fun isApnTypeReadOnly(apnType: String, readOnlyTypes: List): Boolean { + val apnTypes = splitToList(apnType) + return ApnSetting.TYPE_ALL_STRING in readOnlyTypes || + ApnSetting.TYPE_ALL_STRING in apnTypes && readOnlyTypes.isNotEmpty() || + apnTypes.any { it in readOnlyTypes } } - private fun getEditableApnType( - defaultApnTypes: List, - readOnlyApnTypes: List - ): String { - return defaultApnTypes.filterNot { apnType -> - readOnlyApnTypes.contains(apnType) || apnType in listOf( - APN_TYPE_IA, - APN_TYPE_EMERGENCY, - APN_TYPE_MCX, - APN_TYPE_IMS, + fun getOptions(context: Context, apnType: String, readOnlyTypes: List) = buildList { + val apnTypes = splitToList(apnType) + add( + context.createSettingsDropdownCheckOption( + text = ApnSetting.TYPE_ALL_STRING, + isSelectAll = true, + changeable = readOnlyTypes.isEmpty(), + selected = ApnSetting.TYPE_ALL_STRING in apnTypes, ) - }.joinToString() + ) + for (type in APN_TYPES) { + add( + context.createSettingsDropdownCheckOption( + text = type, + changeable = ApnSetting.TYPE_ALL_STRING !in readOnlyTypes && + type !in readOnlyTypes, + selected = ApnSetting.TYPE_ALL_STRING in apnTypes || type in apnTypes, + ) + ) + } + }.also { Log.d(TAG, "APN Type options: $it") } + + private fun Context.createSettingsDropdownCheckOption( + text: String, + isSelectAll: Boolean = false, + changeable: Boolean, + selected: Boolean, + ) = SettingsDropdownCheckOption( + text = text, + isSelectAll = isSelectAll, + changeable = changeable, + selected = mutableStateOf(selected), + ) { + if (!changeable) { + val message = resources.getString(R.string.error_adding_apn_type, text) + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } } -} \ No newline at end of file + + fun List.toApnType(): String { + val (selectAllOptions, regularOptions) = partition { it.isSelectAll } + for (selectAllOption in selectAllOptions) { + if (selectAllOption.selected.value) return ApnSetting.TYPE_ALL_STRING + } + return regularOptions.filter { it.selected.value }.joinToString(",") { it.text } + } + + private val NotPreSelectedTypes = setOf( + ApnSetting.TYPE_IMS_STRING, + ApnSetting.TYPE_IA_STRING, + ApnSetting.TYPE_EMERGENCY_STRING, + ApnSetting.TYPE_MCX_STRING, + ) + + fun getPreSelectedApnType(customizedConfig: CustomizedConfig): String = + (customizedConfig.defaultApnTypes + ?: defaultPreSelectedApnTypes(customizedConfig.readOnlyApnTypes)) + .joinToString(",") + + private fun defaultPreSelectedApnTypes(readOnlyApnTypes: List) = + if (ApnSetting.TYPE_ALL_STRING in readOnlyApnTypes) emptyList() + else APN_TYPES.filter { it !in readOnlyApnTypes + NotPreSelectedTypes } +} diff --git a/src/com/android/settings/notification/zen/ZenAccessSettings.java b/src/com/android/settings/notification/zen/ZenAccessSettings.java index 418a5719b8a..f765d6d2e4d 100644 --- a/src/com/android/settings/notification/zen/ZenAccessSettings.java +++ b/src/com/android/settings/notification/zen/ZenAccessSettings.java @@ -39,8 +39,8 @@ import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetail import com.android.settings.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.EmptyTextSettings; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.search.SearchIndexable; -import com.android.settingslib.widget.AppPreference; import java.util.ArrayList; import java.util.Collections; @@ -122,7 +122,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements for (ApplicationInfo app : apps) { final String pkg = app.packageName; final CharSequence label = app.loadLabel(mPkgMan); - final AppPreference pref = new AppPreference(getPrefContext()); + final RestrictedAppPreference pref = new RestrictedAppPreference(getPrefContext()); pref.setKey(pkg); pref.setIcon(app.loadIcon(mPkgMan)); pref.setTitle(label); @@ -133,6 +133,8 @@ public class ZenAccessSettings extends EmptyTextSettings implements } else { // Not auto approved, update summary according to notification backend. pref.setSummary(getPreferenceSummary(pkg)); + pref.checkEcmRestrictionAndSetDisabled( + android.Manifest.permission.MANAGE_NOTIFICATIONS, app.packageName); } pref.setOnPreferenceClickListener(preference -> { AppInfoBase.startAppInfoFragment( diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt index 37507a82274..2c4a2957422 100644 --- a/src/com/android/settings/overlay/FeatureFactory.kt +++ b/src/com/android/settings/overlay/FeatureFactory.kt @@ -24,6 +24,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider import com.android.settings.bluetooth.BluetoothFeatureProvider +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider import com.android.settings.connecteddevice.stylus.StylusFeatureProvider import com.android.settings.dashboard.DashboardFeatureProvider @@ -182,6 +183,11 @@ abstract class FeatureFactory { */ abstract val displayFeatureProvider: DisplayFeatureProvider + /** + * Gets implementation for audio sharing related feature. + */ + abstract val audioSharingFeatureProvider: AudioSharingFeatureProvider + companion object { private var _factory: FeatureFactory? = null diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt index e0313b7b7f1..e1519b3af1f 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt @@ -34,6 +34,8 @@ import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImp import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl import com.android.settings.bluetooth.BluetoothFeatureProvider import com.android.settings.bluetooth.BluetoothFeatureProviderImpl +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProviderImpl import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider import com.android.settings.connecteddevice.fastpair.FastPairFeatureProviderImpl @@ -192,7 +194,12 @@ open class FeatureFactoryImpl : FeatureFactory() { override val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider by lazy { PrivateSpaceLoginFeatureProviderImpl() } + override val displayFeatureProvider: DisplayFeatureProvider by lazy { DisplayFeatureProviderImpl() } + + override val audioSharingFeatureProvider: AudioSharingFeatureProvider by lazy { + AudioSharingFeatureProviderImpl() + } } diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java index 0741ce4b02c..b1d04d4398c 100644 --- a/src/com/android/settings/search/SearchFeatureProvider.java +++ b/src/com/android/settings/search/SearchFeatureProvider.java @@ -56,7 +56,7 @@ public interface SearchFeatureProvider { * @throws IllegalArgumentException when caller is null * @throws SecurityException when caller is not allowed to launch search result page */ - void verifyLaunchSearchResultPageCaller(Context context, @NonNull ComponentName caller) + void verifyLaunchSearchResultPageCaller(@NonNull Context context, @NonNull String callerPackage) throws SecurityException, IllegalArgumentException; /** diff --git a/src/com/android/settings/search/SearchFeatureProviderImpl.java b/src/com/android/settings/search/SearchFeatureProviderImpl.java index 6f909709058..3a62ddfb67e 100644 --- a/src/com/android/settings/search/SearchFeatureProviderImpl.java +++ b/src/com/android/settings/search/SearchFeatureProviderImpl.java @@ -17,13 +17,14 @@ package com.android.settings.search; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.Settings; import android.text.TextUtils; +import androidx.annotation.NonNull; + import com.android.settingslib.search.SearchIndexableResources; import com.android.settingslib.search.SearchIndexableResourcesMobile; @@ -32,21 +33,18 @@ import com.android.settingslib.search.SearchIndexableResourcesMobile; */ public class SearchFeatureProviderImpl implements SearchFeatureProvider { - private static final String TAG = "SearchFeatureProvider"; - private SearchIndexableResources mSearchIndexableResources; @Override - public void verifyLaunchSearchResultPageCaller(Context context, ComponentName caller) { - if (caller == null) { + public void verifyLaunchSearchResultPageCaller(@NonNull Context context, + @NonNull String callerPackage) { + if (TextUtils.isEmpty(callerPackage)) { throw new IllegalArgumentException("ExternalSettingsTrampoline intents " + "must be called with startActivityForResult"); } - final String packageName = caller.getPackageName(); - final boolean isSettingsPackage = TextUtils.equals(packageName, context.getPackageName()) - || TextUtils.equals(getSettingsIntelligencePkgName(context), packageName); - final boolean isAllowlistedPackage = - isSignatureAllowlisted(context, caller.getPackageName()); + final boolean isSettingsPackage = TextUtils.equals(callerPackage, context.getPackageName()) + || TextUtils.equals(getSettingsIntelligencePkgName(context), callerPackage); + final boolean isAllowlistedPackage = isSignatureAllowlisted(context, callerPackage); if (isSettingsPackage || isAllowlistedPackage) { return; } diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java index 5d897af3b35..04d9db56eb7 100644 --- a/src/com/android/settings/search/SearchResultTrampoline.java +++ b/src/com/android/settings/search/SearchResultTrampoline.java @@ -21,7 +21,6 @@ import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB; import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent; import android.app.Activity; -import android.content.ComponentName; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -53,11 +52,11 @@ public class SearchResultTrampoline extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final ComponentName callingActivity = getCallingActivity(); + final String callerPackage = getLaunchedFromPackage(); // First make sure caller has privilege to launch a search result page. FeatureFactory.getFeatureFactory() .getSearchFeatureProvider() - .verifyLaunchSearchResultPageCaller(this, callingActivity); + .verifyLaunchSearchResultPageCaller(this, callerPackage); // Didn't crash, proceed and launch the result as a subsetting. Intent intent = getIntent(); final String highlightMenuKey = intent.getStringExtra( @@ -106,7 +105,7 @@ public class SearchResultTrampoline extends Activity { if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) || ActivityEmbeddingUtils.isAlreadyEmbedded(this)) { startActivity(intent); - } else if (isSettingsIntelligence(callingActivity)) { + } else if (isSettingsIntelligence(callerPackage)) { if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) { startActivity(getTrampolineIntent(intent, highlightMenuKey) .setClass(this, DeepLinkHomepageActivityInternal.class) @@ -139,9 +138,9 @@ public class SearchResultTrampoline extends Activity { finish(); } - private boolean isSettingsIntelligence(ComponentName callingActivity) { - return callingActivity != null && TextUtils.equals( - callingActivity.getPackageName(), + private boolean isSettingsIntelligence(String callerPackage) { + return TextUtils.equals( + callerPackage, FeatureFactory.getFeatureFactory().getSearchFeatureProvider() .getSettingsIntelligencePkgName(this)); } diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptions.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptions.kt index 7f7d8c544b4..1ed595909e0 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptions.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptions.kt @@ -17,6 +17,7 @@ package com.android.settings.spa.app.appinfo import android.app.AppOpsManager +import android.app.ecm.EnhancedConfirmationManager import android.content.Context import android.content.pm.ApplicationInfo import android.os.UserManager @@ -90,12 +91,18 @@ fun AppInfoSettingsMoreOptions( private fun ApplicationInfo.allowRestrictedSettings(context: Context, onSuccess: () -> Unit) { AppInfoDashboardFragment.showLockScreen(context) { - context.appOpsManager.setMode( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, - packageName, - AppOpsManager.MODE_ALLOWED, - ) + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + val manager = context.getSystemService(EnhancedConfirmationManager::class.java)!! + manager.clearRestriction(packageName) + } else { + context.appOpsManager.setMode( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, + packageName, + AppOpsManager.MODE_ALLOWED, + ) + } onSuccess() val toastString = context.getString( R.string.toast_allows_restricted_settings_successfully, @@ -137,7 +144,7 @@ private suspend fun ApplicationInfo.getMoreOptionsState( ) } val shouldShowAccessRestrictedSettingsDeferred = async { - shouldShowAccessRestrictedSettings(context.appOpsManager) + shouldShowAccessRestrictedSettings(context) } val isProfileOrDeviceOwner = Utils.isProfileOrDeviceOwner(context.userManager, context.devicePolicyManager, packageName) @@ -169,7 +176,14 @@ private fun ApplicationInfo.isOtherUserHasInstallPackage( .filter { it.id != userId } .any { packageManagers.isPackageInstalledAsUser(packageName, it.id) } -private fun ApplicationInfo.shouldShowAccessRestrictedSettings(appOpsManager: AppOpsManager) = - appOpsManager.noteOpNoThrow( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, packageName, null, null - ) == AppOpsManager.MODE_IGNORED +private fun ApplicationInfo.shouldShowAccessRestrictedSettings(context: Context): Boolean { + return if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + val manager = context.getSystemService(EnhancedConfirmationManager::class.java)!! + manager.isClearRestrictionAllowed(packageName) + } else { + context.appOpsManager.noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, packageName, null, null + ) == AppOpsManager.MODE_IGNORED + } +} diff --git a/src/com/android/settings/utils/ManagedServiceSettings.java b/src/com/android/settings/utils/ManagedServiceSettings.java index d5f00408fd5..9f5fbc51f0d 100644 --- a/src/com/android/settings/utils/ManagedServiceSettings.java +++ b/src/com/android/settings/utils/ManagedServiceSettings.java @@ -37,14 +37,14 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceScreen; -import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.widget.EmptyTextSettings; +import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.applications.ServiceListing; -import com.android.settingslib.widget.AppSwitchPreference; +import com.android.settingslib.widget.TwoTargetPreference; import java.util.List; @@ -121,10 +121,12 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings { } final CharSequence finalTitle = title; final String summary = service.loadLabel(mPm).toString(); - final TwoStatePreference pref = new AppSwitchPreference(getPrefContext()); + final RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(getPrefContext()); pref.setPersistent(false); pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo, UserHandle.getUserId(service.applicationInfo.uid))); + pref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM); if (title != null && !title.equals(summary)) { pref.setTitle(title); pref.setSummary(summary); @@ -150,6 +152,9 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings { } }); pref.setKey(cn.flattenToString()); + if (!pref.isChecked()) { + pref.checkEcmRestrictionAndSetDisabled(mConfig.permission, service.packageName); + } screen.addPreference(pref); } highlightPreferenceIfNeeded(); diff --git a/src/com/android/settings/widget/RestrictedAppPreference.java b/src/com/android/settings/widget/RestrictedAppPreference.java index f93b935d5b0..c76a5de4535 100644 --- a/src/com/android/settings/widget/RestrictedAppPreference.java +++ b/src/com/android/settings/widget/RestrictedAppPreference.java @@ -21,6 +21,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; +import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; @@ -72,10 +73,18 @@ public class RestrictedAppPreference extends AppPreference { @Override public void setEnabled(boolean enabled) { - if (isDisabledByAdmin() && enabled) { - return; + boolean changed = false; + if (enabled && isDisabledByAdmin()) { + mHelper.setDisabledByAdmin(null); + changed = true; + } + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); + changed = true; + } + if (!changed) { + super.setEnabled(enabled); } - super.setEnabled(enabled); } public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) { @@ -88,6 +97,10 @@ public class RestrictedAppPreference extends AppPreference { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public void useAdminDisabledSummary(boolean useSummary) { mHelper.useAdminDisabledSummary(useSummary); } @@ -112,4 +125,15 @@ public class RestrictedAppPreference extends AppPreference { public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java index c105d088bb4..d88a83eba51 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java @@ -38,6 +38,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.SettingsActivity; import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; import com.google.common.collect.ImmutableList; @@ -57,7 +58,10 @@ import java.util.ArrayList; import java.util.List; /** Tests for {@link AccessibilityDetailsSettingsFragment}. */ -@Config(shadows = ShadowDevicePolicyManager.class) +@Config(shadows = { + ShadowDevicePolicyManager.class, + ShadowRestrictedLockUtilsInternal.class +}) @RunWith(RobolectricTestRunner.class) public class AccessibilityDetailsSettingsFragmentTest { private static final String PACKAGE_NAME = "com.foo.bar"; diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index db6f43bf2f9..05e56ca74a4 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -52,6 +52,7 @@ import com.android.settings.testutils.XmlTestUtils; import com.android.settings.testutils.shadow.ShadowApplicationPackageManager; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -87,6 +88,7 @@ import java.util.List; ShadowUserManager.class, ShadowColorDisplayManager.class, ShadowApplicationPackageManager.class, + ShadowRestrictedLockUtilsInternal.class, }) public class AccessibilitySettingsTest { private static final String PACKAGE_NAME = "com.android.test"; diff --git a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java index 99a78cfc408..bc9c1d83fb1 100644 --- a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java @@ -31,9 +31,14 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import org.junit.Rule; @@ -45,6 +50,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; @@ -53,6 +59,9 @@ import java.util.List; /** Test for {@link RestrictedPreferenceHelper}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowRestrictedLockUtilsInternal.class +}) public class RestrictedPreferenceHelperTest { private static final String PACKAGE_NAME = "com.android.test"; @@ -72,6 +81,11 @@ public class RestrictedPreferenceHelperTest { private AccessibilityShortcutInfo mShortcutInfo; private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Test public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { final String key = COMPONENT_NAME.flattenToString(); @@ -85,6 +99,37 @@ public class RestrictedPreferenceHelperTest { assertThat(preference.getKey()).isEqualTo(key); } + @Test + @RequiresFlagsEnabled(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED}) + public void createAccessibilityServicePreferenceList_ecmRestricted_prefIsEcmRestricted() { + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs( + mServiceInfo.getResolveInfo().serviceInfo.packageName); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isTrue(); + } + + @Test + @RequiresFlagsEnabled(value = {android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED}) + public void createAccessibilityServicePreferenceList_ecmNotRestricted_prefIsNotEcmRestricted() { + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isFalse(); + } + @Test public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { final String key = COMPONENT_NAME.flattenToString(); diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java index e91c0fa571f..46b19102e2b 100644 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java @@ -16,13 +16,35 @@ package com.android.settings.applications.specialaccess.premiumsms; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Process; import android.telephony.SmsManager; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; +import androidx.preference.R; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.datausage.AppStateDataUsageBridge; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; +import com.android.settingslib.RestrictedDropDownPreference; +import com.android.settingslib.applications.ApplicationsState; import org.junit.Before; import org.junit.Test; @@ -30,19 +52,28 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; @RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowRestrictedLockUtilsInternal.class +}) public class PremiumSmsAccessTest { private FakeFeatureFactory mFeatureFactory; private PremiumSmsAccess mFragment; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); mFeatureFactory = FakeFeatureFactory.setupForTest(); mFragment = new PremiumSmsAccess(); - mFragment.onAttach(RuntimeEnvironment.application); + mContext = RuntimeEnvironment.application; + mFragment.onAttach(mContext); } @Test @@ -74,4 +105,89 @@ public class PremiumSmsAccessTest { "app", SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW); } + + @Test + public void onRebuildComplete_ecmRestricted_shouldBeDisabled() { + mFragment = spy(mFragment); + mContext = spy(mContext); + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.preference_dropdown, null); + PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = spy(preferenceManager.createPreferenceScreen(mContext)); + doReturn(preferenceManager).when(mFragment).getPreferenceManager(); + doReturn(preferenceScreen).when(mFragment).getPreferenceScreen(); + final String testPkg = "com.example.disabled"; + doNothing().when(mContext).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(testPkg); + + doAnswer((invocation) -> { + final RestrictedDropDownPreference preference = invocation.getArgument(0); + // Verify preference is disabled by ecm and the summary is changed accordingly. + assertThat(preference.isDisabledByEcm()).isTrue(); + assertThat(preference.getSummary().toString()).isEqualTo( + mContext.getString( + com.android.settingslib.R.string.disabled_by_app_ops_text)); + preference.onBindViewHolder(holder); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is launched + verify(mContext).startActivity(any()); + + return null; + }).when(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + + mFragment.onRebuildComplete(createAppEntries(testPkg)); + verify(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + } + + @Test + public void onRebuildComplete_ecmNotRestricted_notDisabled() { + mFragment = spy(mFragment); + mContext = spy(mContext); + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.preference_dropdown, null); + PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = spy(preferenceManager.createPreferenceScreen(mContext)); + doReturn(preferenceManager).when(mFragment).getPreferenceManager(); + doReturn(preferenceScreen).when(mFragment).getPreferenceScreen(); + final String testPkg = "com.example.enabled"; + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + + + doAnswer((invocation) -> { + final RestrictedDropDownPreference preference = invocation.getArgument(0); + assertThat(preference.isDisabledByEcm()).isFalse(); + assertThat(preference.getSummary().toString()).isEqualTo(""); + preference.onBindViewHolder(holder); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is not launched + verify(mContext, never()).startActivity(any()); + + return null; + }).when(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + + mFragment.onRebuildComplete(createAppEntries(testPkg)); + verify(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + } + + private ArrayList createAppEntries(String... packageNames) { + final ArrayList appEntries = new ArrayList<>(); + for (int i = 0; i < packageNames.length; ++i) { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = packageNames[i]; + info.uid = Process.FIRST_APPLICATION_UID + i; + info.sourceDir = info.packageName; + final ApplicationsState.AppEntry appEntry = + spy(new ApplicationsState.AppEntry(mContext, info, i)); + appEntry.extraInfo = new AppStateDataUsageBridge + .DataUsageState(false, false); + doNothing().when(appEntry).ensureLabel(any(Context.class)); + ReflectionHelpers.setField(appEntry, "info", info); + appEntries.add(appEntry); + } + return appEntries; + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index dceadeb97aa..5a7e247c5b2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -16,7 +16,6 @@ package com.android.settings.bluetooth; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -25,36 +24,26 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Pair; import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.flags.Flags; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowAudioManager; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; -import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; -import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -75,9 +64,6 @@ import java.util.Collection; ShadowBluetoothUtils.class }) public class AvailableMediaBluetoothDeviceUpdaterTest { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; @Mock private DashboardFragment mDashboardFragment; @@ -86,11 +72,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Mock private BluetoothDevice mBluetoothDevice; @Mock private Drawable mDrawable; @Mock private LocalBluetoothManager mLocalBtManager; - @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; - @Mock private LocalBluetoothLeBroadcast mBroadcast; - @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; - @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState; private Context mContext; private AvailableMediaBluetoothDeviceUpdater mBluetoothDeviceUpdater; @@ -98,12 +80,14 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { private AudioManager mAudioManager; private BluetoothDevicePreference mPreference; private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private AudioSharingFeatureProvider mFeatureProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mFeatureProvider = FakeFeatureFactory.setupForTest().getAudioSharingFeatureProvider(); mAudioManager = mContext.getSystemService(AudioManager.class); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; mLocalBtManager = Utils.getLocalBtManager(mContext); @@ -267,13 +251,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_addsPreference() { - setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false); + public void + onProfileConnectionStateChanged_leaDeviceConnected_notInCallNoSharing_addsPreference() { mAudioManager.setMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); + when(mFeatureProvider.isAudioSharingFilterMatched( + any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class))) + .thenReturn(false); mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mCachedBluetoothDevice, @@ -284,13 +270,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_addsPreference() { - setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false); + public void + onProfileConnectionStateChanged_leaDeviceConnected_inCallNoSharing_addsPreference() { mAudioManager.setMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); + when(mFeatureProvider.isAudioSharingFilterMatched( + any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class))) + .thenReturn(false); mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mCachedBluetoothDevice, @@ -301,50 +289,16 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void - onProfileConnectionStateChanged_leaDeviceConnected_notInCall_notInBroadcast_addsPref() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ false); + onProfileConnectionStateChanged_leaDeviceConnected_notInCallInSharing_removesPref() { mAudioManager.setMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); - - mBluetoothDeviceUpdater.onProfileConnectionStateChanged( - mCachedBluetoothDevice, - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.LE_AUDIO); - - verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void - onProfileConnectionStateChanged_leaDeviceConnected_inCall_notInBroadcast_addsPref() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ false); - mAudioManager.setMode(AudioManager.MODE_IN_CALL); - when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) + when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); + when(mFeatureProvider.isAudioSharingFilterMatched( + any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class))) .thenReturn(true); - when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); - - mBluetoothDeviceUpdater.onProfileConnectionStateChanged( - mCachedBluetoothDevice, - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.LE_AUDIO); - - verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void - onProfileConnectionStateChanged_leaDeviceConnected_notInCall_inBroadcast_removesPref() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ true); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) - .thenReturn(true); - when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mCachedBluetoothDevice, @@ -355,14 +309,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void - onProfileConnectionStateChanged_leaDeviceConnected_inCall_inBroadcast_removesPref() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ true); - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + public void onProfileConnectionStateChanged_leaDeviceConnected_inCallInSharing_removesPref() { + mAudioManager.setMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); + when(mFeatureProvider.isAudioSharingFilterMatched( + any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class))) + .thenReturn(true); mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mCachedBluetoothDevice, @@ -414,56 +369,9 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onClick_Preference_setActive() { - setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false); mBluetoothDeviceUpdater.onPreferenceClick(mPreference); verify(mCachedBluetoothDevice).setActive(); } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void onClick_Preference_isNotBroadcasting_setActive() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ false); - mBluetoothDeviceUpdater.onPreferenceClick(mPreference); - - verify(mCachedBluetoothDevice).setActive(); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void onClick_Preference_isBroadcasting_stopBroadcastingAndSetActive() { - setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ true); - doNothing().when(mBroadcast).stopBroadcast(anyInt()); - mBluetoothDeviceUpdater.onPreferenceClick(mPreference); - - verify(mBroadcast).stopBroadcast(anyInt()); - verify(mCachedBluetoothDevice).setActive(); - } - - private void setUpBroadcast(boolean isSupported, boolean isBroadcasting) { - if (isSupported) { - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager); - when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); - when(mBroadcast.isEnabled(null)).thenReturn(isBroadcasting); - when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()) - .thenReturn(mAssistant); - if (isBroadcasting) { - when(mAssistant.getAllSources(any())) - .thenReturn(ImmutableList.of(mBroadcastReceiveState)); - } else { - when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of()); - } - } else { - mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); - mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); - } - } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java new file mode 100644 index 00000000000..0edbc7709ba --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java @@ -0,0 +1,67 @@ +/* + * 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.audiosharing; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class AudioSharingFeatureProviderImplTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock private CachedBluetoothDevice mCachedDevice; + @Mock private LocalBluetoothManager mLocalBtManager; + @Mock private DashboardFragment mFragment; + private Context mContext; + private AudioSharingFeatureProviderImpl mFeatureProvider; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mFeatureProvider = new AudioSharingFeatureProviderImpl(); + } + + @Test + public void createAudioSharingDevicePreferenceController_returnsNull() { + assertThat( + mFeatureProvider.createAudioSharingDevicePreferenceController( + mContext, mFragment, /* lifecycle= */ null)) + .isNull(); + } + + @Test + public void isAudioSharingFilterMatched_returnsFalse() { + assertThat(mFeatureProvider.isAudioSharingFilterMatched(mCachedDevice, mLocalBtManager)) + .isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java index 8aa2e5aaabf..ce47052a271 100644 --- a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -163,6 +164,81 @@ public class UnrestrictedDataAccessPreferenceControllerTest { mController.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); } + @Test + public void onRebuildComplete_ecmRestricted_shouldBeDisabled() { + mFragment = spy(new UnrestrictedDataAccess()); + mContext = spy(mContext); + doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); + mController.setParentFragment(mFragment); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + doReturn(0).when(mPreferenceScreen).getPreferenceCount(); + final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); + ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); + ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); + + final String testPkg = "com.example.disabled"; + doNothing().when(mContext).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(testPkg); + + doAnswer((invocation) -> { + final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); + // Verify preference is disabled by ecm and the summary is changed accordingly. + assertThat(preference.isDisabledByEcm()).isTrue(); + assertThat(preference.getSummary().toString()).isEqualTo( + mContext.getString( + com.android.settingslib.R.string.disabled_by_app_ops_text)); + assertThat(preference.isChecked()).isFalse(); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is launched + assertThat(preference.isChecked()).isFalse(); + verify(mContext).startActivity(any()); + + return null; + }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + + mController.onRebuildComplete(createAppEntries(testPkg)); + verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + } + + @Test + public void onRebuildComplete_ecmNotRestricted_notDisabled() { + mFragment = spy(new UnrestrictedDataAccess()); + mContext = spy(mContext); + doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); + mController.setParentFragment(mFragment); + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + doReturn(0).when(mPreferenceScreen).getPreferenceCount(); + final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); + ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); + ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); + + final String testPkg = "com.example.enabled"; + doNothing().when(mContext).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + + doAnswer((invocation) -> { + final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); + assertThat(preference.isDisabledByEcm()).isFalse(); + assertThat(preference.getSummary()).isEqualTo(""); + assertThat(preference.isChecked()).isFalse(); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is not launched + assertThat(preference.isChecked()).isTrue(); + verify(mContext, never()).startActivity(any()); + + return null; + }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + + mController.onRebuildComplete(createAppEntries(testPkg)); + verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + } + private ArrayList createAppEntries(String... packageNames) { final ArrayList appEntries = new ArrayList<>(); for (int i = 0; i < packageNames.length; ++i) { diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java index f3496001d09..8a7419bb1ba 100644 --- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java @@ -20,7 +20,6 @@ package com.android.settings.search; import static com.google.common.truth.Truth.assertThat; import android.app.settings.SettingsEnums; -import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; @@ -131,20 +130,22 @@ public class SearchFeatureProviderImplTest { @Test(expected = SecurityException.class) public void verifyLaunchSearchResultPageCaller_badCaller_shouldCrash() { - final ComponentName cn = new ComponentName("pkg", "class"); - mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn); + final String packageName = "pkg"; + + mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName); } @Test public void verifyLaunchSearchResultPageCaller_settingsCaller_shouldNotCrash() { - final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class"); - mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn); + final String packageName = mActivity.getPackageName(); + + mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName); } @Test public void verifyLaunchSearchResultPageCaller_settingsIntelligenceCaller_shouldNotCrash() { final String packageName = mProvider.getSettingsIntelligencePkgName(mActivity); - final ComponentName cn = new ComponentName(packageName, "class"); - mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn); + + mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName); } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java index 5989d49c7fd..e03134b0fee 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java @@ -15,9 +15,11 @@ */ package com.android.settings.testutils.shadow; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.Intent; import com.android.internal.util.ArrayUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -38,6 +40,8 @@ public class ShadowRestrictedLockUtilsInternal { private static DevicePolicyManager sDevicePolicyManager; private static String[] sDisabledTypes; private static int sKeyguardDisabledFeatures; + private static String[] sEcmRestrictedPkgs; + private static boolean sAccessibilityServiceRestricted; @Resetter public static void reset() { @@ -47,6 +51,8 @@ public class ShadowRestrictedLockUtilsInternal { sDisabledTypes = new String[0]; sMaximumTimeToLockIsSet = false; sMteOverridden = false; + sEcmRestrictedPkgs = new String[0]; + sAccessibilityServiceRestricted = false; } @Implementation @@ -108,10 +114,35 @@ public class ShadowRestrictedLockUtilsInternal { return sMteOverridden ? new EnforcedAdmin() : null; } + public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context, + String packageName, int userId) { + return sAccessibilityServiceRestricted ? new EnforcedAdmin() : null; + } + + @Implementation + public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context, + @NonNull String settingIdentifier, @NonNull String packageName) { + if (ArrayUtils.contains(sEcmRestrictedPkgs, packageName)) { + return new Intent(); + } + + return null; + } + + @Implementation + public static boolean isEnhancedConfirmationRestricted(@NonNull Context context, + @NonNull String settingIdentifier, @NonNull String packageName) { + return false; + } + public static void setRestricted(boolean restricted) { sIsRestricted = restricted; } + public static void setEcmRestrictedPkgs(String... pkgs) { + sEcmRestrictedPkgs = pkgs; + } + public static void setRestrictedPkgs(String... pkgs) { sRestrictedPkgs = pkgs; } diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java index a11b226db28..f49cc6873eb 100644 --- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java @@ -27,6 +27,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider; import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider; import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider; import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider; import com.android.settings.connecteddevice.stylus.StylusFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; @@ -103,6 +104,7 @@ public class FakeFeatureFactory extends FeatureFactory { public FastPairFeatureProvider mFastPairFeatureProvider; public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider; + public AudioSharingFeatureProvider mAudioSharingFeatureProvider; /** * Call this in {@code @Before} method of the test class to use fake factory. @@ -152,6 +154,7 @@ public class FakeFeatureFactory extends FeatureFactory { mFastPairFeatureProvider = mock(FastPairFeatureProvider.class); mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); + mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class); } @Override @@ -339,5 +342,10 @@ public class FakeFeatureFactory extends FeatureFactory { public DisplayFeatureProvider getDisplayFeatureProvider() { return mDisplayFeatureProvider; } + + @Override + public AudioSharingFeatureProvider getAudioSharingFeatureProvider() { + return mAudioSharingFeatureProvider; + } } diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt index ec3b75470b0..415531882a2 100644 --- a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt @@ -58,7 +58,7 @@ class ApnRepositoryTest { @Test fun getApnDataFromUri() { // mock out resources and the feature provider - val cursor = MatrixCursor(sProjection) + val cursor = MatrixCursor(Projection) cursor.addRow( arrayOf( 0, @@ -82,7 +82,7 @@ class ApnRepositoryTest { 1, ) ) - whenever(contentResolver.query(uri, sProjection, null, null, null)).thenReturn(cursor) + whenever(contentResolver.query(uri, Projection, null, null, null)).thenReturn(cursor) val apnData = getApnDataFromUri(uri, context) diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptionsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptionsTest.kt index 71035163057..9e7ec9bcd42 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptionsTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInfoSettingsMoreOptionsTest.kt @@ -19,11 +19,16 @@ package com.android.settings.spa.app.appinfo import android.app.AppOpsManager import android.app.KeyguardManager import android.app.admin.DevicePolicyManager +import android.app.ecm.EnhancedConfirmationManager import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.UserInfo import android.os.UserManager +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsNotDisplayed @@ -61,6 +66,9 @@ class AppInfoSettingsMoreOptionsTest { @get:Rule val composeTestRule = createComposeRule() + @get:Rule + val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private lateinit var mockSession: MockitoSession @Spy @@ -81,6 +89,9 @@ class AppInfoSettingsMoreOptionsTest { @Mock private lateinit var appOpsManager: AppOpsManager + @Mock + private lateinit var enhancedConfirmationManager: EnhancedConfirmationManager + @Mock private lateinit var keyguardManager: KeyguardManager @@ -103,6 +114,8 @@ class AppInfoSettingsMoreOptionsTest { whenever(context.devicePolicyManager).thenReturn(devicePolicyManager) whenever(context.appOpsManager).thenReturn(appOpsManager) whenever(context.getSystemService(KeyguardManager::class.java)).thenReturn(keyguardManager) + whenever(context.getSystemService(EnhancedConfirmationManager::class.java)) + .thenReturn(enhancedConfirmationManager) whenever(keyguardManager.isKeyguardSecure).thenReturn(false) whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME)) .thenReturn(false) @@ -158,7 +171,9 @@ class AppInfoSettingsMoreOptionsTest { } @Test - fun shouldShowAccessRestrictedSettings() { + @RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) + fun shouldShowAccessRestrictedSettings_appOp() { whenever( appOpsManager.noteOpNoThrow( AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, UID, PACKAGE_NAME, null, null @@ -186,6 +201,31 @@ class AppInfoSettingsMoreOptionsTest { ) } + @Test + @RequiresFlagsEnabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS, + android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) + fun shouldShowAccessRestrictedSettings() { + whenever( + enhancedConfirmationManager.isClearRestrictionAllowed(PACKAGE_NAME) + ).thenReturn(true) + val app = ApplicationInfo().apply { + packageName = PACKAGE_NAME + uid = UID + } + + setContent(app) + composeTestRule.onRoot().performClick() + + composeTestRule.waitUntilExists( + hasText(context.getString(R.string.app_restricted_settings_lockscreen_title)) + ) + composeTestRule + .onNodeWithText(context + .getString(R.string.app_restricted_settings_lockscreen_title)) + .performClick() + verify(enhancedConfirmationManager).clearRestriction(PACKAGE_NAME) + } + private fun setContent(app: ApplicationInfo) { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt index 4048c24754d..606db8e1c87 100644 --- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt +++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt @@ -25,6 +25,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider import com.android.settings.bluetooth.BluetoothFeatureProvider +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider import com.android.settings.connecteddevice.stylus.StylusFeatureProvider import com.android.settings.dashboard.DashboardFeatureProvider @@ -149,4 +150,6 @@ class FakeFeatureFactory : FeatureFactory() { get() = TODO("Not yet implemented") override val displayFeatureProvider: DisplayFeatureProvider get() = TODO("Not yet implemented") + override val audioSharingFeatureProvider: AudioSharingFeatureProvider + get() = TODO("Not yet implemented") } diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index fa5af6d4f9e..eb236854450 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -36,6 +36,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.core.app.ApplicationProvider; @@ -45,6 +48,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.RestrictedSwitchPreference; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -58,6 +62,8 @@ public class ApprovalPreferenceControllerTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private Context mContext; private FakeFeatureFactory mFeatureFactory; @@ -122,6 +128,7 @@ public class ApprovalPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS) public void updateState_checked() { when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( AppOpsManager.MODE_ALLOWED); @@ -136,19 +143,26 @@ public class ApprovalPreferenceControllerTest { } @Test + @RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS) public void restrictedSettings_appOpsDisabled() { - when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( - AppOpsManager.MODE_ERRORED); + Assert.assertFalse(android.security.Flags.extendEcmToAllSettings()); + when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_ERRORED); + doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE); when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(false); RestrictedSwitchPreference pref = new RestrictedSwitchPreference( mContext); pref.setAppOps(mAppOpsManager); + mController.setSettingIdentifier(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); mController.updateState(pref); + + verify(mAppOpsManager).noteOpNoThrow(anyInt(), anyInt(), anyString()); assertThat(pref.isEnabled()).isFalse(); } @Test + @RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS) public void restrictedSettings_serviceAlreadyEnabled() { when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( AppOpsManager.MODE_ERRORED); diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java index 9e7948c7da8..4f17a3acfff 100644 --- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -27,6 +27,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider; import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider; import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; +import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider; import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider; import com.android.settings.connecteddevice.stylus.StylusFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; @@ -102,6 +103,7 @@ public class FakeFeatureFactory extends FeatureFactory { public FastPairFeatureProvider mFastPairFeatureProvider; public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider; + public AudioSharingFeatureProvider mAudioSharingFeatureProvider; /** Call this in {@code @Before} method of the test class to use fake factory. */ public static FakeFeatureFactory setupForTest() { @@ -153,6 +155,7 @@ public class FakeFeatureFactory extends FeatureFactory { mFastPairFeatureProvider = mock(FastPairFeatureProvider.class); mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); + mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class); } @Override @@ -340,4 +343,9 @@ public class FakeFeatureFactory extends FeatureFactory { public DisplayFeatureProvider getDisplayFeatureProvider() { return mDisplayFeatureProvider; } + + @Override + public AudioSharingFeatureProvider getAudioSharingFeatureProvider() { + return mAudioSharingFeatureProvider; + } }