Snap for 11479750 from 12f99280a0 to 24Q3-release

Change-Id: I0a173f9a00e2ecdb60fed9a043d78bf129632a31
This commit is contained in:
Android Build Coastguard Worker
2024-02-22 00:22:05 +00:00
51 changed files with 1173 additions and 693 deletions

View File

@@ -46,7 +46,6 @@ java_library {
android_library { android_library {
name: "Settings-core", name: "Settings-core",
platform_apis: true,
defaults: [ defaults: [
"SettingsLib-search-defaults", "SettingsLib-search-defaults",
"SettingsLintDefaults", "SettingsLintDefaults",

View File

@@ -4362,6 +4362,10 @@
<string name="trackpad_pointer_speed">Pointer speed</string> <string name="trackpad_pointer_speed">Pointer speed</string>
<!-- Title for the button to trigger the 'touch gesture' education. [CHAR LIMIT=35] --> <!-- Title for the button to trigger the 'touch gesture' education. [CHAR LIMIT=35] -->
<string name="trackpad_touch_gesture">Learn touchpad gestures</string> <string name="trackpad_touch_gesture">Learn touchpad gestures</string>
<!-- Search keywords for "touchpad" -->
<string name="keywords_touchpad">trackpad, track pad, mouse, cursor, scroll, swipe, right click, click, pointer</string>
<!-- Search keywords for 'Bottom-right tap', the name of the touchpad setting that allows the user to click the bottom right corner of the touchpad for more options. -->
<string name="keywords_trackpad_bottom_right_tap">right click, tap</string>
<!-- Title text for 'Go home' gesture education [CHAR LIMIT=35] --> <!-- Title text for 'Go home' gesture education [CHAR LIMIT=35] -->
<string name="gesture_title_go_home">Go home</string> <string name="gesture_title_go_home">Go home</string>

View File

@@ -49,7 +49,8 @@
android:summary="@string/trackpad_bottom_right_tap_summary" android:summary="@string/trackpad_bottom_right_tap_summary"
android:icon="@drawable/ic_trackpad_bottom_right_click" android:icon="@drawable/ic_trackpad_bottom_right_click"
settings:controller="com.android.settings.inputmethod.TrackpadBottomPreferenceController" settings:controller="com.android.settings.inputmethod.TrackpadBottomPreferenceController"
android:order="30"/> android:order="30"
settings:keywords="@string/keywords_trackpad_bottom_right_tap"/>
<com.android.settings.widget.SeekBarPreference <com.android.settings.widget.SeekBarPreference
android:key="trackpad_pointer_speed" android:key="trackpad_pointer_speed"

View File

@@ -21,7 +21,6 @@ import android.app.AppOpsManager;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
public class ActionDisabledByAppOpsDialog extends Activity public class ActionDisabledByAppOpsDialog extends Activity
implements DialogInterface.OnDismissListener { implements DialogInterface.OnDismissListener {

View File

@@ -43,6 +43,7 @@ import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.SubSettingLauncher;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.accessibility.AccessibilityUtils; import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List; import java.util.List;
@@ -164,16 +165,9 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
if (permittedServices != null && !permittedServices.contains(packageName)) { if (permittedServices != null && !permittedServices.contains(packageName)) {
return false; return false;
} }
try {
final int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, return !RestrictedLockUtilsInternal.isEnhancedConfirmationRestricted(getContext(),
uid, packageName); packageName, AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
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;
}
} }
private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) { private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) {

View File

@@ -235,10 +235,11 @@ public class RestrictedPreferenceHelper {
boolean serviceAllowed = permittedServices == null || permittedServices.contains( boolean serviceAllowed = permittedServices == null || permittedServices.contains(
preference.getPackageName()); preference.getPackageName());
if (android.security.Flags.extendEcmToAllSettings()) { if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
&& android.security.Flags.extendEcmToAllSettings()) {
preference.checkEcmRestrictionAndSetDisabled( preference.checkEcmRestrictionAndSetDisabled(
AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE, AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
preference.getPackageName(), preference.getUid()); preference.getPackageName());
if (preference.isDisabledByEcm()) { if (preference.isDisabledByEcm()) {
serviceAllowed = false; serviceAllowed = false;
} }
@@ -257,9 +258,7 @@ public class RestrictedPreferenceHelper {
preference.setEnabled(false); preference.setEnabled(false);
} }
} }
return; } else {
}
boolean appOpsAllowed; boolean appOpsAllowed;
if (serviceAllowed) { if (serviceAllowed) {
try { try {
@@ -294,6 +293,7 @@ public class RestrictedPreferenceHelper {
} }
} }
} }
}
/** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */
private void putBasicExtras(RestrictedPreference preference, String prefKey, private void putBasicExtras(RestrictedPreference preference, String prefKey,

View File

@@ -174,7 +174,7 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
if (shouldEnable && !hasAccess) { if (shouldEnable && !hasAccess) {
mSwitchPref.checkEcmRestrictionAndSetDisabled(AppOpsManager.OPSTR_GET_USAGE_STATS, mSwitchPref.checkEcmRestrictionAndSetDisabled(AppOpsManager.OPSTR_GET_USAGE_STATS,
mPackageName, mPackageInfo.applicationInfo.uid); mPackageName);
shouldEnable = !mSwitchPref.isDisabledByEcm(); shouldEnable = !mSwitchPref.isDisabledByEcm();
} }

View File

@@ -24,6 +24,7 @@ import android.app.Activity;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@@ -490,12 +491,23 @@ public class AppInfoDashboardFragment extends DashboardFragment
return true; return true;
case ACCESS_RESTRICTED_SETTINGS: case ACCESS_RESTRICTED_SETTINGS:
showLockScreen(getContext(), () -> { showLockScreen(getContext(), () -> {
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( final AppOpsManager appOpsManager = getContext().getSystemService(
AppOpsManager.class); AppOpsManager.class);
appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
getUid(), getUid(),
getPackageName(), getPackageName(),
AppOpsManager.MODE_ALLOWED); AppOpsManager.MODE_ALLOWED);
}
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
final String toastString = getContext().getString( final String toastString = getContext().getString(
R.string.toast_allows_restricted_settings_successfully, R.string.toast_allows_restricted_settings_successfully,
@@ -527,6 +539,16 @@ public class AppInfoDashboardFragment extends DashboardFragment
} }
private boolean shouldShowAccessRestrictedSettings() { private boolean shouldShowAccessRestrictedSettings() {
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 { try {
final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow( final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(), AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(),
@@ -537,6 +559,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
return false; return false;
} }
} }
}
@VisibleForTesting @VisibleForTesting
boolean shouldShowUninstallForAll(AppEntry appEntry) { boolean shouldShowUninstallForAll(AppEntry appEntry) {

View File

@@ -222,7 +222,7 @@ public class DeviceAdminListPreferenceController extends BasePreferenceControlle
pref.setOnPreferenceChangeListener((preference, newValue) -> false); pref.setOnPreferenceChangeListener((preference, newValue) -> false);
pref.setSingleLineTitle(true); pref.setSingleLineTitle(true);
pref.checkEcmRestrictionAndSetDisabled(Manifest.permission.BIND_DEVICE_ADMIN, pref.checkEcmRestrictionAndSetDisabled(Manifest.permission.BIND_DEVICE_ADMIN,
item.getPackageName(), item.getUid()); item.getPackageName());
} }
/** /**

View File

@@ -24,6 +24,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
@@ -42,7 +43,7 @@ public class ApprovalPreferenceController extends BasePreferenceController {
private NotificationManager mNm; private NotificationManager mNm;
private PackageManager mPm; private PackageManager mPm;
// The appOp representing this preference // The appOp representing this preference
private String mAppOpStr; private String mSettingIdentifier;
public ApprovalPreferenceController(Context context, String key) { public ApprovalPreferenceController(Context context, String key) {
super(context, key); super(context, key);
@@ -76,8 +77,9 @@ public class ApprovalPreferenceController extends BasePreferenceController {
/** /**
* Set the associated appOp for the Setting * Set the associated appOp for the Setting
*/ */
public ApprovalPreferenceController setAppOpStr(String appOpStr) { @NonNull
mAppOpStr = appOpStr; public ApprovalPreferenceController setSettingIdentifier(@NonNull String settingIdentifier) {
mSettingIdentifier = settingIdentifier;
return this; 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) { if (!isAllowedCn && !isEnabled) {
preference.setEnabled(false); preference.setEnabled(false);
} else if (isEnabled) { } else if (isEnabled) {
preference.setEnabled(true); preference.setEnabled(true);
} else { } else {
preference.checkEcmRestrictionAndSetDisabled(mAppOpStr, preference.checkEcmRestrictionAndSetDisabled(mSettingIdentifier,
mCn.getPackageName(), mPkgInfo.applicationInfo.uid); mCn.getPackageName());
} }
} else { } else {
preference.updateState( preference.updateState(

View File

@@ -103,7 +103,7 @@ public class NotificationAccessDetails extends DashboardFragment {
.setCn(mComponentName) .setCn(mComponentName)
.setNm(context.getSystemService(NotificationManager.class)) .setNm(context.getSystemService(NotificationManager.class))
.setPm(mPm) .setPm(mPm)
.setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS) .setSettingIdentifier(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
.setParent(this); .setParent(this);
use(HeaderPreferenceController.class) use(HeaderPreferenceController.class)
.setFragment(this) .setFragment(this)

View File

@@ -25,7 +25,6 @@ import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.DropDownPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceScreen; 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.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EmptyTextSettings; import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.RestrictedDropDownPreference;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.ApplicationsState.Callbacks;
@@ -52,6 +52,8 @@ import java.util.ArrayList;
public class PremiumSmsAccess extends EmptyTextSettings public class PremiumSmsAccess extends EmptyTextSettings
implements Callback, Callbacks, OnPreferenceChangeListener { implements Callback, Callbacks, OnPreferenceChangeListener {
private static final String ECM_RESTRICTION_KEY = "android:premium_sms_access";
private ApplicationsState mApplicationsState; private ApplicationsState mApplicationsState;
private AppStateSmsPremBridge mSmsBackend; private AppStateSmsPremBridge mSmsBackend;
private Session mSession; 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; private final AppEntry mAppEntry;
public PremiumSmsPreference(AppEntry appEntry, Context context) { public PremiumSmsPreference(AppEntry appEntry, Context context) {
@@ -224,6 +226,7 @@ public class PremiumSmsAccess extends EmptyTextSettings
}); });
setValue(String.valueOf(getCurrentValue())); setValue(String.valueOf(getCurrentValue()));
setSummary("%s"); setSummary("%s");
this.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, appEntry.info.packageName);
} }
private int getCurrentValue() { private int getCurrentValue() {

View File

@@ -23,7 +23,7 @@ import android.util.Log;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback; 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.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -72,6 +72,32 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) { if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile); 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. // If device is Hearing Aid, it is compatible with HFP and A2DP.
// It would show in Available Devices group. // It would show in Available Devices group.
if (cachedDevice.isConnectedAshaHearingAidDevice()) { if (cachedDevice.isConnectedAshaHearingAidDevice()) {
@@ -82,20 +108,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
+ ", the Hearing Aid profile is connected."); + ", the Hearing Aid profile is connected.");
return true; 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, // According to the current audio profile type,
// this page will show the bluetooth device that have corresponding profile. // this page will show the bluetooth device that have corresponding profile.
// For example: // For example:
@@ -125,13 +138,9 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory);
final CachedBluetoothDevice device = final CachedBluetoothDevice device =
((BluetoothDevicePreference) preference).getBluetoothDevice(); ((BluetoothDevicePreference) preference).getBluetoothDevice();
if (AudioSharingUtils.isFeatureEnabled() FeatureFactory.getFeatureFactory()
&& AudioSharingUtils.isBroadcasting(mLocalBtManager)) { .getAudioSharingFeatureProvider()
if (DBG) { .handleMediaDeviceOnClick(mLocalManager);
Log.d(TAG, "onPreferenceClick stop broadcasting.");
}
AudioSharingUtils.stopBroadcasting(mLocalBtManager);
}
return device.setActive(); return device.setActive();
} }

View File

@@ -34,8 +34,10 @@ import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceGroup import androidx.preference.PreferenceGroup
import com.android.settings.R import com.android.settings.R
import com.android.settings.dashboard.RestrictedDashboardFragment import com.android.settings.dashboard.RestrictedDashboardFragment
import com.android.settings.flags.Flags
import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothDeviceFilter import com.android.settingslib.bluetooth.BluetoothDeviceFilter
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -217,6 +219,14 @@ abstract class DeviceListPreferenceFragment(restrictedKey: String?) :
) )
return 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 // Only add device preference when it's not found in the map and there's no other state
// message showing in the list // message showing in the list
val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) { val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) {

View File

@@ -22,12 +22,13 @@ import android.provider.DeviceConfig;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.dashboard.DashboardFragment; 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.search.BaseSearchIndexProvider;
import com.android.settings.slices.SlicePreferenceController; import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ConnectedDeviceDashboardFragment extends DashboardFragment { public class ConnectedDeviceDashboardFragment extends DashboardFragment {
@@ -87,9 +92,6 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
+ ", action : " + ", action : "
+ action); + action);
} }
if (AudioSharingUtils.isFeatureEnabled()) {
use(AudioSharingDevicePreferenceController.class).init(this);
}
use(AvailableMediaDeviceGroupController.class).init(this); use(AvailableMediaDeviceGroupController.class).init(this);
use(ConnectedDeviceGroupController.class).init(this); use(ConnectedDeviceGroupController.class).init(this);
use(PreviouslyConnectedDevicePreferenceController.class).init(this); use(PreviouslyConnectedDevicePreferenceController.class).init(this);
@@ -112,6 +114,29 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
} }
} }
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, /* fragment= */ this, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(
Context context,
@Nullable ConnectedDeviceDashboardFragment fragment,
@Nullable Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
if (AudioSharingUtils.isFeatureEnabled()) {
AbstractPreferenceController audioSharingController =
FeatureFactory.getFeatureFactory()
.getAudioSharingFeatureProvider()
.createAudioSharingDevicePreferenceController(
context, fragment, lifecycle);
if (audioSharingController != null) {
controllers.add(audioSharingController);
}
}
return controllers;
}
@VisibleForTesting @VisibleForTesting
boolean isAlwaysDiscoverable(String callingAppPackageName, String action) { boolean isAlwaysDiscoverable(String callingAppPackageName, String action) {
return TextUtils.equals(SLICE_ACTION, action) return TextUtils.equals(SLICE_ACTION, action)
@@ -122,5 +147,12 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
/** For Search. */ /** For Search. */
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.connected_devices); new BaseSearchIndexProvider(R.xml.connected_devices) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(
context, /* fragment= */ null, /* lifecycle= */ null);
}
};
} }

View File

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

View File

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

View File

@@ -20,7 +20,9 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle; import android.os.UserHandle;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import com.android.settings.R; import com.android.settings.R;
@@ -36,6 +38,7 @@ import com.android.settingslib.widget.AppSwitchPreference;
public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements
DataSaverBackend.Listener { DataSaverBackend.Listener {
private static final String ECM_SETTING_IDENTIFIER = "android:unrestricted_data_access";
private final ApplicationsState mApplicationsState; private final ApplicationsState mApplicationsState;
private final AppEntry mEntry; private final AppEntry mEntry;
@@ -58,6 +61,7 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem
mParentFragment = parentFragment; mParentFragment = parentFragment;
setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled( setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(
context, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); context, entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
mHelper.checkEcmRestrictionAndSetDisabled(ECM_SETTING_IDENTIFIER, entry.info.packageName);
updateState(); updateState();
setKey(generateKey(mEntry)); setKey(generateKey(mEntry));
@@ -166,10 +170,24 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem
return mHelper.isDisabledByAdmin(); return mHelper.isDisabledByAdmin();
} }
@VisibleForTesting
boolean isDisabledByEcm() {
return mHelper.isDisabledByEcm();
}
public void setDisabledByAdmin(EnforcedAdmin admin) { public void setDisabledByAdmin(EnforcedAdmin admin) {
mHelper.setDisabledByAdmin(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. // Sets UI state based on allowlist/denylist status.
public void updateState() { public void updateState() {
setTitle(mEntry.label); setTitle(mEntry.label);
@@ -179,7 +197,8 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem
setSummary(com.android.settingslib.widget.restricted.R.string.disabled_by_admin); setSummary(com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
} else if (mDataUsageState.isDataSaverDenylisted) { } else if (mDataUsageState.isDataSaverDenylisted) {
setSummary(R.string.restrict_background_blocklisted); setSummary(R.string.restrict_background_blocklisted);
} else { // If disabled by ECM, the summary is set directly by the switch.
} else if (!isDisabledByEcm()) {
setSummary(""); setSummary("");
} }
} }

View File

@@ -151,6 +151,7 @@ public class UnrestrictedDataAccessPreferenceController extends BasePreferenceCo
} else { } else {
preference.setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(mContext, preference.setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(mContext,
entry.info.packageName, UserHandle.getUserId(entry.info.uid))); entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
preference.checkEcmRestrictionAndSetDisabled(entry.info.packageName);
preference.updateState(); preference.updateState();
} }
preference.setOrder(i); preference.setOrder(i);

View File

@@ -52,7 +52,7 @@ public class TrackpadTouchGestureSettings extends DashboardFragment {
} }
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.trackpad_settings) { new BaseSearchIndexProvider(R.xml.trackpad_gesture_settings) {
@Override @Override
protected boolean isPageSearchEnabled(Context context) { protected boolean isPageSearchEnabled(Context context) {
return FeatureFlagUtils return FeatureFlagUtils

View File

@@ -38,17 +38,10 @@ import androidx.compose.ui.res.stringResource
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.android.settings.R 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.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox 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.SettingsOutlinedTextField
import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword
import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreference
@@ -79,7 +72,7 @@ object ApnEditPageProvider : SettingsPageProvider {
val uriString = arguments!!.getString(URI) val uriString = arguments!!.getString(URI)
val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString))) val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString)))
val subId = arguments.getInt(SUB_ID) 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 { val apnDataCur = remember {
mutableStateOf(apnDataInit) mutableStateOf(apnDataInit)
} }
@@ -101,12 +94,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
val context = LocalContext.current val context = LocalContext.current
val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList() val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList()
val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList() val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList()
val networkTypeSelectedOptionsState = remember { var apnTypeMmsSelected by remember { mutableStateOf(false) }
getNetworkTypeSelectedOptionsState(apnData.networkType)
}
var apnTypeSelectedOptionsState = remember {
getApnTypeSelectedOptionsState(apnData.apnType)
}
val navController = LocalNavController.current val navController = LocalNavController.current
var valid: String? var valid: String?
RegularScaffold( RegularScaffold(
@@ -114,11 +102,6 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
actions = { actions = {
if (!apnData.customizedConfig.readOnlyApn) { if (!apnData.customizedConfig.readOnlyApn) {
Button(onClick = { Button(onClick = {
apnData = apnData.copy(
networkType = ApnNetworkTypes.getNetworkType(
networkTypeSelectedOptionsState
)
)
valid = validateAndSaveApnData( valid = validateAndSaveApnData(
apnDataInit, apnDataInit,
apnData, apnData,
@@ -193,27 +176,12 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
label = stringResource(R.string.apn_server), label = stringResource(R.string.apn_server),
enabled = apnData.serverEnabled enabled = apnData.serverEnabled
) { apnData = apnData.copy(server = it) } ) { apnData = apnData.copy(server = it) }
SettingsExposedDropdownMenuCheckBox( ApnTypeCheckBox(
label = stringResource(R.string.apn_type), apnData = apnData,
options = APN_TYPES_OPTIONS, onTypeChanged = { apnData = apnData.copy(apnType = it) },
selectedOptionsState = apnTypeSelectedOptionsState, onMmsSelectedChanged = { apnTypeMmsSelected = it },
enabled = apnData.apnTypeEnabled,
errorMessage = validateAPNType(
apnData.validEnabled, apnData.apnType,
apnData.customizedConfig.readOnlyApnTypes, context
) )
) { if (apnTypeMmsSelected) {
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))) {
SettingsOutlinedTextField( SettingsOutlinedTextField(
value = apnData.mmsc, value = apnData.mmsc,
label = stringResource(R.string.apn_mmsc), label = stringResource(R.string.apn_mmsc),
@@ -249,13 +217,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState<ApnData>, uriInit: Ur
selectedOptionIndex = apnData.apnRoaming, selectedOptionIndex = apnData.apnRoaming,
enabled = apnData.apnRoamingEnabled enabled = apnData.apnRoamingEnabled
) { apnData = apnData.copy(apnRoaming = it) } ) { apnData = apnData.copy(apnRoaming = it) }
SettingsExposedDropdownMenuCheckBox( ApnNetworkTypeCheckBox(apnData) { apnData = apnData.copy(networkType = it) }
label = stringResource(R.string.network_type),
options = getNetworkTypeDisplayNames(),
selectedOptionsState = networkTypeSelectedOptionsState,
emptyVal = stringResource(R.string.network_type_unspecified),
enabled = apnData.networkTypeEnabled
) {}
SwitchPreference( SwitchPreference(
object : SwitchPreferenceModel { object : SwitchPreferenceModel {
override val title = context.resources.getString(R.string.carrier_enabled) override val title = context.resources.getString(R.string.carrier_enabled)

View File

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

View File

@@ -17,8 +17,9 @@
package com.android.settings.network.apn package com.android.settings.network.apn
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateMap
import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption
object ApnNetworkTypes { object ApnNetworkTypes {
private val Types = listOf( private val Types = listOf(
@@ -39,32 +40,40 @@ object ApnNetworkTypes {
TelephonyManager.NETWORK_TYPE_NR, TelephonyManager.NETWORK_TYPE_NR,
) )
fun getNetworkTypeDisplayNames(): List<String> = fun getNetworkTypeOptions(): List<SettingsDropdownCheckOption> =
Types.map { TelephonyManager.getNetworkTypeName(it) } Types.map { SettingsDropdownCheckOption(TelephonyManager.getNetworkTypeName(it)) }
/** /**
* Gets the selected Network type Selected Options according to network type. * Gets the selected Network type Selected Options according to network type.
* @param networkType Initialized network type bitmask, often multiple network type options may * @param networkType Initialized network type bitmask, often multiple network type options may
* be included. * be included.
*/ */
fun getNetworkTypeSelectedOptionsState(networkType: Long): SnapshotStateList<Int> { fun networkTypeToSelectedStateMap(
val networkTypeSelectedOptionsState = mutableStateListOf<Int>() options: List<SettingsDropdownCheckOption>,
networkType: Long,
): SnapshotStateMap<SettingsDropdownCheckOption, Boolean> {
val stateMap = mutableStateMapOf<SettingsDropdownCheckOption, Boolean>()
Types.forEachIndexed { index, type -> Types.forEachIndexed { index, type ->
if (networkType and TelephonyManager.getBitMaskForNetworkType(type) != 0L) { 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. * 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<Int>): Long { fun selectedStateMapToNetworkType(
options: List<SettingsDropdownCheckOption>,
stateMap: SnapshotStateMap<SettingsDropdownCheckOption, Boolean>,
): Long {
var networkType = 0L var networkType = 0L
networkTypeSelectedOptionsState.forEach { option -> options.forEachIndexed { index, option ->
networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[option]) if (stateMap[option] == true) {
networkType = networkType or TelephonyManager.getBitMaskForNetworkType(Types[index])
}
} }
return networkType return networkType
} }

View File

@@ -18,6 +18,7 @@ package com.android.settings.network.apn
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.Telephony import android.provider.Telephony
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
@@ -27,26 +28,7 @@ import com.android.settings.R
import com.android.settingslib.utils.ThreadUtils import com.android.settingslib.utils.ThreadUtils
import java.util.Locale import java.util.Locale
const val NAME_INDEX = 1 val Projection = arrayOf(
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(
Telephony.Carriers._ID, // 0 Telephony.Carriers._ID, // 0
Telephony.Carriers.NAME, // 1 Telephony.Carriers.NAME, // 1
Telephony.Carriers.APN, // 2 Telephony.Carriers.APN, // 2
@@ -68,7 +50,7 @@ val sProjection = arrayOf(
Telephony.Carriers.USER_EDITABLE, // 18 Telephony.Carriers.USER_EDITABLE, // 18
) )
const val TAG = "ApnRepository" private const val TAG = "ApnRepository"
/** /**
* Query apn related information based on uri. * Query apn related information based on uri.
@@ -79,56 +61,39 @@ const val TAG = "ApnRepository"
fun getApnDataFromUri(uri: Uri, context: Context): ApnData { fun getApnDataFromUri(uri: Uri, context: Context): ApnData {
var apnData = ApnData() var apnData = ApnData()
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList()
contentResolver.query( contentResolver.query(
uri, uri,
sProjection, Projection,
null /* selection */, null /* selection */,
null /* selectionArgs */, null /* selectionArgs */,
null /* sortOrder */ null /* sortOrder */
).use { cursor -> ).use { cursor ->
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
val name = cursor.getString(NAME_INDEX) apnData = ApnData(
val apn = cursor.getString(APN_INDEX) id = cursor.getInt(Telephony.Carriers._ID),
val proxy = cursor.getString(PROXY_INDEX) name = cursor.getString(Telephony.Carriers.NAME),
val port = cursor.getString(PORT_INDEX) apn = cursor.getString(Telephony.Carriers.APN),
val userName = cursor.getString(USER_INDEX) proxy = cursor.getString(Telephony.Carriers.PROXY),
val server = cursor.getString(SERVER_INDEX) port = cursor.getString(Telephony.Carriers.PORT),
val passWord = cursor.getString(PASSWORD_INDEX) userName = cursor.getString(Telephony.Carriers.USER),
val mmsc = cursor.getString(MMSC_INDEX) passWord = cursor.getString(Telephony.Carriers.PASSWORD),
val mmsProxy = cursor.getString(MMSPROXY_INDEX) server = cursor.getString(Telephony.Carriers.SERVER),
val mmsPort = cursor.getString(MMSPORT_INDEX) mmsc = cursor.getString(Telephony.Carriers.MMSC),
val authType = cursor.getInt(AUTH_TYPE_INDEX) mmsProxy = cursor.getString(Telephony.Carriers.MMSPROXY),
val apnType = cursor.getString(TYPE_INDEX) mmsPort = cursor.getString(Telephony.Carriers.MMSPORT),
val apnProtocol = convertProtocol2Options(cursor.getString(PROTOCOL_INDEX), context) authType = cursor.getInt(Telephony.Carriers.AUTH_TYPE),
val apnRoaming = apnType = cursor.getString(Telephony.Carriers.TYPE),
convertProtocol2Options(cursor.getString(ROAMING_PROTOCOL_INDEX), context) apnProtocol = context.convertProtocol2Options(
val apnEnable = cursor.getInt(CARRIER_ENABLED_INDEX) == 1 cursor.getString(Telephony.Carriers.PROTOCOL)
val networkType = cursor.getLong(NETWORK_TYPE_INDEX) ),
apnRoaming = context.convertProtocol2Options(
val edited = cursor.getInt(EDITED_INDEX) cursor.getString(Telephony.Carriers.ROAMING_PROTOCOL)
val userEditable = cursor.getInt(USER_EDITABLE_INDEX) ),
apnEnable = cursor.getInt(Telephony.Carriers.CARRIER_ENABLED) == 1,
apnData = apnData.copy( networkType = cursor.getLong(Telephony.Carriers.NETWORK_TYPE_BITMASK),
name = name, edited = cursor.getInt(Telephony.Carriers.EDITED_STATUS),
apn = apn, userEditable = cursor.getInt(Telephony.Carriers.USER_EDITABLE),
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,
) )
} }
} }
@@ -138,42 +103,23 @@ fun getApnDataFromUri(uri: Uri, context: Context): ApnData {
return 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 * Returns The UI choice index corresponding to the given raw value of the protocol preference
* raw value of the protocol preference (e.g., "IPV4V6"). If unknown, * (e.g., "IPV4V6").
* return null. * If unknown, return -1.
*
* @return UI choice
*/ */
private fun convertProtocol2Options(raw: String, context: Context): String { private fun Context.convertProtocol2Options(protocol: String): Int {
val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() var normalizedProtocol = protocol.uppercase(Locale.getDefault())
val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() if (normalizedProtocol == "IPV4") normalizedProtocol = "IP"
var uRaw = raw.uppercase(Locale.getDefault()) return resources.getStringArray(R.array.apn_protocol_values).indexOf(normalizedProtocol)
uRaw = if (uRaw == "IPV4") "IP" else uRaw
val protocolIndex = apnProtocolValues.indexOf(uRaw)
return if (protocolIndex == -1) {
""
} else {
try {
apnProtocolOptions[protocolIndex]
} catch (e: ArrayIndexOutOfBoundsException) {
""
}
}
} }
fun convertOptions2Protocol(protocolIndex: Int, context: Context): String { fun Context.convertOptions2Protocol(protocolIndex: Int): String =
val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList() resources.getStringArray(R.array.apn_protocol_values).getOrElse(protocolIndex) { "" }
return if (protocolIndex == -1) {
""
} else {
try {
apnProtocolValues[protocolIndex]
} catch (e: ArrayIndexOutOfBoundsException) {
""
}
}
}
fun updateApnDataToDatabase( fun updateApnDataToDatabase(
newApn: Boolean, newApn: Boolean,
@@ -183,13 +129,13 @@ fun updateApnDataToDatabase(
) { ) {
ThreadUtils.postOnBackgroundThread { ThreadUtils.postOnBackgroundThread {
if (newApn) { 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) val newUri = context.contentResolver.insert(uriInit, values)
if (newUri == null) { if (newUri == null) {
Log.e(TAG, "Can't add a new apn to database $uriInit") Log.e(TAG, "Can't add a new apn to database $uriInit")
} }
} else { } else {
// Update the existing apn Log.d(TAG, "Updating an existing APN to the database $uriInit $values")
context.contentResolver.update( context.contentResolver.update(
uriInit, values, null /* where */, null /* selection Args */ uriInit, values, null /* where */, null /* selection Args */
) )
@@ -210,9 +156,12 @@ private val NonDuplicatedKeys = setOf(
) )
fun isItemExist(apnData: ApnData, context: Context): String? { fun isItemExist(apnData: ApnData, context: Context): String? {
val contentValueMap = apnData.getContentValueMap(context).filterKeys { it in NonDuplicatedKeys } val selectionMap = apnData.getContentValueMap(context).filterKeys { it in NonDuplicatedKeys }
val list = contentValueMap.entries.toList() .mapKeys { "${it.key} = ?" }
val selection = list.joinToString(" AND ") { "${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<String> = list.map { it.value.toString() }.toTypedArray() val selectionArgs: Array<String> = list.map { it.value.toString() }.toTypedArray()
context.contentResolver.query( context.contentResolver.query(
Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, apnData.subId.toString()), Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, apnData.subId.toString()),

View File

@@ -22,19 +22,14 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Telephony import android.provider.Telephony
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import android.text.TextUtils
import android.util.Log import android.util.Log
import com.android.internal.util.ArrayUtils
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.apn.ApnTypes.APN_TYPES import com.android.settings.network.apn.ApnTypes.getPreSelectedApnType
import com.android.settings.network.apn.ApnTypes.APN_TYPE_ALL
import com.android.settings.network.apn.ApnTypes.APN_TYPE_EMERGENCY private const val TAG = "ApnStatus"
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
data class ApnData( data class ApnData(
val id: Int = -1,
val name: String = "", val name: String = "",
val apn: String = "", val apn: String = "",
val proxy: String = "", val proxy: String = "",
@@ -86,8 +81,8 @@ data class ApnData(
Telephony.Carriers.MMSPROXY to mmsProxy, Telephony.Carriers.MMSPROXY to mmsProxy,
Telephony.Carriers.MMSPORT to mmsPort, Telephony.Carriers.MMSPORT to mmsPort,
Telephony.Carriers.AUTH_TYPE to authType, Telephony.Carriers.AUTH_TYPE to authType,
Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context), Telephony.Carriers.PROTOCOL to context.convertOptions2Protocol(apnProtocol),
Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context), Telephony.Carriers.ROAMING_PROTOCOL to context.convertOptions2Protocol(apnRoaming),
Telephony.Carriers.TYPE to apnType, Telephony.Carriers.TYPE to apnType,
Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType, Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType,
Telephony.Carriers.CARRIER_ENABLED to apnEnable, Telephony.Carriers.CARRIER_ENABLED to apnEnable,
@@ -105,7 +100,7 @@ data class CustomizedConfig(
val isAddApnAllowed: Boolean = true, val isAddApnAllowed: Boolean = true,
val readOnlyApnTypes: List<String> = emptyList(), val readOnlyApnTypes: List<String> = emptyList(),
val readOnlyApnFields: List<String> = emptyList(), val readOnlyApnFields: List<String> = emptyList(),
val defaultApnTypes: List<String> = emptyList(), val defaultApnTypes: List<String>? = null,
val defaultApnProtocol: String = "", val defaultApnProtocol: String = "",
val defaultApnRoamingProtocol: String = "", val defaultApnRoamingProtocol: String = "",
) )
@@ -118,19 +113,18 @@ data class CustomizedConfig(
* *
* @return Initialized CustomizedConfig information. * @return Initialized CustomizedConfig information.
*/ */
fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData { fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData? {
val uriType = arguments.getString(URI_TYPE) ?: return null
val uriType = arguments.getString(URI_TYPE)!!
if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) { if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit") Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit")
return ApnData() //TODO: finish return null
} }
var apnDataInit = when (uriType) { var apnDataInit = when (uriType) {
EDIT_URL -> getApnDataFromUri(uriInit, context) EDIT_URL -> getApnDataFromUri(uriInit, context)
INSERT_URL -> ApnData() INSERT_URL -> ApnData()
else -> ApnData() //TODO: finish else -> return null
} }
if (uriType == INSERT_URL) { if (uriType == INSERT_URL) {
@@ -143,13 +137,18 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int
apnDataInit = apnDataInit =
apnDataInit.copy(customizedConfig = getCarrierCustomizedConfig(apnDataInit, configManager)) apnDataInit.copy(customizedConfig = getCarrierCustomizedConfig(apnDataInit, configManager))
if (apnDataInit.newApn) {
apnDataInit = apnDataInit.copy(
apnType = getPreSelectedApnType(apnDataInit.customizedConfig)
)
}
apnDataInit = apnDataInit.copy( apnDataInit = apnDataInit.copy(
apnEnableEnabled = apnEnableEnabled =
context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled) context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled)
) )
// TODO: mIsCarrierIdApn // TODO: mIsCarrierIdApn
disableInit(apnDataInit) return disableInit(apnDataInit)
return apnDataInit
} }
/** /**
@@ -199,53 +198,7 @@ fun validateApnData(apnData: ApnData, context: Context): String? {
if (errorMsg == null) { if (errorMsg == null) {
errorMsg = isItemExist(apnData, context) errorMsg = isItemExist(apnData, context)
} }
if (errorMsg == null) { return errorMsg?.apply { Log.d(TAG, "APN data not valid, reason: $this") }
errorMsg = validateAPNType(
true,
apnData.apnType,
apnData.customizedConfig.readOnlyApnTypes,
context
)
}
return errorMsg
}
private fun getUserEnteredApnType(apnType: String, readOnlyApnTypes: List<String>): 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>): 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()
} }
/** /**
@@ -258,6 +211,10 @@ fun getCarrierCustomizedConfig(
apnInit: ApnData, apnInit: ApnData,
configManager: CarrierConfigManager configManager: CarrierConfigManager
): CustomizedConfig { ): CustomizedConfig {
fun log(message: String) {
Log.d(TAG, "getCarrierCustomizedConfig: $message")
}
val b = configManager.getConfigForSubId( val b = configManager.getConfigForSubId(
apnInit.subId, apnInit.subId,
CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY,
@@ -270,72 +227,61 @@ fun getCarrierCustomizedConfig(
val customizedConfig = CustomizedConfig( val customizedConfig = CustomizedConfig(
readOnlyApnTypes = b.getStringArray( readOnlyApnTypes = b.getStringArray(
CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY 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 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 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 CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING
) ?: "", defaultApnRoamingProtocol = b.getString( ) ?: "",
defaultApnRoamingProtocol = b.getString(
CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING 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)) { customizedConfig.defaultApnTypes?.takeIf { it.isNotEmpty() }?.let {
Log.d( log("default apn types: $it")
TAG,
"getCarrierCustomizedConfig: default apn types: " + customizedConfig.defaultApnTypes.joinToString(
", "
)
)
} }
if (!TextUtils.isEmpty(customizedConfig.defaultApnProtocol)) { if (customizedConfig.defaultApnProtocol.isNotEmpty()) {
Log.d( log("default apn protocol: ${customizedConfig.defaultApnProtocol}")
TAG,
"getCarrierCustomizedConfig: default apn protocol: ${customizedConfig.defaultApnProtocol}"
)
} }
if (!TextUtils.isEmpty(customizedConfig.defaultApnRoamingProtocol)) { if (customizedConfig.defaultApnRoamingProtocol.isNotEmpty()) {
Log.d( log("default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}")
TAG,
"getCarrierCustomizedConfig: default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}"
)
} }
if (!customizedConfig.isAddApnAllowed) { if (!customizedConfig.isAddApnAllowed) {
Log.d(TAG, "getCarrierCustomizedConfig: not allow to add new APN") log("not allow to add new APN")
} }
return customizedConfig return customizedConfig
} }
fun disableInit(apnDataInit: ApnData): ApnData { private fun ApnData.isReadOnly(): Boolean {
var apnData = apnDataInit Log.d(TAG, "isReadOnly: edited $edited")
val isUserEdited = apnDataInit.edited == Telephony.Carriers.USER_EDITED if (edited == Telephony.Carriers.USER_EDITED) return false
Log.d(TAG, "disableInit: EDITED $isUserEdited")
// if it's not a USER_EDITED apn, check if it's read-only // if it's not a USER_EDITED apn, check if it's read-only
if (!isUserEdited && (apnDataInit.userEditable == 0 return userEditable == 0 ||
|| apnTypesMatch(apnDataInit.customizedConfig.readOnlyApnTypes, apnDataInit.apnType)) ApnTypes.isApnTypeReadOnly(apnType, customizedConfig.readOnlyApnTypes)
) { }
fun disableInit(apnDataInit: ApnData): ApnData {
if (apnDataInit.isReadOnly()) {
Log.d(TAG, "disableInit: read-only APN") Log.d(TAG, "disableInit: read-only APN")
apnData = val apnData = apnDataInit.copy(
apnDataInit.copy(customizedConfig = apnDataInit.customizedConfig.copy(readOnlyApn = true)) 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(", ")
})"
) )
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 return apnData
} }
private fun apnTypesMatch(apnTypeList: List<String>, apnType: String): Boolean {
val normalizeApnTypeList = apnTypeList.map(::normalizeApnType)
return hasAllApns(normalizeApnTypeList) ||
apnType.split(",").map(::normalizeApnType).all { it in normalizeApnTypeList }
}
fun hasAllApns(apnTypes: List<String>): 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) { fun deleteApn(uri: Uri, context: Context) {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
contentResolver.delete(uri, null, null) 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) return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty)
else null else null
} }
fun validateAPNType(
validEnabled: Boolean,
apnType: String,
readOnlyApnTypes: List<String>,
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
}

View File

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

View File

@@ -16,128 +16,112 @@
package com.android.settings.network.apn package com.android.settings.network.apn
import androidx.compose.runtime.mutableStateListOf import android.content.Context
import androidx.compose.runtime.snapshots.SnapshotStateList 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 { object ApnTypes {
/** private const val TAG = "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.<br></br>
* APN_TYPE_ALL is a special type to indicate that this APN entry can
* service all data connections.
*/
const val APN_TYPE_ALL = "*"
/** APN type for default data traffic */ private val APN_TYPES = arrayOf(
const val APN_TYPE_DEFAULT = "default" ApnSetting.TYPE_DEFAULT_STRING,
ApnSetting.TYPE_MMS_STRING,
/** APN type for MMS traffic */ ApnSetting.TYPE_SUPL_STRING,
const val APN_TYPE_MMS = "mms" ApnSetting.TYPE_DUN_STRING,
ApnSetting.TYPE_HIPRI_STRING,
/** APN type for SUPL assisted GPS */ ApnSetting.TYPE_FOTA_STRING,
const val APN_TYPE_SUPL = "supl" ApnSetting.TYPE_IMS_STRING,
ApnSetting.TYPE_CBS_STRING,
/** APN type for DUN traffic */ ApnSetting.TYPE_IA_STRING,
const val APN_TYPE_DUN = "dun" ApnSetting.TYPE_EMERGENCY_STRING,
ApnSetting.TYPE_MCX_STRING,
/** APN type for HiPri traffic */ ApnSetting.TYPE_XCAP_STRING,
const val APN_TYPE_HIPRI = "hipri" ApnSetting.TYPE_VSIM_STRING,
ApnSetting.TYPE_BIP_STRING,
/** APN type for FOTA */ ApnSetting.TYPE_ENTERPRISE_STRING,
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
) )
val APN_TYPES_OPTIONS = listOf(APN_TYPE_ALL) + APN_TYPES private fun splitToList(apnType: String): List<String> {
val types = apnType.split(',').map { it.trim().toLowerCase(Locale.current) }
fun getApnTypeSelectedOptionsState(apnType: String): SnapshotStateList<Int> { if (ApnSetting.TYPE_ALL_STRING in types || APN_TYPES.all { it in types }) {
val apnTypeSelectedOptionsState = mutableStateListOf<Int>() return listOf(ApnSetting.TYPE_ALL_STRING)
if (apnType.contains(APN_TYPE_ALL))
APN_TYPES_OPTIONS.forEachIndexed { index, _ ->
apnTypeSelectedOptionsState.add(index)
} }
else { return APN_TYPES.filter { it in types }
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))
}
return apnTypeSelectedOptionsState
} }
fun updateApnType( fun isApnTypeReadOnly(apnType: String, readOnlyTypes: List<String>): Boolean {
apnTypeSelectedOptionsState: SnapshotStateList<Int>, val apnTypes = splitToList(apnType)
defaultApnTypes: List<String>, return ApnSetting.TYPE_ALL_STRING in readOnlyTypes ||
readOnlyApnTypes: List<String> ApnSetting.TYPE_ALL_STRING in apnTypes && readOnlyTypes.isNotEmpty() ||
): String { apnTypes.any { it in readOnlyTypes }
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
} }
private fun getEditableApnType( fun getOptions(context: Context, apnType: String, readOnlyTypes: List<String>) = buildList {
defaultApnTypes: List<String>, val apnTypes = splitToList(apnType)
readOnlyApnTypes: List<String> add(
): String { context.createSettingsDropdownCheckOption(
return defaultApnTypes.filterNot { apnType -> text = ApnSetting.TYPE_ALL_STRING,
readOnlyApnTypes.contains(apnType) || apnType in listOf( isSelectAll = true,
APN_TYPE_IA, changeable = readOnlyTypes.isEmpty(),
APN_TYPE_EMERGENCY, selected = ApnSetting.TYPE_ALL_STRING in apnTypes,
APN_TYPE_MCX, )
APN_TYPE_IMS, )
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,
)
) )
}.joinToString()
} }
}.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()
}
}
fun List<SettingsDropdownCheckOption>.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<String>) =
if (ApnSetting.TYPE_ALL_STRING in readOnlyApnTypes) emptyList()
else APN_TYPES.filter { it !in readOnlyApnTypes + NotPreSelectedTypes }
} }

View File

@@ -39,8 +39,8 @@ import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetail
import com.android.settings.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin; import com.android.settings.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EmptyTextSettings; import com.android.settings.widget.EmptyTextSettings;
import com.android.settings.widget.RestrictedAppPreference;
import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.AppPreference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -122,7 +122,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements
for (ApplicationInfo app : apps) { for (ApplicationInfo app : apps) {
final String pkg = app.packageName; final String pkg = app.packageName;
final CharSequence label = app.loadLabel(mPkgMan); final CharSequence label = app.loadLabel(mPkgMan);
final AppPreference pref = new AppPreference(getPrefContext()); final RestrictedAppPreference pref = new RestrictedAppPreference(getPrefContext());
pref.setKey(pkg); pref.setKey(pkg);
pref.setIcon(app.loadIcon(mPkgMan)); pref.setIcon(app.loadIcon(mPkgMan));
pref.setTitle(label); pref.setTitle(label);
@@ -133,6 +133,8 @@ public class ZenAccessSettings extends EmptyTextSettings implements
} else { } else {
// Not auto approved, update summary according to notification backend. // Not auto approved, update summary according to notification backend.
pref.setSummary(getPreferenceSummary(pkg)); pref.setSummary(getPreferenceSummary(pkg));
pref.checkEcmRestrictionAndSetDisabled(
android.Manifest.permission.MANAGE_NOTIFICATIONS, app.packageName);
} }
pref.setOnPreferenceClickListener(preference -> { pref.setOnPreferenceClickListener(preference -> {
AppInfoBase.startAppInfoFragment( AppInfoBase.startAppInfoFragment(

View File

@@ -24,6 +24,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
import com.android.settings.bluetooth.BluetoothFeatureProvider 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.fastpair.FastPairFeatureProvider
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
import com.android.settings.dashboard.DashboardFeatureProvider import com.android.settings.dashboard.DashboardFeatureProvider
@@ -182,6 +183,11 @@ abstract class FeatureFactory {
*/ */
abstract val displayFeatureProvider: DisplayFeatureProvider abstract val displayFeatureProvider: DisplayFeatureProvider
/**
* Gets implementation for audio sharing related feature.
*/
abstract val audioSharingFeatureProvider: AudioSharingFeatureProvider
companion object { companion object {
private var _factory: FeatureFactory? = null private var _factory: FeatureFactory? = null

View File

@@ -34,6 +34,8 @@ import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImp
import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl
import com.android.settings.bluetooth.BluetoothFeatureProvider import com.android.settings.bluetooth.BluetoothFeatureProvider
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl 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.dock.DockUpdaterFeatureProviderImpl
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProviderImpl import com.android.settings.connecteddevice.fastpair.FastPairFeatureProviderImpl
@@ -192,7 +194,12 @@ open class FeatureFactoryImpl : FeatureFactory() {
override val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider by lazy { override val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider by lazy {
PrivateSpaceLoginFeatureProviderImpl() PrivateSpaceLoginFeatureProviderImpl()
} }
override val displayFeatureProvider: DisplayFeatureProvider by lazy { override val displayFeatureProvider: DisplayFeatureProvider by lazy {
DisplayFeatureProviderImpl() DisplayFeatureProviderImpl()
} }
override val audioSharingFeatureProvider: AudioSharingFeatureProvider by lazy {
AudioSharingFeatureProviderImpl()
}
} }

View File

@@ -56,7 +56,7 @@ public interface SearchFeatureProvider {
* @throws IllegalArgumentException when caller is null * @throws IllegalArgumentException when caller is null
* @throws SecurityException when caller is not allowed to launch search result page * @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; throws SecurityException, IllegalArgumentException;
/** /**

View File

@@ -17,13 +17,14 @@
package com.android.settings.search; package com.android.settings.search;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.android.settingslib.search.SearchIndexableResources; import com.android.settingslib.search.SearchIndexableResources;
import com.android.settingslib.search.SearchIndexableResourcesMobile; import com.android.settingslib.search.SearchIndexableResourcesMobile;
@@ -32,21 +33,18 @@ import com.android.settingslib.search.SearchIndexableResourcesMobile;
*/ */
public class SearchFeatureProviderImpl implements SearchFeatureProvider { public class SearchFeatureProviderImpl implements SearchFeatureProvider {
private static final String TAG = "SearchFeatureProvider";
private SearchIndexableResources mSearchIndexableResources; private SearchIndexableResources mSearchIndexableResources;
@Override @Override
public void verifyLaunchSearchResultPageCaller(Context context, ComponentName caller) { public void verifyLaunchSearchResultPageCaller(@NonNull Context context,
if (caller == null) { @NonNull String callerPackage) {
if (TextUtils.isEmpty(callerPackage)) {
throw new IllegalArgumentException("ExternalSettingsTrampoline intents " throw new IllegalArgumentException("ExternalSettingsTrampoline intents "
+ "must be called with startActivityForResult"); + "must be called with startActivityForResult");
} }
final String packageName = caller.getPackageName(); final boolean isSettingsPackage = TextUtils.equals(callerPackage, context.getPackageName())
final boolean isSettingsPackage = TextUtils.equals(packageName, context.getPackageName()) || TextUtils.equals(getSettingsIntelligencePkgName(context), callerPackage);
|| TextUtils.equals(getSettingsIntelligencePkgName(context), packageName); final boolean isAllowlistedPackage = isSignatureAllowlisted(context, callerPackage);
final boolean isAllowlistedPackage =
isSignatureAllowlisted(context, caller.getPackageName());
if (isSettingsPackage || isAllowlistedPackage) { if (isSettingsPackage || isAllowlistedPackage) {
return; return;
} }

View File

@@ -21,7 +21,6 @@ import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB;
import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent; import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.getTrampolineIntent;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -53,11 +52,11 @@ public class SearchResultTrampoline extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final ComponentName callingActivity = getCallingActivity(); final String callerPackage = getLaunchedFromPackage();
// First make sure caller has privilege to launch a search result page. // First make sure caller has privilege to launch a search result page.
FeatureFactory.getFeatureFactory() FeatureFactory.getFeatureFactory()
.getSearchFeatureProvider() .getSearchFeatureProvider()
.verifyLaunchSearchResultPageCaller(this, callingActivity); .verifyLaunchSearchResultPageCaller(this, callerPackage);
// Didn't crash, proceed and launch the result as a subsetting. // Didn't crash, proceed and launch the result as a subsetting.
Intent intent = getIntent(); Intent intent = getIntent();
final String highlightMenuKey = intent.getStringExtra( final String highlightMenuKey = intent.getStringExtra(
@@ -106,7 +105,7 @@ public class SearchResultTrampoline extends Activity {
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)
|| ActivityEmbeddingUtils.isAlreadyEmbedded(this)) { || ActivityEmbeddingUtils.isAlreadyEmbedded(this)) {
startActivity(intent); startActivity(intent);
} else if (isSettingsIntelligence(callingActivity)) { } else if (isSettingsIntelligence(callerPackage)) {
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) { if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SETTINGS_SEARCH_ALWAYS_EXPAND)) {
startActivity(getTrampolineIntent(intent, highlightMenuKey) startActivity(getTrampolineIntent(intent, highlightMenuKey)
.setClass(this, DeepLinkHomepageActivityInternal.class) .setClass(this, DeepLinkHomepageActivityInternal.class)
@@ -139,9 +138,9 @@ public class SearchResultTrampoline extends Activity {
finish(); finish();
} }
private boolean isSettingsIntelligence(ComponentName callingActivity) { private boolean isSettingsIntelligence(String callerPackage) {
return callingActivity != null && TextUtils.equals( return TextUtils.equals(
callingActivity.getPackageName(), callerPackage,
FeatureFactory.getFeatureFactory().getSearchFeatureProvider() FeatureFactory.getFeatureFactory().getSearchFeatureProvider()
.getSettingsIntelligencePkgName(this)); .getSettingsIntelligencePkgName(this));
} }

View File

@@ -17,6 +17,7 @@
package com.android.settings.spa.app.appinfo package com.android.settings.spa.app.appinfo
import android.app.AppOpsManager import android.app.AppOpsManager
import android.app.ecm.EnhancedConfirmationManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.UserManager import android.os.UserManager
@@ -90,12 +91,18 @@ fun AppInfoSettingsMoreOptions(
private fun ApplicationInfo.allowRestrictedSettings(context: Context, onSuccess: () -> Unit) { private fun ApplicationInfo.allowRestrictedSettings(context: Context, onSuccess: () -> Unit) {
AppInfoDashboardFragment.showLockScreen(context) { AppInfoDashboardFragment.showLockScreen(context) {
if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
&& android.security.Flags.extendEcmToAllSettings()) {
val manager = context.getSystemService(EnhancedConfirmationManager::class.java)!!
manager.clearRestriction(packageName)
} else {
context.appOpsManager.setMode( context.appOpsManager.setMode(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
uid, uid,
packageName, packageName,
AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED,
) )
}
onSuccess() onSuccess()
val toastString = context.getString( val toastString = context.getString(
R.string.toast_allows_restricted_settings_successfully, R.string.toast_allows_restricted_settings_successfully,
@@ -137,7 +144,7 @@ private suspend fun ApplicationInfo.getMoreOptionsState(
) )
} }
val shouldShowAccessRestrictedSettingsDeferred = async { val shouldShowAccessRestrictedSettingsDeferred = async {
shouldShowAccessRestrictedSettings(context.appOpsManager) shouldShowAccessRestrictedSettings(context)
} }
val isProfileOrDeviceOwner = val isProfileOrDeviceOwner =
Utils.isProfileOrDeviceOwner(context.userManager, context.devicePolicyManager, packageName) Utils.isProfileOrDeviceOwner(context.userManager, context.devicePolicyManager, packageName)
@@ -169,7 +176,14 @@ private fun ApplicationInfo.isOtherUserHasInstallPackage(
.filter { it.id != userId } .filter { it.id != userId }
.any { packageManagers.isPackageInstalledAsUser(packageName, it.id) } .any { packageManagers.isPackageInstalledAsUser(packageName, it.id) }
private fun ApplicationInfo.shouldShowAccessRestrictedSettings(appOpsManager: AppOpsManager) = private fun ApplicationInfo.shouldShowAccessRestrictedSettings(context: Context): Boolean {
appOpsManager.noteOpNoThrow( 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.OP_ACCESS_RESTRICTED_SETTINGS, uid, packageName, null, null
) == AppOpsManager.MODE_IGNORED ) == AppOpsManager.MODE_IGNORED
}
}

View File

@@ -37,14 +37,14 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.widget.EmptyTextSettings; import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.applications.ServiceListing; import com.android.settingslib.applications.ServiceListing;
import com.android.settingslib.widget.AppSwitchPreference; import com.android.settingslib.widget.TwoTargetPreference;
import java.util.List; import java.util.List;
@@ -121,10 +121,12 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings {
} }
final CharSequence finalTitle = title; final CharSequence finalTitle = title;
final String summary = service.loadLabel(mPm).toString(); final String summary = service.loadLabel(mPm).toString();
final TwoStatePreference pref = new AppSwitchPreference(getPrefContext()); final RestrictedSwitchPreference pref =
new RestrictedSwitchPreference(getPrefContext());
pref.setPersistent(false); pref.setPersistent(false);
pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo, pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
UserHandle.getUserId(service.applicationInfo.uid))); UserHandle.getUserId(service.applicationInfo.uid)));
pref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM);
if (title != null && !title.equals(summary)) { if (title != null && !title.equals(summary)) {
pref.setTitle(title); pref.setTitle(title);
pref.setSummary(summary); pref.setSummary(summary);
@@ -150,6 +152,9 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings {
} }
}); });
pref.setKey(cn.flattenToString()); pref.setKey(cn.flattenToString());
if (!pref.isChecked()) {
pref.checkEcmRestrictionAndSetDisabled(mConfig.permission, service.packageName);
}
screen.addPreference(pref); screen.addPreference(pref);
} }
highlightPreferenceIfNeeded(); highlightPreferenceIfNeeded();

View File

@@ -21,6 +21,7 @@ import android.os.UserHandle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
@@ -72,11 +73,19 @@ public class RestrictedAppPreference extends AppPreference {
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (isDisabledByAdmin() && enabled) { boolean changed = false;
return; 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) { public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
if (mHelper.setDisabledByAdmin(admin)) { if (mHelper.setDisabledByAdmin(admin)) {
@@ -88,6 +97,10 @@ public class RestrictedAppPreference extends AppPreference {
return mHelper.isDisabledByAdmin(); return mHelper.isDisabledByAdmin();
} }
public boolean isDisabledByEcm() {
return mHelper.isDisabledByEcm();
}
public void useAdminDisabledSummary(boolean useSummary) { public void useAdminDisabledSummary(boolean useSummary) {
mHelper.useAdminDisabledSummary(useSummary); mHelper.useAdminDisabledSummary(useSummary);
} }
@@ -112,4 +125,15 @@ public class RestrictedAppPreference extends AppPreference {
public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
mHelper.checkRestrictionAndSetDisabled(userRestriction, 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);
}
} }

View File

@@ -38,6 +38,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -57,7 +58,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** Tests for {@link AccessibilityDetailsSettingsFragment}. */ /** Tests for {@link AccessibilityDetailsSettingsFragment}. */
@Config(shadows = ShadowDevicePolicyManager.class) @Config(shadows = {
ShadowDevicePolicyManager.class,
ShadowRestrictedLockUtilsInternal.class
})
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class AccessibilityDetailsSettingsFragmentTest { public class AccessibilityDetailsSettingsFragmentTest {
private static final String PACKAGE_NAME = "com.foo.bar"; private static final String PACKAGE_NAME = "com.foo.bar";

View File

@@ -52,6 +52,7 @@ import com.android.settings.testutils.XmlTestUtils;
import com.android.settings.testutils.shadow.ShadowApplicationPackageManager; import com.android.settings.testutils.shadow.ShadowApplicationPackageManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settingslib.RestrictedPreference; import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -87,6 +88,7 @@ import java.util.List;
ShadowUserManager.class, ShadowUserManager.class,
ShadowColorDisplayManager.class, ShadowColorDisplayManager.class,
ShadowApplicationPackageManager.class, ShadowApplicationPackageManager.class,
ShadowRestrictedLockUtilsInternal.class,
}) })
public class AccessibilitySettingsTest { public class AccessibilitySettingsTest {
private static final String PACKAGE_NAME = "com.android.test"; private static final String PACKAGE_NAME = "com.android.test";

View File

@@ -31,9 +31,14 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo; 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 androidx.test.core.app.ApplicationProvider;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference; import com.android.settingslib.RestrictedPreference;
import org.junit.Rule; import org.junit.Rule;
@@ -45,6 +50,7 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; import java.io.IOException;
@@ -53,6 +59,9 @@ import java.util.List;
/** Test for {@link RestrictedPreferenceHelper}. */ /** Test for {@link RestrictedPreferenceHelper}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowRestrictedLockUtilsInternal.class
})
public class RestrictedPreferenceHelperTest { public class RestrictedPreferenceHelperTest {
private static final String PACKAGE_NAME = "com.android.test"; private static final String PACKAGE_NAME = "com.android.test";
@@ -72,6 +81,11 @@ public class RestrictedPreferenceHelperTest {
private AccessibilityShortcutInfo mShortcutInfo; private AccessibilityShortcutInfo mShortcutInfo;
private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext); private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext);
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Test @Test
public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() {
final String key = COMPONENT_NAME.flattenToString(); final String key = COMPONENT_NAME.flattenToString();
@@ -85,6 +99,37 @@ public class RestrictedPreferenceHelperTest {
assertThat(preference.getKey()).isEqualTo(key); 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<AccessibilityServiceInfo> infoList = new ArrayList<>(
singletonList(mServiceInfo));
final List<RestrictedPreference> 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<AccessibilityServiceInfo> infoList = new ArrayList<>(
singletonList(mServiceInfo));
final List<RestrictedPreference> preferenceList =
mHelper.createAccessibilityServicePreferenceList(infoList);
final RestrictedPreference preference = preferenceList.get(0);
assertThat(preference.isDisabledByEcm()).isFalse();
}
@Test @Test
public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() {
final String key = COMPONENT_NAME.flattenToString(); final String key = COMPONENT_NAME.flattenToString();

View File

@@ -16,13 +16,35 @@
package com.android.settings.applications.specialaccess.premiumsms; 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 static org.mockito.Mockito.verify;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.telephony.SmsManager; 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.internal.logging.nano.MetricsProto;
import com.android.settings.datausage.AppStateDataUsageBridge;
import com.android.settings.testutils.FakeFeatureFactory; 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.Before;
import org.junit.Test; import org.junit.Test;
@@ -30,19 +52,28 @@ import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowRestrictedLockUtilsInternal.class
})
public class PremiumSmsAccessTest { public class PremiumSmsAccessTest {
private FakeFeatureFactory mFeatureFactory; private FakeFeatureFactory mFeatureFactory;
private PremiumSmsAccess mFragment; private PremiumSmsAccess mFragment;
private Context mContext;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mFeatureFactory = FakeFeatureFactory.setupForTest(); mFeatureFactory = FakeFeatureFactory.setupForTest();
mFragment = new PremiumSmsAccess(); mFragment = new PremiumSmsAccess();
mFragment.onAttach(RuntimeEnvironment.application); mContext = RuntimeEnvironment.application;
mFragment.onAttach(mContext);
} }
@Test @Test
@@ -74,4 +105,89 @@ public class PremiumSmsAccessTest {
"app", "app",
SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW); 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<ApplicationsState.AppEntry> createAppEntries(String... packageNames) {
final ArrayList<ApplicationsState.AppEntry> 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;
}
} }

View File

@@ -16,7 +16,6 @@
package com.android.settings.bluetooth; package com.android.settings.bluetooth;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -25,36 +24,26 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.AudioManager; 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 android.util.Pair;
import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider;
import com.android.settings.dashboard.DashboardFragment; 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.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 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.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -75,9 +64,6 @@ import java.util.Collection;
ShadowBluetoothUtils.class ShadowBluetoothUtils.class
}) })
public class AvailableMediaBluetoothDeviceUpdaterTest { public class AvailableMediaBluetoothDeviceUpdaterTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
@Mock private DashboardFragment mDashboardFragment; @Mock private DashboardFragment mDashboardFragment;
@@ -86,11 +72,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
@Mock private BluetoothDevice mBluetoothDevice; @Mock private BluetoothDevice mBluetoothDevice;
@Mock private Drawable mDrawable; @Mock private Drawable mDrawable;
@Mock private LocalBluetoothManager mLocalBtManager; @Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState;
private Context mContext; private Context mContext;
private AvailableMediaBluetoothDeviceUpdater mBluetoothDeviceUpdater; private AvailableMediaBluetoothDeviceUpdater mBluetoothDeviceUpdater;
@@ -98,12 +80,14 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
private AudioManager mAudioManager; private AudioManager mAudioManager;
private BluetoothDevicePreference mPreference; private BluetoothDevicePreference mPreference;
private ShadowBluetoothAdapter mShadowBluetoothAdapter; private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private AudioSharingFeatureProvider mFeatureProvider;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mFeatureProvider = FakeFeatureFactory.setupForTest().getAudioSharingFeatureProvider();
mAudioManager = mContext.getSystemService(AudioManager.class); mAudioManager = mContext.getSystemService(AudioManager.class);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
mLocalBtManager = Utils.getLocalBtManager(mContext); mLocalBtManager = Utils.getLocalBtManager(mContext);
@@ -267,13 +251,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
} }
@Test @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void
public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_addsPreference() { onProfileConnectionStateChanged_leaDeviceConnected_notInCallNoSharing_addsPreference() {
setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false);
mAudioManager.setMode(AudioManager.MODE_NORMAL); mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true); .thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedBluetoothDevice,
@@ -284,13 +270,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
} }
@Test @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void
public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_addsPreference() { onProfileConnectionStateChanged_leaDeviceConnected_inCallNoSharing_addsPreference() {
setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false);
mAudioManager.setMode(AudioManager.MODE_IN_CALL); mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true); .thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mFeatureProvider.isAudioSharingFilterMatched(
any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
.thenReturn(false);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedBluetoothDevice,
@@ -301,50 +289,16 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
} }
@Test @Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void public void
onProfileConnectionStateChanged_leaDeviceConnected_notInCall_notInBroadcast_addsPref() { onProfileConnectionStateChanged_leaDeviceConnected_notInCallInSharing_removesPref() {
setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ false);
mAudioManager.setMode(AudioManager.MODE_NORMAL); mAudioManager.setMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true); .thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged( when(mFeatureProvider.isAudioSharingFilterMatched(
mCachedBluetoothDevice, any(CachedBluetoothDevice.class), any(LocalBluetoothManager.class)))
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)))
.thenReturn(true); .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( mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedBluetoothDevice,
@@ -355,14 +309,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
} }
@Test @Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onProfileConnectionStateChanged_leaDeviceConnected_inCallInSharing_removesPref() {
public void mAudioManager.setMode(AudioManager.MODE_NORMAL);
onProfileConnectionStateChanged_leaDeviceConnected_inCall_inBroadcast_removesPref() {
setUpBroadcast(/* isSupported= */ true, /* isBroadcasting= */ true);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true); .thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).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( mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedBluetoothDevice,
@@ -414,56 +369,9 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
} }
@Test @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void onClick_Preference_setActive() { public void onClick_Preference_setActive() {
setUpBroadcast(/* isSupported= */ false, /* isBroadcasting= */ false);
mBluetoothDeviceUpdater.onPreferenceClick(mPreference); mBluetoothDeviceUpdater.onPreferenceClick(mPreference);
verify(mCachedBluetoothDevice).setActive(); 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);
}
}
} }

View File

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

View File

@@ -25,6 +25,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -163,6 +164,81 @@ public class UnrestrictedDataAccessPreferenceControllerTest {
mController.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); 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<AppEntry> createAppEntries(String... packageNames) { private ArrayList<AppEntry> createAppEntries(String... packageNames) {
final ArrayList<AppEntry> appEntries = new ArrayList<>(); final ArrayList<AppEntry> appEntries = new ArrayList<>();
for (int i = 0; i < packageNames.length; ++i) { for (int i = 0; i < packageNames.length; ++i) {

View File

@@ -20,7 +20,6 @@ package com.android.settings.search;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
@@ -131,20 +130,22 @@ public class SearchFeatureProviderImplTest {
@Test(expected = SecurityException.class) @Test(expected = SecurityException.class)
public void verifyLaunchSearchResultPageCaller_badCaller_shouldCrash() { public void verifyLaunchSearchResultPageCaller_badCaller_shouldCrash() {
final ComponentName cn = new ComponentName("pkg", "class"); final String packageName = "pkg";
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName);
} }
@Test @Test
public void verifyLaunchSearchResultPageCaller_settingsCaller_shouldNotCrash() { public void verifyLaunchSearchResultPageCaller_settingsCaller_shouldNotCrash() {
final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class"); final String packageName = mActivity.getPackageName();
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName);
} }
@Test @Test
public void verifyLaunchSearchResultPageCaller_settingsIntelligenceCaller_shouldNotCrash() { public void verifyLaunchSearchResultPageCaller_settingsIntelligenceCaller_shouldNotCrash() {
final String packageName = mProvider.getSettingsIntelligencePkgName(mActivity); final String packageName = mProvider.getSettingsIntelligencePkgName(mActivity);
final ComponentName cn = new ComponentName(packageName, "class");
mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn); mProvider.verifyLaunchSearchResultPageCaller(mActivity, packageName);
} }
} }

View File

@@ -15,9 +15,11 @@
*/ */
package com.android.settings.testutils.shadow; package com.android.settings.testutils.shadow;
import android.annotation.NonNull;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -38,6 +40,8 @@ public class ShadowRestrictedLockUtilsInternal {
private static DevicePolicyManager sDevicePolicyManager; private static DevicePolicyManager sDevicePolicyManager;
private static String[] sDisabledTypes; private static String[] sDisabledTypes;
private static int sKeyguardDisabledFeatures; private static int sKeyguardDisabledFeatures;
private static String[] sEcmRestrictedPkgs;
private static boolean sAccessibilityServiceRestricted;
@Resetter @Resetter
public static void reset() { public static void reset() {
@@ -47,6 +51,8 @@ public class ShadowRestrictedLockUtilsInternal {
sDisabledTypes = new String[0]; sDisabledTypes = new String[0];
sMaximumTimeToLockIsSet = false; sMaximumTimeToLockIsSet = false;
sMteOverridden = false; sMteOverridden = false;
sEcmRestrictedPkgs = new String[0];
sAccessibilityServiceRestricted = false;
} }
@Implementation @Implementation
@@ -108,10 +114,35 @@ public class ShadowRestrictedLockUtilsInternal {
return sMteOverridden ? new EnforcedAdmin() : null; 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) { public static void setRestricted(boolean restricted) {
sIsRestricted = restricted; sIsRestricted = restricted;
} }
public static void setEcmRestrictedPkgs(String... pkgs) {
sEcmRestrictedPkgs = pkgs;
}
public static void setRestrictedPkgs(String... pkgs) { public static void setRestrictedPkgs(String... pkgs) {
sRestrictedPkgs = pkgs; sRestrictedPkgs = pkgs;
} }

View File

@@ -27,6 +27,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider; import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider; import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider; 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.fastpair.FastPairFeatureProvider;
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider; import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider;
@@ -103,6 +104,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public FastPairFeatureProvider mFastPairFeatureProvider; public FastPairFeatureProvider mFastPairFeatureProvider;
public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider;
public DisplayFeatureProvider mDisplayFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider;
public AudioSharingFeatureProvider mAudioSharingFeatureProvider;
/** /**
* Call this in {@code @Before} method of the test class to use fake factory. * 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); mFastPairFeatureProvider = mock(FastPairFeatureProvider.class);
mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class);
mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class);
mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class);
} }
@Override @Override
@@ -339,5 +342,10 @@ public class FakeFeatureFactory extends FeatureFactory {
public DisplayFeatureProvider getDisplayFeatureProvider() { public DisplayFeatureProvider getDisplayFeatureProvider() {
return mDisplayFeatureProvider; return mDisplayFeatureProvider;
} }
@Override
public AudioSharingFeatureProvider getAudioSharingFeatureProvider() {
return mAudioSharingFeatureProvider;
}
} }

View File

@@ -58,7 +58,7 @@ class ApnRepositoryTest {
@Test @Test
fun getApnDataFromUri() { fun getApnDataFromUri() {
// mock out resources and the feature provider // mock out resources and the feature provider
val cursor = MatrixCursor(sProjection) val cursor = MatrixCursor(Projection)
cursor.addRow( cursor.addRow(
arrayOf<Any>( arrayOf<Any>(
0, 0,
@@ -82,7 +82,7 @@ class ApnRepositoryTest {
1, 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) val apnData = getApnDataFromUri(uri, context)

View File

@@ -19,11 +19,16 @@ package com.android.settings.spa.app.appinfo
import android.app.AppOpsManager import android.app.AppOpsManager
import android.app.KeyguardManager import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.app.ecm.EnhancedConfirmationManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.UserInfo import android.content.pm.UserInfo
import android.os.UserManager 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.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotDisplayed
@@ -61,6 +66,9 @@ class AppInfoSettingsMoreOptionsTest {
@get:Rule @get:Rule
val composeTestRule = createComposeRule() val composeTestRule = createComposeRule()
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private lateinit var mockSession: MockitoSession private lateinit var mockSession: MockitoSession
@Spy @Spy
@@ -81,6 +89,9 @@ class AppInfoSettingsMoreOptionsTest {
@Mock @Mock
private lateinit var appOpsManager: AppOpsManager private lateinit var appOpsManager: AppOpsManager
@Mock
private lateinit var enhancedConfirmationManager: EnhancedConfirmationManager
@Mock @Mock
private lateinit var keyguardManager: KeyguardManager private lateinit var keyguardManager: KeyguardManager
@@ -103,6 +114,8 @@ class AppInfoSettingsMoreOptionsTest {
whenever(context.devicePolicyManager).thenReturn(devicePolicyManager) whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
whenever(context.appOpsManager).thenReturn(appOpsManager) whenever(context.appOpsManager).thenReturn(appOpsManager)
whenever(context.getSystemService(KeyguardManager::class.java)).thenReturn(keyguardManager) whenever(context.getSystemService(KeyguardManager::class.java)).thenReturn(keyguardManager)
whenever(context.getSystemService(EnhancedConfirmationManager::class.java))
.thenReturn(enhancedConfirmationManager)
whenever(keyguardManager.isKeyguardSecure).thenReturn(false) whenever(keyguardManager.isKeyguardSecure).thenReturn(false)
whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME)) whenever(Utils.isProfileOrDeviceOwner(userManager, devicePolicyManager, PACKAGE_NAME))
.thenReturn(false) .thenReturn(false)
@@ -158,7 +171,9 @@ class AppInfoSettingsMoreOptionsTest {
} }
@Test @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( whenever(
appOpsManager.noteOpNoThrow( appOpsManager.noteOpNoThrow(
AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, UID, PACKAGE_NAME, null, null 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) { private fun setContent(app: ApplicationInfo) {
composeTestRule.setContent { composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { CompositionLocalProvider(LocalContext provides context) {

View File

@@ -25,6 +25,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
import com.android.settings.bluetooth.BluetoothFeatureProvider 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.fastpair.FastPairFeatureProvider
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
import com.android.settings.dashboard.DashboardFeatureProvider import com.android.settings.dashboard.DashboardFeatureProvider
@@ -149,4 +150,6 @@ class FakeFeatureFactory : FeatureFactory() {
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val displayFeatureProvider: DisplayFeatureProvider override val displayFeatureProvider: DisplayFeatureProvider
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val audioSharingFeatureProvider: AudioSharingFeatureProvider
get() = TODO("Not yet implemented")
} }

View File

@@ -36,6 +36,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.platform.test.annotations.EnableFlags; 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 android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider; 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.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.RestrictedSwitchPreference;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@@ -58,6 +62,8 @@ public class ApprovalPreferenceControllerTest {
@Rule @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private Context mContext; private Context mContext;
private FakeFeatureFactory mFeatureFactory; private FakeFeatureFactory mFeatureFactory;
@@ -122,6 +128,7 @@ public class ApprovalPreferenceControllerTest {
} }
@Test @Test
@RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS)
public void updateState_checked() { public void updateState_checked() {
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn(
AppOpsManager.MODE_ALLOWED); AppOpsManager.MODE_ALLOWED);
@@ -136,19 +143,26 @@ public class ApprovalPreferenceControllerTest {
} }
@Test @Test
@RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS)
public void restrictedSettings_appOpsDisabled() { public void restrictedSettings_appOpsDisabled() {
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( Assert.assertFalse(android.security.Flags.extendEcmToAllSettings());
AppOpsManager.MODE_ERRORED); 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); when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(false);
RestrictedSwitchPreference pref = new RestrictedSwitchPreference( RestrictedSwitchPreference pref = new RestrictedSwitchPreference(
mContext); mContext);
pref.setAppOps(mAppOpsManager); pref.setAppOps(mAppOpsManager);
mController.setSettingIdentifier(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
mController.updateState(pref); mController.updateState(pref);
verify(mAppOpsManager).noteOpNoThrow(anyInt(), anyInt(), anyString());
assertThat(pref.isEnabled()).isFalse(); assertThat(pref.isEnabled()).isFalse();
} }
@Test @Test
@RequiresFlagsDisabled(android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS)
public void restrictedSettings_serviceAlreadyEnabled() { public void restrictedSettings_serviceAlreadyEnabled() {
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn(
AppOpsManager.MODE_ERRORED); AppOpsManager.MODE_ERRORED);

View File

@@ -27,6 +27,7 @@ import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider; import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider; import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider; 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.fastpair.FastPairFeatureProvider;
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider; import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider;
@@ -102,6 +103,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public FastPairFeatureProvider mFastPairFeatureProvider; public FastPairFeatureProvider mFastPairFeatureProvider;
public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider; public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider;
public DisplayFeatureProvider mDisplayFeatureProvider; public DisplayFeatureProvider mDisplayFeatureProvider;
public AudioSharingFeatureProvider mAudioSharingFeatureProvider;
/** Call this in {@code @Before} method of the test class to use fake factory. */ /** Call this in {@code @Before} method of the test class to use fake factory. */
public static FakeFeatureFactory setupForTest() { public static FakeFeatureFactory setupForTest() {
@@ -153,6 +155,7 @@ public class FakeFeatureFactory extends FeatureFactory {
mFastPairFeatureProvider = mock(FastPairFeatureProvider.class); mFastPairFeatureProvider = mock(FastPairFeatureProvider.class);
mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class); mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class);
mDisplayFeatureProvider = mock(DisplayFeatureProvider.class); mDisplayFeatureProvider = mock(DisplayFeatureProvider.class);
mAudioSharingFeatureProvider = mock(AudioSharingFeatureProvider.class);
} }
@Override @Override
@@ -340,4 +343,9 @@ public class FakeFeatureFactory extends FeatureFactory {
public DisplayFeatureProvider getDisplayFeatureProvider() { public DisplayFeatureProvider getDisplayFeatureProvider() {
return mDisplayFeatureProvider; return mDisplayFeatureProvider;
} }
@Override
public AudioSharingFeatureProvider getAudioSharingFeatureProvider() {
return mAudioSharingFeatureProvider;
}
} }