diff --git a/res/drawable/ic_enterprise.xml b/res/drawable/ic_enterprise.xml index c2d9df61893..231f706b83f 100644 --- a/res/drawable/ic_enterprise.xml +++ b/res/drawable/ic_enterprise.xml @@ -17,9 +17,9 @@ + android:viewportWidth="960" + android:viewportHeight="960"> \ No newline at end of file diff --git a/res/drawable/ic_stylus.xml b/res/drawable/ic_stylus.xml new file mode 100644 index 00000000000..eb52fec0418 --- /dev/null +++ b/res/drawable/ic_stylus.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java index af06a0144b9..89a13eff6c8 100644 --- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java +++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java @@ -43,18 +43,22 @@ public final class CombinedProviderInfo { private final List mCredentialProviderInfos; private final @Nullable AutofillServiceInfo mAutofillServiceInfo; private final boolean mIsDefaultAutofillProvider; - private final boolean mIsDefaultCredmanProvider; + private final boolean mIsPrimaryCredmanProvider; /** Constructs an information instance from both autofill and credential provider. */ public CombinedProviderInfo( @Nullable List cpis, @Nullable AutofillServiceInfo asi, boolean isDefaultAutofillProvider, - boolean isDefaultCredmanProvider) { - mCredentialProviderInfos = new ArrayList<>(cpis); + boolean IsPrimaryCredmanProvider) { + if (cpis == null) { + mCredentialProviderInfos = new ArrayList<>(); + } else { + mCredentialProviderInfos = new ArrayList<>(cpis); + } mAutofillServiceInfo = asi; mIsDefaultAutofillProvider = isDefaultAutofillProvider; - mIsDefaultCredmanProvider = isDefaultCredmanProvider; + mIsPrimaryCredmanProvider = IsPrimaryCredmanProvider; } /** Returns the credential provider info. */ @@ -149,8 +153,8 @@ public final class CombinedProviderInfo { } /** Returns whether the provider is the default credman provider. */ - public boolean isDefaultCredmanProvider() { - return mIsDefaultCredmanProvider; + public boolean isPrimaryCredmanProvider() { + return mIsPrimaryCredmanProvider; } /** Returns the settings subtitle. */ @@ -192,7 +196,13 @@ public final class CombinedProviderInfo { } } - // TODO(280454916): Add logic here. + // If there is a primary cred man provider then return that. + for (CombinedProviderInfo cpi : providers) { + if (cpi.isPrimaryCredmanProvider()) { + return cpi; + } + } + return null; } @@ -250,14 +260,14 @@ public final class CombinedProviderInfo { } // Check if we have any enabled cred man services. - boolean isDefaultCredmanProvider = false; - if (!cpi.isEmpty()) { - isDefaultCredmanProvider = cpi.get(0).isEnabled(); + boolean isPrimaryCredmanProvider = false; + if (cpi != null && !cpi.isEmpty()) { + isPrimaryCredmanProvider = cpi.get(0).isPrimary(); } cmpi.add( new CombinedProviderInfo( - cpi, selectedAsi, isDefaultAutofillProvider, isDefaultCredmanProvider)); + cpi, selectedAsi, isDefaultAutofillProvider, isPrimaryCredmanProvider)); } return cmpi; diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index 069336e7000..67a1fa8ebdd 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -120,6 +120,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl mCredentialManager = getCredentialManager(context, preferenceKey.equals("credentials_test")); new SettingContentObserver(mHandler).register(context.getContentResolver()); + mSettingsPackageMonitor.register(context, context.getMainLooper(), false); } private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) { @@ -321,7 +322,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl mEnabledPackageNames.clear(); for (CredentialProviderInfo cpi : availableServices) { - if (cpi.isEnabled()) { + if (cpi.isEnabled() && !cpi.isPrimary()) { mEnabledPackageNames.add(cpi.getServiceInfo().packageName); } } @@ -560,15 +561,25 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return; } - List enabledServices = getEnabledSettings(); + // Get the existing primary providers since we don't touch them in + // this part of the UI we should just copy them over. + Set primaryServices = new HashSet<>(); + for (CredentialProviderInfo service : mServices) { + if (service.isPrimary()) { + primaryServices.add(service.getServiceInfo().getComponentName().flattenToString()); + } + } + mCredentialManager.setEnabledProviders( - enabledServices, + new ArrayList<>(primaryServices), + getEnabledSettings(), getUser(), mExecutor, new OutcomeReceiver() { @Override public void onResult(Void result) { Log.i(TAG, "setEnabledProviders success"); + updateFromExternal(); } @Override diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java index cfaf7a211db..793aa3c1cbe 100644 --- a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java @@ -47,7 +47,9 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.CandidateInfo; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class DefaultCombinedPicker extends DefaultAppPickerFragment { @@ -338,9 +340,9 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { return true; } - private void setProviders(String autofillProvider, List credManProviders) { + private void setProviders(String autofillProvider, List primaryCredManProviders) { if (TextUtils.isEmpty(autofillProvider)) { - if (credManProviders.size() > 0) { + if (primaryCredManProviders.size() > 0) { autofillProvider = CredentialManagerPreferenceController .AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER; @@ -350,12 +352,25 @@ public class DefaultCombinedPicker extends DefaultAppPickerFragment { Settings.Secure.putStringForUser( getContext().getContentResolver(), AUTOFILL_SETTING, autofillProvider, mUserId); - CredentialManager service = getCredentialProviderService(); + final CredentialManager service = getCredentialProviderService(); if (service == null) { return; } + // Get the existing secondary providers since we don't touch them in + // this part of the UI we should just copy them over. + final List credManProviders = new ArrayList<>(); + for (CredentialProviderInfo cpi : + service.getCredentialProviderServices( + mUserId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)) { + + if (cpi.isEnabled()) { + credManProviders.add(cpi.getServiceInfo().getComponentName().flattenToString()); + } + } + service.setEnabledProviders( + primaryCredManProviders, credManProviders, mUserId, ContextCompat.getMainExecutor(getContext()), diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java index ca049bcdd48..64d4f0dd5ed 100644 --- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java @@ -135,12 +135,12 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon /** Provides Intent to setting activity for the specified autofill service. */ static final class AutofillSettingIntentProvider { - private final String mSelectedKey; + private final String mKey; private final Context mContext; private final int mUserId; public AutofillSettingIntentProvider(Context context, int userId, String key) { - mSelectedKey = key; + mKey = key; mContext = context; mUserId = userId; } @@ -153,10 +153,9 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; - final String flattenKey = - new ComponentName(serviceInfo.packageName, serviceInfo.name) - .flattenToString(); - if (TextUtils.equals(mSelectedKey, flattenKey)) { + + // If there are multiple autofill services then pick the first one. + if (mKey.startsWith(serviceInfo.packageName)) { final String settingsActivity; try { settingsActivity = @@ -164,7 +163,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon .getSettingsActivity(); } catch (SecurityException e) { // Service does not declare the proper permission, ignore it. - Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e); + Log.e(TAG, "Error getting info for " + serviceInfo + ": " + e); return null; } if (TextUtils.isEmpty(settingsActivity)) { diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index 620cfb0586d..32ca2777392 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -72,6 +72,7 @@ public class RequestPermissionActivity extends Activity implements private int mRequest; private AlertDialog mDialog; + private AlertDialog mRequestDialog; private BroadcastReceiver mReceiver; @@ -96,33 +97,35 @@ public class RequestPermissionActivity extends Activity implements if (mRequest == REQUEST_DISABLE) { switch (btState) { case BluetoothAdapter.STATE_OFF: - case BluetoothAdapter.STATE_TURNING_OFF: { + case BluetoothAdapter.STATE_TURNING_OFF: proceedAndFinish(); - } break; - + break; case BluetoothAdapter.STATE_ON: - case BluetoothAdapter.STATE_TURNING_ON: { - RequestPermissionHelper.INSTANCE.requestDisable(this, mAppLabel, - () -> { - onDisableConfirmed(); - return Unit.INSTANCE; - }, - () -> { - cancelAndFinish(); - return Unit.INSTANCE; - }); - } break; - - default: { + case BluetoothAdapter.STATE_TURNING_ON: + mRequestDialog = + RequestPermissionHelper.INSTANCE.requestDisable(this, mAppLabel, + () -> { + onDisableConfirmed(); + return Unit.INSTANCE; + }, + () -> { + cancelAndFinish(); + return Unit.INSTANCE; + }); + if (mRequestDialog != null) { + mRequestDialog.show(); + } + break; + default: Log.e(TAG, "Unknown adapter state: " + btState); cancelAndFinish(); - } break; + break; } } else { switch (btState) { case BluetoothAdapter.STATE_OFF: case BluetoothAdapter.STATE_TURNING_OFF: - case BluetoothAdapter.STATE_TURNING_ON: { + case BluetoothAdapter.STATE_TURNING_ON: /* * Strictly speaking STATE_TURNING_ON belong with STATE_ON; * however, BT may not be ready when the user clicks yes and we @@ -131,20 +134,23 @@ public class RequestPermissionActivity extends Activity implements * case via the broadcast receiver. */ - // Start the helper activity to ask the user about enabling bt AND discovery - RequestPermissionHelper.INSTANCE.requestEnable(this, mAppLabel, - mRequest == REQUEST_ENABLE_DISCOVERABLE ? mTimeout : -1, - () -> { - onEnableConfirmed(); - return Unit.INSTANCE; - }, - () -> { - cancelAndFinish(); - return Unit.INSTANCE; - }); - } break; - - case BluetoothAdapter.STATE_ON: { + // Show the helper dialog to ask the user about enabling bt AND discovery + mRequestDialog = + RequestPermissionHelper.INSTANCE.requestEnable(this, mAppLabel, + mRequest == REQUEST_ENABLE_DISCOVERABLE ? mTimeout : -1, + () -> { + onEnableConfirmed(); + return Unit.INSTANCE; + }, + () -> { + cancelAndFinish(); + return Unit.INSTANCE; + }); + if (mRequestDialog != null) { + mRequestDialog.show(); + } + break; + case BluetoothAdapter.STATE_ON: if (mRequest == REQUEST_ENABLE) { // Nothing to do. Already enabled. proceedAndFinish(); @@ -152,12 +158,11 @@ public class RequestPermissionActivity extends Activity implements // Ask the user about enabling discovery mode createDialog(); } - } break; - - default: { + break; + default: Log.e(TAG, "Unknown adapter state: " + btState); cancelAndFinish(); - } break; + break; } } } @@ -275,10 +280,6 @@ public class RequestPermissionActivity extends Activity implements } } - if (mDialog != null) { - mDialog.dismiss(); - } - setResult(returnCode); finish(); } @@ -365,6 +366,14 @@ public class RequestPermissionActivity extends Activity implements unregisterReceiver(mReceiver); mReceiver = null; } + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + mDialog = null; + } + if (mRequestDialog != null && mRequestDialog.isShowing()) { + mRequestDialog.dismiss(); + mRequestDialog = null; + } } @Override diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelper.kt b/src/com/android/settings/bluetooth/RequestPermissionHelper.kt index 000a7d16295..73084e4371d 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionHelper.kt +++ b/src/com/android/settings/bluetooth/RequestPermissionHelper.kt @@ -30,20 +30,20 @@ object RequestPermissionHelper { timeout: Int, onAllow: () -> Unit, onDeny: () -> Unit, - ) { + ): AlertDialog? { if (context.resources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) { // Don't even show the dialog if configured this way onAllow() - return + return null } - AlertDialog.Builder(context).apply { + return AlertDialog.Builder(context).apply { setMessage(context.getEnableMessage(timeout, appLabel)) setPositiveButton(R.string.allow) { _, _ -> if (context.isDisallowBluetooth()) onDeny() else onAllow() } setNegativeButton(R.string.deny) { _, _ -> onDeny() } setOnCancelListener { onDeny() } - }.show() + }.create() } fun requestDisable( @@ -51,18 +51,18 @@ object RequestPermissionHelper { appLabel: CharSequence?, onAllow: () -> Unit, onDeny: () -> Unit, - ) { + ): AlertDialog? { if (context.resources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) { // Don't even show the dialog if configured this way onAllow() - return + return null } - AlertDialog.Builder(context).apply { + return AlertDialog.Builder(context).apply { setMessage(context.getDisableMessage(appLabel)) setPositiveButton(R.string.allow) { _, _ -> onAllow() } setNegativeButton(R.string.deny) { _, _ -> onDeny() } setOnCancelListener { onDeny() } - }.show() + }.create() } } diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java index 947c5acc83b..1187c59e8d7 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusDeviceUpdater.java @@ -165,8 +165,7 @@ public class StylusDeviceUpdater implements InputManager.InputDeviceListener, } mUsiPreference.setKey(PREF_KEY); mUsiPreference.setTitle(R.string.stylus_connected_devices_title); - // TODO(b/250909304): pending actual icon visD - mUsiPreference.setIcon(R.drawable.ic_edit); + mUsiPreference.setIcon(R.drawable.ic_stylus); mUsiPreference.setOnPreferenceClickListener((Preference p) -> { mMetricsFeatureProvider.logClickedPreference(p, mFragment.getMetricsCategory()); launchDeviceDetails(); diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java index 379815b0442..23db3cb1052 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java @@ -70,8 +70,7 @@ public class StylusUsiHeaderController extends BasePreferenceController implemen ImageView iconView = mHeaderPreference.findViewById(R.id.entity_header_icon); if (iconView != null) { - // TODO(b/250909304): get proper icon once VisD ready - iconView.setImageResource(R.drawable.ic_edit); + iconView.setImageResource(R.drawable.ic_stylus); iconView.setContentDescription("Icon for stylus"); } refresh(); diff --git a/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java index 0bbfb98ebeb..10d30139b6c 100644 --- a/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java +++ b/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java @@ -30,12 +30,12 @@ public class TrackpadReverseScrollingPreferenceController extends TogglePreferen @Override public boolean isChecked() { - return InputSettings.useTouchpadNaturalScrolling(mContext); + return !InputSettings.useTouchpadNaturalScrolling(mContext); } @Override public boolean setChecked(boolean isChecked) { - InputSettings.setTouchpadNaturalScrolling(mContext, isChecked); + InputSettings.setTouchpadNaturalScrolling(mContext, !isChecked); return true; } diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt index 27f7241b257..2b52b21af0c 100644 --- a/src/com/android/settings/spa/SpaActivity.kt +++ b/src/com/android/settings/spa/SpaActivity.kt @@ -22,14 +22,34 @@ import android.content.Intent import android.os.RemoteException import android.os.UserHandle import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.util.SESSION_BROWSE import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams +import com.google.android.setupcompat.util.WizardManagerHelper class SpaActivity : BrowseActivity() { + override fun isPageEnabled(page: SettingsPage) = + super.isPageEnabled(page) && !isSuwAndPageBlocked(page.sppName) + companion object { private const val TAG = "SpaActivity" + + /** The pages that blocked from SUW. */ + private val SuwBlockedPages = setOf(AppInfoSettingsProvider.name) + + @VisibleForTesting + fun Context.isSuwAndPageBlocked(name: String): Boolean = + if (name in SuwBlockedPages && !WizardManagerHelper.isDeviceProvisioned(this)) { + Log.w(TAG, "$name blocked before SUW completed."); + true + } else { + false + } + @JvmStatic fun Context.startSpaActivity(destination: String) { val intent = Intent(this, SpaActivity::class.java) diff --git a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt index 9b363358fc8..527c6d9bb96 100644 --- a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt +++ b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt @@ -19,12 +19,14 @@ package com.android.settings.spa.app.specialaccess import android.Manifest import android.app.AlarmManager import android.app.compat.CompatChanges +import android.app.settings.SettingsEnums import android.content.Context import android.content.pm.ApplicationInfo import android.os.PowerExemptionManager import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import com.android.settings.R +import com.android.settings.overlay.FeatureFactory import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IPackageManagers @@ -85,6 +87,17 @@ class AlarmsAndRemindersAppListModel( override fun setAllowed(record: AlarmsAndRemindersAppRecord, newAllowed: Boolean) { record.controller.setAllowed(newAllowed) + logPermissionChange(newAllowed) + } + + private fun logPermissionChange(newAllowed: Boolean) { + FeatureFactory.getFactory(context).metricsFeatureProvider.action( + SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_ALARMS_AND_REMINDERS_TOGGLE, + SettingsEnums.ALARMS_AND_REMINDERS, + "", + if (newAllowed) 1 else 0 + ) } private fun createRecord( diff --git a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt index 6466e038006..16520fad635 100644 --- a/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt +++ b/src/com/android/settings/spa/app/specialaccess/AllFilesAccess.kt @@ -18,9 +18,12 @@ package com.android.settings.spa.app.specialaccess import android.Manifest import android.app.AppOpsManager +import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R +import com.android.settings.overlay.FeatureFactory import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider object AllFilesAccessAppListProvider : TogglePermissionAppListProvider { @@ -35,4 +38,17 @@ class AllFilesAccessListModel(context: Context) : AppOpPermissionListModel(conte override val appOp = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE override val permission = Manifest.permission.MANAGE_EXTERNAL_STORAGE override val setModeByUid = true + + override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { + super.setAllowed(record, newAllowed) + logPermissionChange(newAllowed) + } + + private fun logPermissionChange(newAllowed: Boolean) { + val category = when { + newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW + else -> SettingsEnums.APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY + } + FeatureFactory.getFactory(context).metricsFeatureProvider.action(context, category, "") + } } diff --git a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt index d3cd2b506e9..7812675dbe9 100644 --- a/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/DisplayOverOtherApps.kt @@ -18,9 +18,12 @@ package com.android.settings.spa.app.specialaccess import android.Manifest import android.app.AppOpsManager +import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R +import com.android.settings.overlay.FeatureFactory import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider object DisplayOverOtherAppsAppListProvider : TogglePermissionAppListProvider { @@ -34,4 +37,17 @@ class DisplayOverOtherAppsListModel(context: Context) : AppOpPermissionListModel override val footerResId = R.string.allow_overlay_description override val appOp = AppOpsManager.OP_SYSTEM_ALERT_WINDOW override val permission = Manifest.permission.SYSTEM_ALERT_WINDOW + + override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { + super.setAllowed(record, newAllowed) + logPermissionChange(newAllowed) + } + + private fun logPermissionChange(newAllowed: Boolean) { + val category = when { + newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW + else -> SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_DENY + } + FeatureFactory.getFactory(context).metricsFeatureProvider.action(context, category, "") + } } diff --git a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt index 6c7678a0629..e8935e64819 100644 --- a/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/MediaManagementApps.kt @@ -18,9 +18,12 @@ package com.android.settings.spa.app.specialaccess import android.Manifest import android.app.AppOpsManager +import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R +import com.android.settings.overlay.FeatureFactory import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider object MediaManagementAppsAppListProvider : TogglePermissionAppListProvider { @@ -35,4 +38,19 @@ class MediaManagementAppsListModel(context: Context) : AppOpPermissionListModel( override val appOp = AppOpsManager.OP_MANAGE_MEDIA override val permission = Manifest.permission.MANAGE_MEDIA override val setModeByUid = true + + override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { + super.setAllowed(record, newAllowed) + logPermissionChange(newAllowed) + } + + private fun logPermissionChange(newAllowed: Boolean) { + FeatureFactory.getFactory(context).metricsFeatureProvider.action( + SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_MEDIA_MANAGEMENT_APPS_TOGGLE, + SettingsEnums.MEDIA_MANAGEMENT_APPS, + "", + if (newAllowed) 1 else 0 + ) + } } \ No newline at end of file diff --git a/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt b/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt index 9a70871b1e2..668cc8cc129 100644 --- a/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt +++ b/src/com/android/settings/spa/app/specialaccess/ModifySystemSettings.kt @@ -18,9 +18,12 @@ package com.android.settings.spa.app.specialaccess import android.Manifest import android.app.AppOpsManager +import android.app.settings.SettingsEnums import android.content.Context import com.android.settings.R +import com.android.settings.overlay.FeatureFactory import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel +import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider object ModifySystemSettingsAppListProvider : TogglePermissionAppListProvider { @@ -34,4 +37,17 @@ class ModifySystemSettingsListModel(context: Context) : AppOpPermissionListModel override val footerResId = R.string.write_settings_description override val appOp = AppOpsManager.OP_WRITE_SETTINGS override val permission = Manifest.permission.WRITE_SETTINGS + + override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { + super.setAllowed(record, newAllowed) + logPermissionChange(newAllowed) + } + + private fun logPermissionChange(newAllowed: Boolean) { + val category = when { + newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW + else -> SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY + } + FeatureFactory.getFactory(context).metricsFeatureProvider.action(context, category, "") + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionActivityTest.kt b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionActivityTest.kt new file mode 100644 index 00000000000..13cbb350feb --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionActivityTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.bluetooth + +import android.bluetooth.BluetoothAdapter +import android.content.Intent +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.android.controller.ActivityController +import org.robolectric.annotation.Config +import org.robolectric.shadow.api.Shadow +import org.robolectric.shadows.ShadowBluetoothAdapter + +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowAlertDialogCompat::class, ShadowBluetoothAdapter::class]) +class RequestPermissionActivityTest { + private lateinit var activityController: ActivityController + private lateinit var bluetoothAdapter: ShadowBluetoothAdapter + + @Before + fun setUp() { + bluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()) + } + + @After + fun tearDown() { + activityController.pause().stop().destroy() + ShadowAlertDialogCompat.reset() + } + + @Test + fun requestEnable_whenBluetoothIsOff_showConfirmDialog() { + bluetoothAdapter.setState(BluetoothAdapter.STATE_OFF) + + createActivity(action = BluetoothAdapter.ACTION_REQUEST_ENABLE) + + val dialog = ShadowAlertDialogCompat.getLatestAlertDialog() + val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog) + assertThat(shadowDialog.message.toString()) + .isEqualTo("An app wants to turn on Bluetooth") + } + + @Test + fun requestEnable_whenBluetoothIsOn_doNothing() { + bluetoothAdapter.setState(BluetoothAdapter.STATE_ON) + + createActivity(action = BluetoothAdapter.ACTION_REQUEST_ENABLE) + + val dialog = ShadowAlertDialogCompat.getLatestAlertDialog() + assertThat(dialog).isNull() + } + + @Test + fun requestDisable_whenBluetoothIsOff_doNothing() { + bluetoothAdapter.setState(BluetoothAdapter.STATE_OFF) + + createActivity(action = BluetoothAdapter.ACTION_REQUEST_DISABLE) + + val dialog = ShadowAlertDialogCompat.getLatestAlertDialog() + assertThat(dialog).isNull() + } + + @Test + fun requestDisable_whenBluetoothIsOn_showConfirmDialog() { + bluetoothAdapter.setState(BluetoothAdapter.STATE_ON) + + createActivity(action = BluetoothAdapter.ACTION_REQUEST_DISABLE) + + val dialog = ShadowAlertDialogCompat.getLatestAlertDialog() + val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog) + assertThat(shadowDialog.message.toString()) + .isEqualTo("An app wants to turn off Bluetooth") + } + + private fun createActivity(action: String) { + activityController = + ActivityController.of(RequestPermissionActivity(), Intent(action)).apply { + create() + start() + postCreate(null) + resume() + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt index ce0792c468a..e51a2f92919 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt +++ b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt @@ -49,13 +49,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withAppLabelAndNoTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = "App Label", timeout = -1, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs("App Label wants to turn on Bluetooth") } @@ -63,13 +64,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withAppLabelAndZeroTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = "App Label", timeout = 0, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs( "App Label wants to turn on Bluetooth and make your phone visible to other devices. " + @@ -80,13 +82,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withAppLabelAndNormalTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = "App Label", timeout = 120, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs( "App Label wants to turn on Bluetooth and make your phone visible to other devices " + @@ -97,13 +100,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withNoAppLabelAndNoTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = null, timeout = -1, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs("An app wants to turn on Bluetooth") } @@ -111,13 +115,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withNoAppLabelAndZeroTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = null, timeout = 0, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs( "An app wants to turn on Bluetooth and make your phone visible to other devices. " + @@ -128,13 +133,14 @@ class RequestPermissionHelperTest { @Test fun requestEnable_withNoAppLabelAndNormalTimeout_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestEnable( context = activity, appLabel = null, timeout = 120, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs( "An app wants to turn on Bluetooth and make your phone visible to other devices for " + @@ -177,12 +183,13 @@ class RequestPermissionHelperTest { @Test fun requestDisable_withAppLabel_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestDisable( context = activity, appLabel = "App Label", onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs("App Label wants to turn off Bluetooth") } @@ -190,12 +197,13 @@ class RequestPermissionHelperTest { @Test fun requestDisable_withNoAppLabel_hasCorrectMessage() { val activity = activityController.get() + RequestPermissionHelper.requestDisable( context = activity, appLabel = null, onAllow = {}, onDeny = {}, - ) + )!!.show() assertLatestMessageIs("An app wants to turn off Bluetooth") } diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java index b4cb862e6c6..a99abb8d38f 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java @@ -61,22 +61,9 @@ public class TrackpadReverseScrollingPreferenceControllerTest { } @Test - public void setChecked_true_shouldReturn1() { + public void setChecked_true_shouldReturn0() { mController.setChecked(true); - int result = Settings.System.getIntForUser( - mContext.getContentResolver(), - SETTING_KEY, - 0, - UserHandle.USER_CURRENT); - - assertThat(result).isEqualTo(1); - } - - @Test - public void setChecked_false_shouldReturn0() { - mController.setChecked(false); - int result = Settings.System.getIntForUser( mContext.getContentResolver(), SETTING_KEY, @@ -87,7 +74,20 @@ public class TrackpadReverseScrollingPreferenceControllerTest { } @Test - public void isChecked_providerPutInt1_returnTrue() { + public void setChecked_false_shouldReturn1() { + mController.setChecked(false); + + int result = Settings.System.getIntForUser( + mContext.getContentResolver(), + SETTING_KEY, + 0, + UserHandle.USER_CURRENT); + + assertThat(result).isEqualTo(1); + } + + @Test + public void isChecked_providerPutInt1_returnFalse() { Settings.System.putIntForUser( mContext.getContentResolver(), SETTING_KEY, @@ -96,11 +96,11 @@ public class TrackpadReverseScrollingPreferenceControllerTest { boolean result = mController.isChecked(); - assertThat(result).isTrue(); + assertThat(result).isFalse(); } @Test - public void isChecked_providerPutInt0_returnFalse() { + public void isChecked_providerPutInt0_returnTrue() { Settings.System.putIntForUser( mContext.getContentResolver(), SETTING_KEY, @@ -109,6 +109,6 @@ public class TrackpadReverseScrollingPreferenceControllerTest { boolean result = mController.isChecked(); - assertThat(result).isFalse(); + assertThat(result).isTrue(); } } diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt index 46b956e6ccc..1b2a7b1ee90 100644 --- a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt @@ -21,33 +21,71 @@ import android.content.Intent import android.net.Uri import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.settings.spa.SpaActivity.Companion.isSuwAndPageBlocked import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp +import com.android.settings.spa.app.AllAppListPageProvider +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settingslib.spa.framework.util.KEY_DESTINATION +import com.google.android.setupcompat.util.WizardManagerHelper import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule +import org.mockito.MockitoSession +import org.mockito.quality.Strictness +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidJUnit4::class) class SpaActivityTest { - @get:Rule - val mockito: MockitoRule = MockitoJUnit.rule() + private lateinit var mockSession: MockitoSession - @Mock(answer = Answers.RETURNS_DEEP_STUBS) + @Mock private lateinit var context: Context @Before fun setUp() { - `when`(context.applicationContext.packageName).thenReturn("com.android.settings") + mockSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(WizardManagerHelper::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(context.applicationContext).thenReturn(context) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun isSuwAndPageBlocked_regularPage_notBlocked() { + val isBlocked = context.isSuwAndPageBlocked(AllAppListPageProvider.name) + + assertThat(isBlocked).isFalse() + } + + @Test + fun isSuwAndPageBlocked_blocklistedPageInSuw_blocked() { + whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(false) + + val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) + + assertThat(isBlocked).isTrue() + } + + @Test + fun isSuwAndPageBlocked_blocklistedPageNotInSuw_notBlocked() { + whenever(WizardManagerHelper.isDeviceProvisioned(context)).thenReturn(true) + + val isBlocked = context.isSuwAndPageBlocked(AppInfoSettingsProvider.name) + + assertThat(isBlocked).isFalse() } @Test