diff options
59 files changed, 1999 insertions, 911 deletions
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml index 3ff6c8a87..8dd4636b2 100644 --- a/PermissionController/AndroidManifest.xml +++ b/PermissionController/AndroidManifest.xml @@ -44,7 +44,8 @@ <uses-permission android:name="android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" + android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- TODO(b/170896938): make this privileged(signature may only work on pixel) --> @@ -58,6 +59,8 @@ <uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" /> <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" /> <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> + <!--SYSTEM_APPLICATION_OVERLAY will be granted on T+, as installer protection is added in T --> + <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"/> <application android:name="com.android.permissioncontroller.PermissionControllerApplication" android:label="@string/app_name" diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 67e5d0de1..850b4ecb7 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -1427,15 +1427,15 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo <string name="permgrouprequest_sensors">Allow <b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to access sensor data about your vital signs?</string> - <!-- Subtitle of the message shown to the user when the apps requests permission to use the body sensors while app is in foreground and background. Try to keep the link annotation at the end of the string [CHAR LIMIT=150] --> - <string name="permgroupupgraderequestdetail_sensors">This app wants to access sensor data about your vital signs all the time, even when you\u2019re not using the app. <annotation id="link">Allow in settings.</annotation></string> + <!-- Subtitle of the message shown to the user when the apps requests permission to use the body sensors while app is in foreground and background. Try to keep the link annotation at the end of the string [CHAR LIMIT=NONE] --> + <string name="permgroupupgraderequestdetail_sensors">This app wants to access sensor data about your vital signs all the time, even when you\u2019re not using the app. To make this change, <annotation id="link">go to settings</annotation> or keep access for only while the app is in use.</string> <!-- Message shown to the user when the apps requests permission to use the bosy sensors while app is in foreground and background. If ever possible this should stay below 80 characters (assuming the parameters takes 20 characters). Don't abbreviate until the message reaches 120 characters though. [CHAR LIMIT=120] --> <string name="permgroupbackgroundrequest_sensors">Allow <b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to access the sensor data about your vital signs?</string> - <!-- Subtitle of the message shown to the user when the apps requests permission to use the body sensors while app is in foreground and background. Try to keep the link annotation at the end of the string [CHAR LIMIT=150] --> - <string name="permgroupbackgroundrequestdetail_sensors">This app may want to access the sensor data about your vital signs all the time, even when you\u2019re not using the app. <annotation id="link">Allow in settings.</annotation></string> + <!-- Subtitle of the message shown to the user when the apps requests permission to use the body sensors while app is in foreground and background. Try to keep the link annotation at the end of the string [CHAR LIMIT=NONE] --> + <string name="permgroupbackgroundrequestdetail_sensors">To let this app access body sensor data all the time, even when you\u2019rere not using the app, <annotation id="link">go to settings.</annotation></string> <!-- Message shown to the user when the apps requests permission to use the body sensors while app is in foreground and background. If ever possible this should stay below 80 characters (assuming the parameters takes 20 characters). Don't abbreviate until the message reaches 120 characters though. [CHAR LIMIT=120] --> - <string name="permgroupupgraderequest_sensors">Change access to sensor data about your vital signs for <b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b>?</string> + <string name="permgroupupgraderequest_sensors">Keep allowing <b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to access body sensor data while app is in use?</string> <!-- Message shown to the user when the apps requests permission from this group. If ever possible this should stay below 80 characters (assuming the parameters takes 20 characters). Don't abbreviate until the message reaches 120 characters though. [CHAR LIMIT=120] --> <string name="permgrouprequest_notifications">Allow diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt index fca6f1f6a..15693d4e2 100644 --- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt +++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt @@ -429,8 +429,8 @@ suspend fun isPackageHibernationExemptBySystem( return true } + val context = PermissionControllerApplication.get() if (SdkLevel.isAtLeastS()) { - val context = PermissionControllerApplication.get() val hasInstallOrUpdatePermissions = context.checkPermission( Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, pkg.uid) == @@ -464,6 +464,17 @@ suspend fun isPackageHibernationExemptBySystem( } } + if (SdkLevel.isAtLeastT()) { + val roleHolders = context.getSystemService(android.app.role.RoleManager::class.java)!! + .getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT) + if (roleHolders.contains(pkg.packageName)) { + if (DEBUG_HIBERNATION_POLICY) { + DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - device policy manager app") + } + return true + } + } + return false } diff --git a/PermissionController/src/com/android/permissioncontroller/incident/ConfirmationActivity.java b/PermissionController/src/com/android/permissioncontroller/incident/ConfirmationActivity.java index cd9b9a5ba..b5beb9922 100644 --- a/PermissionController/src/com/android/permissioncontroller/incident/ConfirmationActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/incident/ConfirmationActivity.java @@ -38,6 +38,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import java.util.ArrayList; @@ -197,6 +198,11 @@ public class ConfirmationActivity extends Activity implements OnClickListener, O .create(); if (Settings.canDrawOverlays(this)) { final Window w = dialog.getWindow(); + if (SdkLevel.isAtLeastT()) { + WindowManager.LayoutParams lpm = new WindowManager.LayoutParams(); + lpm.setSystemApplicationOverlay(true); + w.setAttributes(lpm); + } w.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } dialog.show(); diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt index da0f26000..f0d811821 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt @@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.data import android.content.Intent import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE +import android.content.pm.PackageManager.FEATURE_LEANBACK import com.android.permissioncontroller.PermissionControllerApplication import kotlinx.coroutines.Job @@ -31,14 +32,26 @@ object LauncherPackagesLiveData : SmartAsyncMediatorLiveData<Set<String>>(), private val LAUNCHER_INTENT = Intent(Intent.ACTION_MAIN, null) .addCategory(Intent.CATEGORY_LAUNCHER) + // On ATV some apps may have a leanback launcher icon but no regular launcher icon + private val LEANBACK_LAUNCHER_INTENT = Intent(Intent.ACTION_MAIN, null) + .addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER) + override suspend fun loadDataAndPostValue(job: Job) { val launcherPkgs = mutableSetOf<String>() + + loadPkgsFromIntent(launcherPkgs, LAUNCHER_INTENT) + if (PermissionControllerApplication.get().packageManager + .hasSystemFeature(FEATURE_LEANBACK)) { + loadPkgsFromIntent(launcherPkgs, LEANBACK_LAUNCHER_INTENT) + } + postValue(launcherPkgs) + } + + private fun loadPkgsFromIntent(launcherPkgs: MutableSet<String>, intent: Intent) { for (info in PermissionControllerApplication.get().packageManager.queryIntentActivities( - LAUNCHER_INTENT, MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)) { + intent, MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)) { launcherPkgs.add(info.activityInfo.packageName) } - - postValue(launcherPkgs) } override fun onPackageUpdate(packageName: String) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java index ecf9c00ea..d83714422 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java @@ -66,6 +66,7 @@ import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFra import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionDetailsWrapperFragment; import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionUsageV2WrapperFragment; import com.android.permissioncontroller.permission.ui.legacy.AppPermissionActivity; +import com.android.permissioncontroller.permission.ui.television.TvUnusedAppsFragment; import com.android.permissioncontroller.permission.ui.wear.AppPermissionsFragmentWear; import com.android.permissioncontroller.permission.utils.KotlinUtils; import com.android.permissioncontroller.permission.utils.Utils; @@ -375,7 +376,10 @@ public final class ManagePermissionsActivity extends SettingsActivity { if (DeviceUtils.isAuto(this)) { androidXFragment = AutoUnusedAppsFragment.newInstance(); androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); - } else if (DeviceUtils.isWear(this) || DeviceUtils.isTelevision(this)) { + } else if (DeviceUtils.isTelevision(this)) { + androidXFragment = TvUnusedAppsFragment.newInstance(); + androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); + } else if (DeviceUtils.isWear(this)) { androidXFragment = HandheldUnusedAppsWrapperFragment.newInstance(); androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); } else { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java index 020bf9a31..d5ddd8c9a 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java @@ -262,11 +262,28 @@ public final class PermissionAppsFragment extends SettingsWithLargeHeader implem CardViewPreference sensorCard = findPreference(BLOCKED_SENSOR_PREF_KEY); if (sensorCard == null) { sensorCard = createSensorCard(); + ensurePreferenceScreen(); getPreferenceScreen().addPreference(sensorCard); } sensorCard.setVisible(true); } + private void ensurePreferenceScreen() { + // Check if preference screen has been already loaded + if (getPreferenceScreen() != null) { + return; + } + boolean isStorageAndLessThanT = !SdkLevel.isAtLeastT() + && mPermGroupName.equals(Manifest.permission_group.STORAGE); + if (isStorageAndLessThanT) { + addPreferencesFromResource(R.xml.allowed_denied_storage); + } else { + addPreferencesFromResource(R.xml.allowed_denied); + } + // Hide allowed foreground label by default, to avoid briefly showing it before updating + findPreference(ALLOWED_FOREGROUND.getCategoryName()).setVisible(false); + } + @RequiresApi(Build.VERSION_CODES.S) private CardViewPreference createSensorCard() { boolean isLocation = Manifest.permission_group.LOCATION.equals(mPermGroupName); @@ -354,15 +371,7 @@ public final class PermissionAppsFragment extends SettingsWithLargeHeader implem private void onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories) { boolean isStorageAndLessThanT = !SdkLevel.isAtLeastT() && mPermGroupName.equals(Manifest.permission_group.STORAGE); - if (getPreferenceScreen() == null) { - if (isStorageAndLessThanT) { - addPreferencesFromResource(R.xml.allowed_denied_storage); - } else { - addPreferencesFromResource(R.xml.allowed_denied); - } - // Hide allowed foreground label by default, to avoid briefly showing it before updating - findPreference(ALLOWED_FOREGROUND.getCategoryName()).setVisible(false); - } + ensurePreferenceScreen(); Context context = getPreferenceManager().getContext(); if (context == null || getActivity() == null || categories == null) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt new file mode 100644 index 000000000..6caaa7420 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 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.permissioncontroller.permission.ui.television + +import android.app.Application +import android.os.Bundle +import android.os.UserHandle +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import com.android.permissioncontroller.R +import com.android.permissioncontroller.hibernation.isHibernationEnabled +import com.android.permissioncontroller.permission.ui.UnusedAppsFragment +import com.android.permissioncontroller.permission.ui.UnusedAppsFragment.Companion.INFO_MSG_CATEGORY + +/** + * TV wrapper, with customizations, around [UnusedAppsFragment]. + */ +class TvUnusedAppsFragment : SettingsWithHeader(), + UnusedAppsFragment.Parent<TvUnusedAppsPreference> { + + companion object { + private const val UNUSED_PREFERENCE_KEY = "unused_pref_row_key" + + /** Create a new instance of this fragment. */ + @JvmStatic + fun newInstance(): TvUnusedAppsFragment { + return TvUnusedAppsFragment() + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + // Preferences will be added via shared logic in [UnusedAppsFragment]. + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + if (savedInstanceState == null) { + val fragment: + UnusedAppsFragment<TvUnusedAppsFragment, TvUnusedAppsPreference> = + UnusedAppsFragment.newInstance() + fragment.arguments = arguments + // child fragment does not have its own UI - it will add to the preferences of this + // parent fragment + childFragmentManager.beginTransaction() + .add(fragment, null) + .commit() + } + } + + override fun createFooterPreference(): Preference { + val preference = Preference(context) + if (isHibernationEnabled()) { + preference.summary = getString(R.string.unused_apps_page_summary) + } else { + preference.summary = + getString(R.string.auto_revoked_apps_page_summary) + } + preference.setIcon(R.drawable.ic_info_outline) + preference.isSelectable = false + return preference + } + + override fun setLoadingState(loading: Boolean, animate: Boolean) { + setLoading(loading, animate) + } + + override fun createUnusedAppPref( + app: Application, + packageName: String, + user: UserHandle + ): TvUnusedAppsPreference { + return TvUnusedAppsPreference(app, packageName, user, requireContext()) + } + + override fun setTitle(title: CharSequence) { + setHeader(null, null, null, title) + } + + override fun setEmptyState(empty: Boolean) { + val infoMsgCategory = + preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)!! + val noUnusedAppsPreference: Preference? = + infoMsgCategory.findPreference<Preference>(UNUSED_PREFERENCE_KEY) + if (empty && noUnusedAppsPreference == null) { + infoMsgCategory.addPreference(createNoUnusedAppsPreference()) + } else if (noUnusedAppsPreference != null) { + noUnusedAppsPreference.setVisible(empty) + } + } + + private fun createNoUnusedAppsPreference(): Preference { + val preference = Preference(context) + preference.title = getString(R.string.zero_unused_apps) + preference.key = UNUSED_PREFERENCE_KEY + preference.isSelectable = false + preference.order = 0 + return preference + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt new file mode 100644 index 000000000..23f4a4589 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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.permissioncontroller.permission.ui.television + +import androidx.preference.Preference +import android.app.Application +import android.content.Context +import android.os.UserHandle +import com.android.permissioncontroller.permission.ui.RemovablePref +import com.android.permissioncontroller.permission.utils.KotlinUtils + +/** + * A TV-styled preference which represents an app that has been auto revoked. Has the app icon and + * label, as well as a button to open the app. + * + * @param app The current application + * @param packageName The name of the package whose icon this preference will retrieve + * @param user The user whose package icon will be retrieved + * @param context The current context + */ +class TvUnusedAppsPreference( + app: Application, + packageName: String, + user: UserHandle, + context: Context +) : Preference(context), RemovablePref { + + init { + icon = KotlinUtils.getBadgedPackageIcon(app, packageName, user) + } + + override fun setRemoveClickRunnable(runnable: Runnable) { + // TV Settings don't have secondary icons and actions + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index 1c4fa2ce7..e3715f74b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -288,7 +288,7 @@ public final class Utils { if (SdkLevel.isAtLeastT()) { PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_AUDIO, READ_MEDIA_AURAL); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGE, READ_MEDIA_VISUAL); + PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGES, READ_MEDIA_VISUAL); PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_VIDEO, READ_MEDIA_VISUAL); } diff --git a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md index 975840aaa..f632332f3 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md +++ b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md @@ -93,8 +93,8 @@ overlay. Since role is modularized, we also need to make this config resource a system API for access by role. -Edit `frameworks/base/core/res/res/values/public.xml` to expose the new config resource as a system -API: +Edit `frameworks/base/core/res/res/values/public-staging.xml` to expose the new config resource as +a system API: ```xml <staging-public-group type="string" first-id="0xXXXXXXXX"> diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java index 0cbc203c0..f10b80467 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java +++ b/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.UserHandle; @@ -204,13 +205,41 @@ public class HomeRoleBehavior implements RoleBehavior { Permissions.grant(packageName, AUTOMOTIVE_PERMISSIONS, true, false, true, false, false, context); } + + // Before T, ALLOW_SLIPPERY_TOUCHES may either not exist, or may not be a role permission + if (isRolePermission(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, context)) { + Permissions.grant(packageName, + Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES), + true, false, true, false, false, context); + } } @Override - public void revoke(@NonNull Role role, @NonNull String packageName, - @NonNull Context context) { + public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { Permissions.revoke(packageName, AUTOMOTIVE_PERMISSIONS, true, false, false, context); } + + // Before T, ALLOW_SLIPPERY_TOUCHES may either not exist, or may not be a role permission + if (isRolePermission(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, context)) { + Permissions.revoke(packageName, + Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES), + true, false, false, context); + } + } + + /** + * Return true if the permission exists, and has 'role' protection level. + * Return false otherwise. + */ + private boolean isRolePermission(@NonNull String permissionName, @NonNull Context context) { + PermissionInfo permissionInfo; + try { + permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + final int flags = permissionInfo.getProtectionFlags(); + return (flags & PermissionInfo.PROTECTION_FLAG_ROLE) == PermissionInfo.PROTECTION_FLAG_ROLE; } } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java index 4b947aa26..64c52d1f0 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java @@ -15,7 +15,13 @@ */ package com.android.permissioncontroller.safetycenter.ui; +import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT; + +import android.content.Intent; import android.os.Bundle; +import android.provider.Settings; +import android.safetycenter.SafetyCenterManager; +import android.util.Log; import androidx.annotation.Keep; @@ -28,9 +34,21 @@ import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; @Keep public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { + private static final String TAG = SafetyCenterActivity.class.getSimpleName(); + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + SafetyCenterManager safetyCenterManager = getSystemService(SafetyCenterManager.class); + + if (safetyCenterManager == null || !safetyCenterManager.isSafetyCenterEnabled()) { + Log.w(TAG, "Safety Center disabled, redirecting to security settings page"); + startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS).addFlags( + FLAG_ACTIVITY_FORWARD_RESULT)); + finish(); + return; + } + setTitle(getString(R.string.safety_center_dashboard_page_title)); getSupportFragmentManager() .beginTransaction() diff --git a/SafetyCenter/Config/safety_center_config.xsd b/SafetyCenter/Config/safety_center_config.xsd index 77909d341..f4adc4721 100644 --- a/SafetyCenter/Config/safety_center_config.xsd +++ b/SafetyCenter/Config/safety_center_config.xsd @@ -72,6 +72,7 @@ <xsd:attribute name="broadcastReceiverClassName" type="xsd:string"/> <xsd:attribute name="loggingAllowed" type="xsd:boolean" default="true"/> <xsd:attribute name="refreshOnPageOpenAllowed" type="xsd:boolean" default="false"/> + <xsd:attribute name="automaticNotificationFromIssueAllowed" type="xsd:boolean" default="false"/> </xsd:complexType> <xsd:complexType name="issue-only-safety-source"> @@ -83,6 +84,7 @@ <xsd:attribute name="broadcastReceiverClassName" type="xsd:string"/> <xsd:attribute name="loggingAllowed" type="xsd:boolean" default="true"/> <xsd:attribute name="refreshOnPageOpenAllowed" type="xsd:boolean" default="false"/> + <xsd:attribute name="automaticNotificationFromIssueAllowed" type="xsd:boolean" default="false"/> </xsd:complexType> <xsd:complexType name="static-safety-source"> diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index 23de4e414..2a28a61c8 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -138,12 +138,12 @@ package android.safetycenter { field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterEntryOrGroup> CREATOR; } - public final class SafetyCenterError implements android.os.Parcelable { - ctor public SafetyCenterError(@NonNull CharSequence); + public final class SafetyCenterErrorDetails implements android.os.Parcelable { + ctor public SafetyCenterErrorDetails(@NonNull CharSequence); method public int describeContents(); method @NonNull public CharSequence getErrorMessage(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterError> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterErrorDetails> CREATOR; } public final class SafetyCenterIssue implements android.os.Parcelable { @@ -170,7 +170,7 @@ package android.safetycenter { method @NonNull public android.app.PendingIntent getPendingIntent(); method @Nullable public CharSequence getSuccessMessage(); method public boolean isInFlight(); - method public boolean isResolving(); + method public boolean willResolve(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterIssue.Action> CREATOR; } @@ -178,11 +178,11 @@ package android.safetycenter { public static final class SafetyCenterIssue.Action.Builder { ctor public SafetyCenterIssue.Action.Builder(@NonNull String); method @NonNull public android.safetycenter.SafetyCenterIssue.Action build(); - method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setInFlight(boolean); + method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setIsInFlight(boolean); method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setLabel(@NonNull CharSequence); method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setPendingIntent(@NonNull android.app.PendingIntent); - method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setResolving(boolean); method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setSuccessMessage(@Nullable CharSequence); + method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setWillResolve(boolean); } public static final class SafetyCenterIssue.Builder { @@ -203,14 +203,14 @@ package android.safetycenter { method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void addOnSafetyCenterDataChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener); method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void clearAllSafetySourceData(); method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void clearSafetyCenterConfigOverride(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void dismissSafetyIssue(@NonNull String); - method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void executeAction(@NonNull String, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void dismissSafetyCenterIssue(@NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void executeSafetyCenterIssueAction(@NonNull String, @NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public android.safetycenter.SafetyCenterData getSafetyCenterData(); method @Nullable @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public android.safetycenter.SafetySourceData getSafetySourceData(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.READ_SAFETY_CENTER_STATUS, android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE}) public boolean isSafetyCenterEnabled(); method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void refreshSafetySources(int); method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void removeOnSafetyCenterDataChangedListener(@NonNull android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener); - method @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public void reportSafetySourceError(@NonNull String, @NonNull android.safetycenter.SafetySourceError); + method @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public void reportSafetySourceError(@NonNull String, @NonNull android.safetycenter.SafetySourceErrorDetails); method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void setSafetyCenterConfigOverride(@NonNull android.safetycenter.config.SafetyCenterConfig); method @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public void setSafetySourceData(@NonNull String, @Nullable android.safetycenter.SafetySourceData, @NonNull android.safetycenter.SafetyEvent); field public static final String ACTION_REFRESH_SAFETY_SOURCES = "android.safetycenter.action.REFRESH_SAFETY_SOURCES"; @@ -228,7 +228,7 @@ package android.safetycenter { } public static interface SafetyCenterManager.OnSafetyCenterDataChangedListener { - method public default void onError(@NonNull android.safetycenter.SafetyCenterError); + method public default void onError(@NonNull android.safetycenter.SafetyCenterErrorDetails); method public void onSafetyCenterDataChanged(@NonNull android.safetycenter.SafetyCenterData); } @@ -281,9 +281,9 @@ package android.safetycenter { public final class SafetyEvent implements android.os.Parcelable { method public int describeContents(); method @Nullable public String getRefreshBroadcastId(); - method public int getSafetyEventType(); method @Nullable public String getSafetySourceIssueActionId(); method @Nullable public String getSafetySourceIssueId(); + method public int getType(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyEvent> CREATOR; field public static final int SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED = 500; // 0x1f4 @@ -318,12 +318,12 @@ package android.safetycenter { method @NonNull public android.safetycenter.SafetySourceData.Builder setStatus(@Nullable android.safetycenter.SafetySourceStatus); } - public final class SafetySourceError implements android.os.Parcelable { - ctor public SafetySourceError(@NonNull android.safetycenter.SafetyEvent); + public final class SafetySourceErrorDetails implements android.os.Parcelable { + ctor public SafetySourceErrorDetails(@NonNull android.safetycenter.SafetyEvent); method public int describeContents(); method @NonNull public android.safetycenter.SafetyEvent getSafetyEvent(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceError> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceErrorDetails> CREATOR; } public final class SafetySourceIssue implements android.os.Parcelable { @@ -353,7 +353,7 @@ package android.safetycenter { method @NonNull public CharSequence getLabel(); method @NonNull public android.app.PendingIntent getPendingIntent(); method @Nullable public CharSequence getSuccessMessage(); - method public boolean isResolving(); + method public boolean willResolve(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceIssue.Action> CREATOR; } @@ -361,8 +361,8 @@ package android.safetycenter { public static final class SafetySourceIssue.Action.Builder { ctor public SafetySourceIssue.Action.Builder(@NonNull String, @NonNull CharSequence, @NonNull android.app.PendingIntent); method @NonNull public android.safetycenter.SafetySourceIssue.Action build(); - method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setResolving(boolean); method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setSuccessMessage(@Nullable CharSequence); + method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setWillResolve(boolean); } public static final class SafetySourceIssue.Builder { @@ -446,6 +446,7 @@ package android.safetycenter.config { method @StringRes public int getTitleForWorkResId(); method @StringRes public int getTitleResId(); method public int getType(); + method public boolean isAutomaticNotificationFromIssueAllowed(); method public boolean isLoggingAllowed(); method public boolean isRefreshOnPageOpenAllowed(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -464,6 +465,7 @@ package android.safetycenter.config { public static final class SafetySource.Builder { ctor public SafetySource.Builder(int); method @NonNull public android.safetycenter.config.SafetySource build(); + method @NonNull public android.safetycenter.config.SafetySource.Builder setAutomaticNotificationFromIssueAllowed(boolean); method @NonNull public android.safetycenter.config.SafetySource.Builder setBroadcastReceiverClassName(@Nullable String); method @NonNull public android.safetycenter.config.SafetySource.Builder setId(@Nullable String); method @NonNull public android.safetycenter.config.SafetySource.Builder setInitialDisplayState(int); diff --git a/framework-s/java/android/safetycenter/IOnSafetyCenterDataChangedListener.aidl b/framework-s/java/android/safetycenter/IOnSafetyCenterDataChangedListener.aidl index 37ab9c600..cee97ea9b 100644 --- a/framework-s/java/android/safetycenter/IOnSafetyCenterDataChangedListener.aidl +++ b/framework-s/java/android/safetycenter/IOnSafetyCenterDataChangedListener.aidl @@ -17,7 +17,7 @@ package android.safetycenter; import android.safetycenter.SafetyCenterData; -import android.safetycenter.SafetyCenterError; +import android.safetycenter.SafetyCenterErrorDetails; /** * Listener for changes to {@link SafetyCenterData}. @@ -30,5 +30,5 @@ oneway interface IOnSafetyCenterDataChangedListener { void onSafetyCenterDataChanged(in SafetyCenterData data); /** Called when SafetyCenter should display an error related to changes in its data. */ - void onError(in SafetyCenterError error); + void onError(in SafetyCenterErrorDetails safetyCenterErrorDetails); } diff --git a/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl b/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl index b25f20d28..902b9f684 100644 --- a/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl +++ b/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl @@ -18,10 +18,9 @@ package android.safetycenter; import android.safetycenter.IOnSafetyCenterDataChangedListener; import android.safetycenter.SafetyCenterData; -import android.safetycenter.SafetyCenterError; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; -import android.safetycenter.SafetySourceError; +import android.safetycenter.SafetySourceErrorDetails; import android.safetycenter.config.SafetyCenterConfig; /** @@ -64,8 +63,9 @@ interface ISafetyCenterManager { * <p>Safety sources should use this API to notify SafetyCenter when SafetyCenter requested or * expected them to perform an action or provide data, but they were unable to do so. */ - void reportSafetySourceError(String safetySourceId, - in SafetySourceError safetySourceError, + void reportSafetySourceError( + String safetySourceId, + in SafetySourceErrorDetails safetySourceErrorDetails, String packageName, int userId); @@ -86,12 +86,16 @@ interface ISafetyCenterManager { int userId); /** - * Dismisses the issue corresponding to the given issue ID. + * Dismiss a Safety Center issue and prevent it from appearing in the Safety Center or affecting + * the overall safety status. */ - void dismissSafetyIssue(String issueId, int userId); + void dismissSafetyCenterIssue(String issueId, int userId); - /** Executes the specified action on the specified issue. */ - void executeAction(String safetyCenterIssueId, String safetyCenterIssueActionId, int userId); + /** Executes the specified Safety Center issue action on the specified Safety Center issue. */ + void executeSafetyCenterIssueAction( + String safetyCenterIssueId, + String safetyCenterIssueActionId, + int userId); /** * Clears all SafetySourceData set by safety sources using setSafetySourceData. diff --git a/framework-s/java/android/safetycenter/SafetyCenterError.aidl b/framework-s/java/android/safetycenter/SafetyCenterErrorDetails.aidl index 032dd8ad7..269ed3e1e 100644 --- a/framework-s/java/android/safetycenter/SafetyCenterError.aidl +++ b/framework-s/java/android/safetycenter/SafetyCenterErrorDetails.aidl @@ -17,8 +17,8 @@ package android.safetycenter; /** - * Parcelable AIDL SafetyCenterError. + * Parcelable AIDL SafetyCenterErrorDetails. * * @hide */ -parcelable SafetyCenterError;
\ No newline at end of file +parcelable SafetyCenterErrorDetails;
\ No newline at end of file diff --git a/framework-s/java/android/safetycenter/SafetyCenterError.java b/framework-s/java/android/safetycenter/SafetyCenterErrorDetails.java index fe8409bb5..99dbc0151 100644 --- a/framework-s/java/android/safetycenter/SafetyCenterError.java +++ b/framework-s/java/android/safetycenter/SafetyCenterErrorDetails.java @@ -29,19 +29,19 @@ import androidx.annotation.RequiresApi; import java.util.Objects; /** - * An error the Safety Center should display to the user. + * Details of an error that the Safety Center should display to the user. * * @hide */ @SystemApi @RequiresApi(TIRAMISU) -public final class SafetyCenterError implements Parcelable { +public final class SafetyCenterErrorDetails implements Parcelable { @NonNull private final CharSequence mErrorMessage; - /** Creates a {@link SafetyCenterError} with a given error message. */ - public SafetyCenterError(@NonNull CharSequence errorMessage) { + /** Creates a {@link SafetyCenterErrorDetails} with a given error message. */ + public SafetyCenterErrorDetails(@NonNull CharSequence errorMessage) { mErrorMessage = errorMessage; } @@ -55,7 +55,7 @@ public final class SafetyCenterError implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - SafetyCenterError that = (SafetyCenterError) o; + SafetyCenterErrorDetails that = (SafetyCenterErrorDetails) o; return TextUtils.equals(mErrorMessage, that.mErrorMessage); } @@ -66,7 +66,7 @@ public final class SafetyCenterError implements Parcelable { @Override public String toString() { - return "SafetyCenterError{" + return "SafetyCenterErrorDetails{" + "mErrorMessage=" + mErrorMessage + '}'; } @@ -82,15 +82,17 @@ public final class SafetyCenterError implements Parcelable { } @NonNull - public static final Creator<SafetyCenterError> CREATOR = new Creator<SafetyCenterError>() { + public static final Creator<SafetyCenterErrorDetails> CREATOR = + new Creator<SafetyCenterErrorDetails>() { @Override - public SafetyCenterError createFromParcel(Parcel in) { - return new SafetyCenterError(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)); + public SafetyCenterErrorDetails createFromParcel(Parcel in) { + return new SafetyCenterErrorDetails( + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)); } @Override - public SafetyCenterError[] newArray(int size) { - return new SafetyCenterError[0]; + public SafetyCenterErrorDetails[] newArray(int size) { + return new SafetyCenterErrorDetails[0]; } }; } diff --git a/framework-s/java/android/safetycenter/SafetyCenterIssue.java b/framework-s/java/android/safetycenter/SafetyCenterIssue.java index 9f5b83c55..3371bc852 100644 --- a/framework-s/java/android/safetycenter/SafetyCenterIssue.java +++ b/framework-s/java/android/safetycenter/SafetyCenterIssue.java @@ -23,6 +23,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.PendingIntent; import android.os.Parcel; @@ -370,7 +371,7 @@ public final class SafetyCenterIssue implements Parcelable { private final CharSequence mLabel; @NonNull private final PendingIntent mPendingIntent; - private final boolean mResolving; + private final boolean mWillResolve; private final boolean mInFlight; @Nullable private final CharSequence mSuccessMessage; @@ -379,13 +380,13 @@ public final class SafetyCenterIssue implements Parcelable { @NonNull String id, @NonNull CharSequence label, @NonNull PendingIntent pendingIntent, - boolean resolving, + boolean willResolve, boolean inFlight, @Nullable CharSequence successMessage) { mId = requireNonNull(id); mLabel = requireNonNull(label); mPendingIntent = requireNonNull(pendingIntent); - mResolving = resolving; + mWillResolve = willResolve; mInFlight = inFlight; mSuccessMessage = successMessage; } @@ -410,14 +411,18 @@ public final class SafetyCenterIssue implements Parcelable { /** * Returns whether invoking this action will fix or address the issue sufficiently for it - * to be considered resolved i.e. the issue will no longer need to be conveyed to the user - * in the UI. + * to be considered resolved (i.e. the issue will no longer need to be conveyed to the user + * in the UI). */ - public boolean isResolving() { - return mResolving; + public boolean willResolve() { + return mWillResolve; } - /** Returns whether or not this action is currently being executed. */ + /** + * Returns whether or not this action is currently being executed (i.e. the user clicked + * on a button that triggered this action, and now the Safety Center is waiting for the + * action's result). + */ public boolean isInFlight() { return mInFlight; } @@ -439,7 +444,7 @@ public final class SafetyCenterIssue implements Parcelable { return Objects.equals(mId, action.mId) && TextUtils.equals(mLabel, action.mLabel) && Objects.equals(mPendingIntent, action.mPendingIntent) - && mResolving == action.mResolving + && mWillResolve == action.mWillResolve && mInFlight == action.mInFlight && TextUtils.equals(mSuccessMessage, action.mSuccessMessage); } @@ -447,7 +452,7 @@ public final class SafetyCenterIssue implements Parcelable { @Override public int hashCode() { return Objects.hash( - mId, mLabel, mSuccessMessage, mResolving, mInFlight, mPendingIntent); + mId, mLabel, mSuccessMessage, mWillResolve, mInFlight, mPendingIntent); } @Override @@ -456,7 +461,7 @@ public final class SafetyCenterIssue implements Parcelable { + "mId=" + mId + ", mLabel=" + mLabel + ", mPendingIntent=" + mPendingIntent - + ", mResolving=" + mResolving + + ", mWillResolve=" + mWillResolve + ", mInFlight=" + mInFlight + ", mSuccessMessage=" + mSuccessMessage + '}'; @@ -472,7 +477,7 @@ public final class SafetyCenterIssue implements Parcelable { dest.writeString(mId); TextUtils.writeToParcel(mLabel, dest, flags); dest.writeParcelable(mPendingIntent, flags); - dest.writeBoolean(mResolving); + dest.writeBoolean(mWillResolve); dest.writeBoolean(mInFlight); TextUtils.writeToParcel(mSuccessMessage, dest, flags); } @@ -486,8 +491,8 @@ public final class SafetyCenterIssue implements Parcelable { .setPendingIntent( in.readParcelable( PendingIntent.class.getClassLoader(), PendingIntent.class)) - .setResolving(in.readBoolean()) - .setInFlight(in.readBoolean()) + .setWillResolve(in.readBoolean()) + .setIsInFlight(in.readBoolean()) .setSuccessMessage(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)) .build(); } @@ -503,7 +508,7 @@ public final class SafetyCenterIssue implements Parcelable { private String mId; private CharSequence mLabel; private PendingIntent mPendingIntent; - private boolean mResolving; + private boolean mWillResolve; private boolean mInFlight; private CharSequence mSuccessMessage; @@ -529,23 +534,28 @@ public final class SafetyCenterIssue implements Parcelable { } /** - * Sets whether or not this action is resolving. Defaults to false. + * Sets whether or not this action will resolve the issue when executed. Defaults to + * false. * - * @see #isResolving() + * @see #willResolve() */ + @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public Builder setResolving(boolean resolving) { - mResolving = resolving; + public Builder setWillResolve(boolean willResolve) { + mWillResolve = willResolve; return this; } /** - * Sets whether or not this action is in flight. Defaults to false. + * Sets a boolean that indicates whether or not this action is currently being executed + * (i.e. the user clicked on a button that triggered this action, and now the Safety + * Center is waiting for the action's result). Defaults to false. * * @see #isInFlight() */ + @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public Builder setInFlight(boolean inFlight) { + public Builder setIsInFlight(boolean inFlight) { mInFlight = inFlight; return this; } @@ -564,7 +574,7 @@ public final class SafetyCenterIssue implements Parcelable { @NonNull public Action build() { return new Action( - mId, mLabel, mPendingIntent, mResolving, mInFlight, mSuccessMessage); + mId, mLabel, mPendingIntent, mWillResolve, mInFlight, mSuccessMessage); } } } diff --git a/framework-s/java/android/safetycenter/SafetyCenterManager.java b/framework-s/java/android/safetycenter/SafetyCenterManager.java index de0e14187..11fc87403 100644 --- a/framework-s/java/android/safetycenter/SafetyCenterManager.java +++ b/framework-s/java/android/safetycenter/SafetyCenterManager.java @@ -81,9 +81,8 @@ public final class SafetyCenterManager { * <p>On receiving this broadcast, safety sources should determine their safety state according * to the parameters specified in the intent extras (see below) and set {@link SafetySourceData} * using {@link #setSafetySourceData}, along with a {@link SafetyEvent} with - * {@link SafetyEvent#getSafetyEventType()} set to - * {@link SafetyEvent#SAFETY_EVENT_TYPE_REFRESH_REQUESTED} and - * {@link SafetyEvent#getRefreshBroadcastId()} set to the value of broadcast intent extra + * {@link SafetyEvent#getType()} set to {@link SafetyEvent#SAFETY_EVENT_TYPE_REFRESH_REQUESTED} + * and {@link SafetyEvent#getRefreshBroadcastId()} set to the value of broadcast intent extra * {@link #EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. If the safety source is unable to provide * data, it can set a {@code null} {@link SafetySourceData}, which will clear any existing * {@link SafetySourceData} stored by Safety Center, and Safety Center will fall back to any @@ -134,7 +133,6 @@ public final class SafetyCenterManager { /** * Used as an {@code String} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to * specify a string identifier for the broadcast. - * */ public static final String EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID = "android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID"; @@ -224,9 +222,10 @@ public final class SafetyCenterManager { /** * Called when the Safety Center should display an error related to changes in its data. * - * @param error an error that should be displayed to the user + * @param errorDetails details of an error that should be displayed to the user */ - default void onError(@NonNull SafetyCenterError error) {} + default void onError(@NonNull SafetyCenterErrorDetails errorDetails) { + } } private final Object mListenersLock = new Object(); @@ -250,7 +249,12 @@ public final class SafetyCenterManager { this.mService = service; } - /** Returns whether the Safety Center feature is enabled. */ + /** + * Returns whether the Safety Center feature is enabled. + * + * <p>If this returns {@code false}, all the other methods in this class will no-op and/or + * return default values. + */ @RequiresPermission(anyOf = { READ_SAFETY_CENTER_STATUS, SEND_SAFETY_CENTER_UPDATE @@ -273,16 +277,17 @@ public final class SafetyCenterManager { * <p>This call will rewrite any existing {@link SafetySourceData} already set for the given * {@code safetySourceId} for the calling user. * - * @param safetySourceId the unique identifier for a safety source in the calling user + * @param safetySourceId the unique identifier for a safety source in the calling user * @param safetySourceData the latest safety data for the safety source in the calling user. If - * a safety source does not have any data to set, it can set its - * {@link SafetySourceData} to {@code null}, in which case Safety Center - * will fall back to any placeholder data specified in the safety source - * xml configuration. - * @param safetyEvent the event that triggered the safety source to set safety data + * a safety source does not have any data to set, it can set its + * {@link SafetySourceData} to {@code null}, in which case Safety Center + * will fall back to any placeholder data specified in the safety source + * xml configuration. + * @param safetyEvent the event that triggered the safety source to set safety data */ @RequiresPermission(SEND_SAFETY_CENTER_UPDATE) - public void setSafetySourceData(@NonNull String safetySourceId, + public void setSafetySourceData( + @NonNull String safetySourceId, @Nullable SafetySourceData safetySourceData, @NonNull SafetyEvent safetyEvent) { requireNonNull(safetySourceId, "safetySourceId cannot be null"); @@ -328,19 +333,20 @@ public final class SafetyCenterManager { * <p>Safety sources should use this API to notify Safety Center when Safety Center requested or * expected them to perform an action or provide data, but they were unable to do so. * - * @param safetySourceId the id of the safety source that provided the issue - * @param safetySourceError the error that occurred + * @param safetySourceId the id of the safety source that provided the issue + * @param safetySourceErrorDetails details of the error that occurred */ @RequiresPermission(SEND_SAFETY_CENTER_UPDATE) public void reportSafetySourceError( - @NonNull String safetySourceId, @NonNull SafetySourceError safetySourceError) { + @NonNull String safetySourceId, + @NonNull SafetySourceErrorDetails safetySourceErrorDetails) { requireNonNull(safetySourceId, "safetySourceId cannot be null"); - requireNonNull(safetySourceError, "safetySourceError cannot be null"); + requireNonNull(safetySourceErrorDetails, "safetySourceErrorDetails cannot be null"); try { mService.reportSafetySourceError( safetySourceId, - safetySourceError, + safetySourceErrorDetails, mContext.getPackageName(), mContext.getUser().getIdentifier()); } catch (RemoteException e) { @@ -432,38 +438,40 @@ public final class SafetyCenterManager { } /** - * Dismiss an active safety issue and prevent it from appearing in the Safety Center or - * affecting the overall safety status. + * Dismiss a Safety Center issue and prevent it from appearing in the Safety Center or affecting + * the overall safety status. * * @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()} */ @RequiresPermission(MANAGE_SAFETY_CENTER) - public void dismissSafetyIssue(@NonNull String safetyCenterIssueId) { + public void dismissSafetyCenterIssue(@NonNull String safetyCenterIssueId) { requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null"); try { - mService.dismissSafetyIssue(safetyCenterIssueId, mContext.getUser().getIdentifier()); + mService.dismissSafetyCenterIssue( + safetyCenterIssueId, mContext.getUser().getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Executes the specified action on the specified issue. + * Executes the specified Safety Center issue action on the specified Safety Center issue. * - * @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()} + * @param safetyCenterIssueId the target issue ID returned by + * {@link SafetyCenterIssue#getId()} * @param safetyCenterIssueActionId the target action ID returned by {@link - * SafetyCenterIssue.Action#getId()} + * SafetyCenterIssue.Action#getId()} */ @RequiresPermission(MANAGE_SAFETY_CENTER) - public void executeAction( + public void executeSafetyCenterIssueAction( @NonNull String safetyCenterIssueId, @NonNull String safetyCenterIssueActionId) { requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null"); requireNonNull(safetyCenterIssueActionId, "safetyCenterIssueActionId cannot be null"); try { - mService.executeAction( + mService.executeSafetyCenterIssueAction( safetyCenterIssueId, safetyCenterIssueActionId, mContext.getUser().getIdentifier()); @@ -552,13 +560,12 @@ public final class SafetyCenterManager { } @Override - public void onError(@NonNull SafetyCenterError safetyCenterError) { - requireNonNull(safetyCenterError, "safetyCenterError cannot be null"); + public void onError(@NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) { + requireNonNull(safetyCenterErrorDetails, "safetyCenterErrorDetails cannot be null"); final long identity = Binder.clearCallingIdentity(); try { - mExecutor.execute( - () -> mOriginalListener.onError(safetyCenterError)); + mExecutor.execute(() -> mOriginalListener.onError(safetyCenterErrorDetails)); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/framework-s/java/android/safetycenter/SafetyEvent.java b/framework-s/java/android/safetycenter/SafetyEvent.java index bf4acb280..97bd0fcf8 100644 --- a/framework-s/java/android/safetycenter/SafetyEvent.java +++ b/framework-s/java/android/safetycenter/SafetyEvent.java @@ -53,7 +53,7 @@ public final class SafetyEvent implements Parcelable { SAFETY_EVENT_TYPE_DEVICE_REBOOTED }) @Retention(RetentionPolicy.SOURCE) - public @interface SafetyEventType { + public @interface Type { } /** * Indicates that there has been a change of state for safety source, which may be independent @@ -104,8 +104,8 @@ public final class SafetyEvent implements Parcelable { } }; - @SafetyEventType - private final int mSafetyEventType; + @Type + private final int mType; @Nullable private final String mRefreshBroadcastId; @Nullable @@ -113,30 +113,30 @@ public final class SafetyEvent implements Parcelable { @Nullable private final String mSafetySourceIssueActionId; - private SafetyEvent(@SafetyEventType int safetyEvent, + private SafetyEvent(@Type int type, @Nullable String refreshBroadcastId, @Nullable String safetySourceIssueId, @Nullable String safetySourceIssueActionId) { - mSafetyEventType = safetyEvent; + mType = type; mRefreshBroadcastId = refreshBroadcastId; mSafetySourceIssueId = safetySourceIssueId; mSafetySourceIssueActionId = safetySourceIssueActionId; } /** Returns the type of the safety event. */ - @SafetyEventType - public int getSafetyEventType() { - return mSafetyEventType; + @Type + public int getType() { + return mType; } /** - * Returns an optional broadcast id provided by Safety Center when requesting a refresh, through + * Returns an optional id provided by Safety Center when requesting a refresh, through * {@link SafetyCenterManager#EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. * * <p>This will only be relevant for events of type * {@link #SAFETY_EVENT_TYPE_REFRESH_REQUESTED}. * - * @see #getSafetyEventType() + * @see #getType() */ @Nullable public String getRefreshBroadcastId() { @@ -150,7 +150,7 @@ public final class SafetyEvent implements Parcelable { * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. * - * @see #getSafetyEventType() + * @see #getType() * @see SafetySourceIssue#getId() */ @Nullable @@ -166,7 +166,7 @@ public final class SafetyEvent implements Parcelable { * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. * - * @see #getSafetyEventType() + * @see #getType() * @see SafetySourceIssue.Action#getId() */ @Nullable @@ -181,7 +181,7 @@ public final class SafetyEvent implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mSafetyEventType); + dest.writeInt(mType); dest.writeString(mRefreshBroadcastId); dest.writeString(mSafetySourceIssueId); dest.writeString(mSafetySourceIssueActionId); @@ -192,7 +192,7 @@ public final class SafetyEvent implements Parcelable { if (this == o) return true; if (!(o instanceof SafetyEvent)) return false; SafetyEvent that = (SafetyEvent) o; - return mSafetyEventType == that.mSafetyEventType + return mType == that.mType && Objects.equals(mRefreshBroadcastId, that.mRefreshBroadcastId) && Objects.equals(mSafetySourceIssueId, that.mSafetySourceIssueId) && Objects.equals(mSafetySourceIssueActionId, that.mSafetySourceIssueActionId); @@ -200,15 +200,15 @@ public final class SafetyEvent implements Parcelable { @Override public int hashCode() { - return Objects.hash(mSafetyEventType, mRefreshBroadcastId, mSafetySourceIssueId, + return Objects.hash(mType, mRefreshBroadcastId, mSafetySourceIssueId, mSafetySourceIssueActionId); } @Override public String toString() { return "SafetyEvent{" - + "mSafetyEventType=" - + mSafetyEventType + + "mType=" + + mType + ", mRefreshBroadcastId='" + mRefreshBroadcastId + '\'' @@ -223,8 +223,8 @@ public final class SafetyEvent implements Parcelable { /** Builder class for {@link SafetyEvent}. */ public static final class Builder { - @SafetyEventType - private final int mSafetyEventType; + @Type + private final int mType; @Nullable private String mRefreshBroadcastId; @Nullable @@ -233,8 +233,8 @@ public final class SafetyEvent implements Parcelable { private String mSafetySourceIssueActionId; /** Creates a {@link Builder} for {@link SafetyEvent}. */ - public Builder(@SafetyEventType int safetyEventType) { - mSafetyEventType = safetyEventType; + public Builder(@Type int type) { + mType = type; } /** @@ -244,7 +244,7 @@ public final class SafetyEvent implements Parcelable { * <p>This will only be relevant for events of type * {@link #SAFETY_EVENT_TYPE_REFRESH_REQUESTED}. * - * @see #getSafetyEventType() + * @see #getType() */ @NonNull public Builder setRefreshBroadcastId(@Nullable String refreshBroadcastId) { @@ -259,7 +259,7 @@ public final class SafetyEvent implements Parcelable { * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. * - * @see #getSafetyEventType() + * @see #getType() * @see SafetySourceIssue#getId() */ @NonNull @@ -276,7 +276,7 @@ public final class SafetyEvent implements Parcelable { * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} or * {@link #SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED}. * - * @see #getSafetyEventType() + * @see #getType() * @see SafetySourceIssue.Action#getId() */ @NonNull @@ -290,7 +290,7 @@ public final class SafetyEvent implements Parcelable { */ @NonNull public SafetyEvent build() { - return new SafetyEvent(mSafetyEventType, mRefreshBroadcastId, mSafetySourceIssueId, + return new SafetyEvent(mType, mRefreshBroadcastId, mSafetySourceIssueId, mSafetySourceIssueActionId); } } diff --git a/framework-s/java/android/safetycenter/SafetySourceData.java b/framework-s/java/android/safetycenter/SafetySourceData.java index 954b2794f..70c0a8186 100644 --- a/framework-s/java/android/safetycenter/SafetySourceData.java +++ b/framework-s/java/android/safetycenter/SafetySourceData.java @@ -29,6 +29,7 @@ import android.os.Parcelable; import androidx.annotation.RequiresApi; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -50,7 +51,12 @@ public final class SafetySourceData implements Parcelable { SafetySourceStatus status = in.readTypedObject(SafetySourceStatus.CREATOR); List<SafetySourceIssue> issues = in.createTypedArrayList(SafetySourceIssue.CREATOR); - return new SafetySourceData(status, issues); + Builder builder = new Builder().setStatus(status); + // TODO(b/224513050): Consider simplifying by adding a new API to the builder. + for (int i = 0; i < issues.size(); i++) { + builder.addIssue(issues.get(i)); + } + return builder.build(); } @Override @@ -70,13 +76,13 @@ public final class SafetySourceData implements Parcelable { this.mIssues = new ArrayList<>(issues); } - /** Returns the data for the safety source status to be shown in UI. */ + /** Returns the data for the {@link SafetySourceStatus} to be shown in UI. */ @Nullable public SafetySourceStatus getStatus() { return mStatus; } - /** Returns the data for the list of safety source issues to be shown in UI. */ + /** Returns the data for the list of {@link SafetySourceIssue}s to be shown in UI. */ @NonNull public List<SafetySourceIssue> getIssues() { return new ArrayList<>(mIssues); @@ -124,14 +130,14 @@ public final class SafetySourceData implements Parcelable { @Nullable private SafetySourceStatus mStatus; - /** Sets data for the safety source status to be shown in UI. */ + /** Sets data for the {@link SafetySourceStatus} to be shown in UI. */ @NonNull public Builder setStatus(@Nullable SafetySourceStatus status) { mStatus = status; return this; } - /** Adds data for a safety source issue to be shown in UI. */ + /** Adds data for a {@link SafetySourceIssue} to be shown in UI. */ @NonNull public Builder addIssue(@NonNull SafetySourceIssue safetySourceIssue) { mIssues.add(requireNonNull(safetySourceIssue)); @@ -139,7 +145,8 @@ public final class SafetySourceData implements Parcelable { } /** - * Clears data for all the safety source issues that were added to this {@link Builder}. + * Clears data for all the {@link SafetySourceIssue}s that were added to this + * {@link Builder}. */ @NonNull public Builder clearIssues() { @@ -152,7 +159,7 @@ public final class SafetySourceData implements Parcelable { public SafetySourceData build() { // TODO(b/207329841): Validate data matches validation in S, for eg that the status // and severity levels of the settings and issues are compatible. - return new SafetySourceData(mStatus, mIssues); + return new SafetySourceData(mStatus, Collections.unmodifiableList(mIssues)); } } } diff --git a/framework-s/java/android/safetycenter/SafetySourceError.aidl b/framework-s/java/android/safetycenter/SafetySourceErrorDetails.aidl index 24c5f802b..96296d259 100644 --- a/framework-s/java/android/safetycenter/SafetySourceError.aidl +++ b/framework-s/java/android/safetycenter/SafetySourceErrorDetails.aidl @@ -17,8 +17,8 @@ package android.safetycenter; /** - * Parcelable AIDL SafetySourceError. + * Parcelable AIDL SafetySourceErrorDetails. * * @hide */ -parcelable SafetySourceError;
\ No newline at end of file +parcelable SafetySourceErrorDetails;
\ No newline at end of file diff --git a/framework-s/java/android/safetycenter/SafetySourceError.java b/framework-s/java/android/safetycenter/SafetySourceErrorDetails.java index afc33eaff..1b42877e6 100644 --- a/framework-s/java/android/safetycenter/SafetySourceError.java +++ b/framework-s/java/android/safetycenter/SafetySourceErrorDetails.java @@ -28,17 +28,17 @@ import androidx.annotation.RequiresApi; import java.util.Objects; /** - * An error that a Safety Source may report to the Safety Center. + * Details of an error that a Safety Source may report to the Safety Center. * * @hide */ @SystemApi @RequiresApi(TIRAMISU) -public final class SafetySourceError implements Parcelable { +public final class SafetySourceErrorDetails implements Parcelable { @NonNull private final SafetyEvent mSafetyEvent; - public SafetySourceError(@NonNull SafetyEvent safetyEvent) { + public SafetySourceErrorDetails(@NonNull SafetyEvent safetyEvent) { mSafetyEvent = safetyEvent; } @@ -53,7 +53,7 @@ public final class SafetySourceError implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - SafetySourceError that = (SafetySourceError) o; + SafetySourceErrorDetails that = (SafetySourceErrorDetails) o; return mSafetyEvent.equals(that.mSafetyEvent); } @@ -64,7 +64,7 @@ public final class SafetySourceError implements Parcelable { @Override public String toString() { - return "SafetySourceError{" + return "SafetySourceErrorDetails{" + "mSafetyEvent=" + mSafetyEvent + '}'; @@ -81,16 +81,17 @@ public final class SafetySourceError implements Parcelable { } @NonNull - public static final Creator<SafetySourceError> CREATOR = new Creator<SafetySourceError>() { + public static final Creator<SafetySourceErrorDetails> CREATOR = + new Creator<SafetySourceErrorDetails>() { @Override - public SafetySourceError createFromParcel(Parcel in) { - return new SafetySourceError(in.readParcelable(SafetyEvent.class.getClassLoader(), - SafetyEvent.class)); + public SafetySourceErrorDetails createFromParcel(Parcel in) { + return new SafetySourceErrorDetails( + in.readParcelable(SafetyEvent.class.getClassLoader(), SafetyEvent.class)); } @Override - public SafetySourceError[] newArray(int size) { - return new SafetySourceError[0]; + public SafetySourceErrorDetails[] newArray(int size) { + return new SafetySourceErrorDetails[0]; } }; } diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java index 1bfab00d0..dc7d91236 100644 --- a/framework-s/java/android/safetycenter/SafetySourceIssue.java +++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java @@ -25,6 +25,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.PendingIntent; import android.os.Parcel; @@ -36,6 +37,7 @@ import androidx.annotation.RequiresApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -111,8 +113,15 @@ public final class SafetySourceIssue implements Parcelable { PendingIntent onDismissPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(in); String issueTypeId = requireNonNull(in.readString()); - return new SafetySourceIssue(id, title, subtitle, summary, severityLevel, - issueCategory, actions, onDismissPendingIntent, issueTypeId); + Builder builder = new Builder(id, title, summary, severityLevel, issueTypeId) + .setSubtitle(subtitle) + .setIssueCategory(issueCategory) + .setOnDismissPendingIntent(onDismissPendingIntent); + // TODO(b/224513050): Consider simplifying by adding a new API to the builder. + for (int i = 0; i < actions.size(); i++) { + builder.addAction(actions.get(i)); + } + return builder.build(); } @Override @@ -208,8 +217,7 @@ public final class SafetySourceIssue implements Parcelable { } /** - * Returns a list of {@link Action} instances representing actions supported in the UI for this - * issue. + * Returns a list of {@link Action}s representing actions supported in the UI for this issue. * * <p>Each issue must contain at least one action, in order to help the user resolve the issue. * @@ -277,7 +285,7 @@ public final class SafetySourceIssue implements Parcelable { return mSeverityLevel == that.mSeverityLevel && TextUtils.equals(mId, that.mId) && TextUtils.equals(mTitle, that.mTitle) - && Objects.equals(mSubtitle, that.mSubtitle) + && TextUtils.equals(mSubtitle, that.mSubtitle) && TextUtils.equals(mSummary, that.mSummary) && mIssueCategory == that.mIssueCategory && mActions.equals(that.mActions) @@ -382,16 +390,14 @@ public final class SafetySourceIssue implements Parcelable { new Parcelable.Creator<Action>() { @Override public Action createFromParcel(Parcel in) { - String id = requireNonNull(in.readString()); - CharSequence label = - requireNonNull( - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)); - PendingIntent pendingIntent = - requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in)); - boolean resolving = in.readBoolean(); - CharSequence successMessage = - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - return new Action(id, label, pendingIntent, resolving, successMessage); + return new Builder( + in.readString(), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in), + PendingIntent.readPendingIntentOrNullFromParcel(in)) + .setWillResolve(in.readBoolean()) + .setSuccessMessage( + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)) + .build(); } @Override @@ -406,7 +412,7 @@ public final class SafetySourceIssue implements Parcelable { private final CharSequence mLabel; @NonNull private final PendingIntent mPendingIntent; - private final boolean mResolving; + private final boolean mWillResolve; @Nullable private final CharSequence mSuccessMessage; @@ -414,12 +420,12 @@ public final class SafetySourceIssue implements Parcelable { String id, @NonNull CharSequence label, @NonNull PendingIntent pendingIntent, - boolean resolving, + boolean willResolve, @Nullable CharSequence successMessage) { mId = id; mLabel = label; mPendingIntent = pendingIntent; - mResolving = resolving; + mWillResolve = willResolve; mSuccessMessage = successMessage; } @@ -457,8 +463,8 @@ public final class SafetySourceIssue implements Parcelable { * to be considered resolved i.e. the issue will no longer need to be conveyed to the user * in the UI. */ - public boolean isResolving() { - return mResolving; + public boolean willResolve() { + return mWillResolve; } /** @@ -480,7 +486,7 @@ public final class SafetySourceIssue implements Parcelable { dest.writeString(mId); TextUtils.writeToParcel(mLabel, dest, flags); mPendingIntent.writeToParcel(dest, flags); - dest.writeBoolean(mResolving); + dest.writeBoolean(mWillResolve); TextUtils.writeToParcel(mSuccessMessage, dest, flags); } @@ -492,13 +498,13 @@ public final class SafetySourceIssue implements Parcelable { return mId.equals(that.mId) && TextUtils.equals(mLabel, that.mLabel) && mPendingIntent.equals(that.mPendingIntent) - && mResolving == that.mResolving - && Objects.equals(mSuccessMessage, that.mSuccessMessage); + && mWillResolve == that.mWillResolve + && TextUtils.equals(mSuccessMessage, that.mSuccessMessage); } @Override public int hashCode() { - return Objects.hash(mId, mLabel, mPendingIntent, mResolving, mSuccessMessage); + return Objects.hash(mId, mLabel, mPendingIntent, mWillResolve, mSuccessMessage); } @Override @@ -507,7 +513,7 @@ public final class SafetySourceIssue implements Parcelable { + "mId=" + mId + ", mLabel=" + mLabel + ", mPendingIntent=" + mPendingIntent - + ", mResolving=" + mResolving + + ", mWillResolve=" + mWillResolve + ", mSuccessMessage=" + mSuccessMessage + '}'; } @@ -520,7 +526,7 @@ public final class SafetySourceIssue implements Parcelable { private final CharSequence mLabel; @NonNull private final PendingIntent mPendingIntent; - private boolean mResolving = false; + private boolean mWillResolve = false; @Nullable private CharSequence mSuccessMessage; @@ -537,11 +543,12 @@ public final class SafetySourceIssue implements Parcelable { /** * Sets whether the action will resolve the safety issue. Defaults to false. * - * @see #isResolving() + * @see #willResolve() */ + @SuppressLint("MissingGetterMatchingBuilder") @NonNull - public Builder setResolving(boolean resolving) { - mResolving = resolving; + public Builder setWillResolve(boolean willResolve) { + mWillResolve = willResolve; return this; } @@ -558,7 +565,7 @@ public final class SafetySourceIssue implements Parcelable { /** Creates the {@link Action} defined by this {@link Builder}. */ @NonNull public Action build() { - return new Action(mId, mLabel, mPendingIntent, mResolving, mSuccessMessage); + return new Action(mId, mLabel, mPendingIntent, mWillResolve, mSuccessMessage); } } } @@ -616,14 +623,14 @@ public final class SafetySourceIssue implements Parcelable { return this; } - /** Adds data for an action to be shown in UI. */ + /** Adds data for an {@link Action} to be shown in UI. */ @NonNull public Builder addAction(@NonNull Action actionData) { mActions.add(requireNonNull(actionData)); return this; } - /** Clears data for all the actions that were added to this {@link Builder}. */ + /** Clears data for all the {@link Action}s that were added to this {@link Builder}. */ @NonNull public Builder clearActions() { mActions.clear(); @@ -654,7 +661,8 @@ public final class SafetySourceIssue implements Parcelable { checkArgument(mActions.size() <= 2, "Safety source issue must not contain more than 2 actions"); return new SafetySourceIssue(mId, mTitle, mSubtitle, mSummary, mSeverityLevel, - mIssueCategory, mActions, mOnDismissPendingIntent, mIssueTypeId); + mIssueCategory, Collections.unmodifiableList(mActions), mOnDismissPendingIntent, + mIssueTypeId); } } } diff --git a/framework-s/java/android/safetycenter/SafetySourceStatus.java b/framework-s/java/android/safetycenter/SafetySourceStatus.java index 83a54106d..9868064ff 100644 --- a/framework-s/java/android/safetycenter/SafetySourceStatus.java +++ b/framework-s/java/android/safetycenter/SafetySourceStatus.java @@ -80,17 +80,14 @@ public final class SafetySourceStatus implements Parcelable { new Parcelable.Creator<SafetySourceStatus>() { @Override public SafetySourceStatus createFromParcel(Parcel in) { - CharSequence title = - requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)); - CharSequence summary = - requireNonNull(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)); - int statusLevel = in.readInt(); - PendingIntent pendingIntent = - requireNonNull(PendingIntent.readPendingIntentOrNullFromParcel(in)); - IconAction iconAction = in.readTypedObject(IconAction.CREATOR); - boolean enabled = in.readBoolean(); - return new SafetySourceStatus(title, summary, statusLevel, pendingIntent, - iconAction, enabled); + return new Builder( + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in), + in.readInt(), + PendingIntent.readPendingIntentOrNullFromParcel(in)) + .setIconAction(in.readTypedObject(IconAction.CREATOR)) + .setEnabled(in.readBoolean()) + .build(); } @Override @@ -150,7 +147,7 @@ public final class SafetySourceStatus implements Parcelable { } /** - * Returns an optional icon action to be displayed in the safety source status UI. + * Returns an optional {@link IconAction} to be displayed in the safety source status UI. * * <p>The icon action will be a clickable icon which performs an action as indicated by the * icon. @@ -387,7 +384,7 @@ public final class SafetySourceStatus implements Parcelable { } /** - * Sets an optional icon action for the safety source status. + * Sets an optional {@link IconAction} for the safety source status. * * @see #getIconAction() */ diff --git a/framework-s/java/android/safetycenter/config/BuilderUtils.java b/framework-s/java/android/safetycenter/config/BuilderUtils.java index b94f9fe54..a01eb65c5 100644 --- a/framework-s/java/android/safetycenter/config/BuilderUtils.java +++ b/framework-s/java/android/safetycenter/config/BuilderUtils.java @@ -16,38 +16,50 @@ package android.safetycenter.config; -import android.annotation.IdRes; +import android.annotation.AnyRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; +import java.util.Objects; + final class BuilderUtils { private BuilderUtils() { } - static void validateAttribute(@Nullable Object attribute, @NonNull String name, - boolean required, boolean prohibited) { + private static void validateAttribute(@Nullable Object attribute, @NonNull String name, + boolean required, boolean prohibited, @Nullable Object defaultValue) { if (attribute == null && required) { throw new IllegalStateException(String.format("Required attribute %s missing", name)); } - if (attribute != null && prohibited) { + boolean nonDefaultValueProvided = !Objects.equals(attribute, defaultValue); + boolean checkProhibited = prohibited && nonDefaultValueProvided; + if (attribute != null && checkProhibited) { throw new IllegalStateException(String.format("Prohibited attribute %s present", name)); } } - @IdRes - static int validateResId(@Nullable @IdRes Integer value, @NonNull String name, boolean required, - boolean prohibited) { - validateAttribute(value, name, required, prohibited); + static void validateAttribute(@Nullable Object attribute, @NonNull String name, + boolean required, boolean prohibited) { + validateAttribute(attribute, name, required, prohibited, null); + } + + @AnyRes + static int validateResId(@Nullable @AnyRes Integer value, @NonNull String name, + boolean required, boolean prohibited) { + validateAttribute(value, name, required, prohibited, Integer.valueOf(Resources.ID_NULL)); if (value == null) { return Resources.ID_NULL; } + if (required && value == Resources.ID_NULL) { + throw new IllegalStateException(String.format("Required attribute %s invalid", name)); + } return value; } static int validateIntDef(@Nullable Integer value, @NonNull String name, boolean required, boolean prohibited, int defaultValue, int... validValues) { - validateAttribute(value, name, required, prohibited); + validateAttribute(value, name, required, prohibited, Integer.valueOf(defaultValue)); if (value == null) { return defaultValue; } @@ -64,7 +76,7 @@ final class BuilderUtils { static int validateInteger(@Nullable Integer value, @NonNull String name, boolean required, boolean prohibited, int defaultValue) { - validateAttribute(value, name, required, prohibited); + validateAttribute(value, name, required, prohibited, Integer.valueOf(defaultValue)); if (value == null) { return defaultValue; } @@ -73,7 +85,7 @@ final class BuilderUtils { static boolean validateBoolean(@Nullable Boolean value, @NonNull String name, boolean required, boolean prohibited, boolean defaultValue) { - validateAttribute(value, name, required, prohibited); + validateAttribute(value, name, required, prohibited, Boolean.valueOf(defaultValue)); if (value == null) { return defaultValue; } diff --git a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java index 9f74be082..6f2bb8d54 100644 --- a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java +++ b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java @@ -50,7 +50,7 @@ public final class SafetyCenterConfig implements Parcelable { mSafetySourcesGroups = safetySourcesGroups; } - /** Returns the list of safety sources groups in the configuration. */ + /** Returns the list of {@link SafetySourcesGroup}s in the configuration. */ @NonNull public List<SafetySourcesGroup> getSafetySourcesGroups() { return mSafetySourcesGroups; @@ -83,7 +83,7 @@ public final class SafetyCenterConfig implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelableList(mSafetySourcesGroups, flags); + dest.writeTypedList(mSafetySourcesGroups); } @NonNull @@ -91,11 +91,14 @@ public final class SafetyCenterConfig implements Parcelable { new Parcelable.Creator<SafetyCenterConfig>() { @Override public SafetyCenterConfig createFromParcel(Parcel in) { - List<SafetySourcesGroup> safetySourcesGroups = new ArrayList<>(); - in.readParcelableList(safetySourcesGroups, - SafetySourcesGroup.class.getClassLoader()); - return new SafetyCenterConfig( - Collections.unmodifiableList(safetySourcesGroups)); + List<SafetySourcesGroup> safetySourcesGroups = + in.createTypedArrayList(SafetySourcesGroup.CREATOR); + Builder builder = new Builder(); + // TODO(b/224513050): Consider simplifying by adding a new API to the builder. + for (int i = 0; i < safetySourcesGroups.size(); i++) { + builder.addSafetySourcesGroup(safetySourcesGroups.get(i)); + } + return builder.build(); } @Override @@ -113,7 +116,7 @@ public final class SafetyCenterConfig implements Parcelable { public Builder() { } - /** Adds a safety source group to the configuration. */ + /** Adds a {@link SafetySourcesGroup} to the configuration. */ @NonNull public Builder addSafetySourcesGroup(@NonNull SafetySourcesGroup safetySourcesGroup) { mSafetySourcesGroups.add(requireNonNull(safetySourcesGroup)); diff --git a/framework-s/java/android/safetycenter/config/SafetyCenterConfigParser.java b/framework-s/java/android/safetycenter/config/SafetyCenterConfigParser.java index 75ca73758..304e17a72 100644 --- a/framework-s/java/android/safetycenter/config/SafetyCenterConfigParser.java +++ b/framework-s/java/android/safetycenter/config/SafetyCenterConfigParser.java @@ -74,6 +74,8 @@ final class SafetyCenterConfigParser { private static final String ATTR_SAFETY_SOURCE_LOGGING_ALLOWED = "loggingAllowed"; private static final String ATTR_SAFETY_SOURCE_REFRESH_ON_PAGE_OPEN_ALLOWED = "refreshOnPageOpenAllowed"; + private static final String ATTR_SAFETY_SOURCE_AUTOMATIC_NOTIFICATION_FROM_ISSUE_ALLOWED = + "automaticNotificationFromIssueAllowed"; private static final String ENUM_STATELESS_ICON_TYPE_NONE = "none"; private static final String ENUM_STATELESS_ICON_TYPE_PRIVACY = "privacy"; @@ -241,6 +243,11 @@ final class SafetyCenterConfigParser { parseBoolean(parser.getAttributeValue(i), name, parser.getAttributeName(i))); break; + case ATTR_SAFETY_SOURCE_AUTOMATIC_NOTIFICATION_FROM_ISSUE_ALLOWED: + builder.setAutomaticNotificationFromIssueAllowed( + parseBoolean(parser.getAttributeValue(i), name, + parser.getAttributeName(i))); + break; default: throwAttributeUnexpected(name, parser.getAttributeName(i)); } diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java index 2ec6cfdba..36dfb0fa0 100644 --- a/framework-s/java/android/safetycenter/config/SafetySource.java +++ b/framework-s/java/android/safetycenter/config/SafetySource.java @@ -141,6 +141,7 @@ public final class SafetySource implements Parcelable { private final String mBroadcastReceiverClassName; private final boolean mLoggingAllowed; private final boolean mRefreshOnPageOpenAllowed; + private final boolean mAutomaticNotificationFromIssueAllowed; /** Returns the id of this safety source. */ private SafetySource( @@ -157,7 +158,8 @@ public final class SafetySource implements Parcelable { @StringRes int searchTermsResId, @Nullable String broadcastReceiverClassName, boolean loggingAllowed, - boolean refreshOnPageOpenAllowed) { + boolean refreshOnPageOpenAllowed, + boolean automaticNotificationFromIssueAllowed) { mType = type; mId = id; mPackageName = packageName; @@ -172,6 +174,7 @@ public final class SafetySource implements Parcelable { mBroadcastReceiverClassName = broadcastReceiverClassName; mLoggingAllowed = loggingAllowed; mRefreshOnPageOpenAllowed = refreshOnPageOpenAllowed; + mAutomaticNotificationFromIssueAllowed = automaticNotificationFromIssueAllowed; } /** Returns the type of this safety source. */ @@ -310,6 +313,15 @@ public final class SafetySource implements Parcelable { return mRefreshOnPageOpenAllowed; } + /** Returns the automatic notification from issue allowed property of this safety source. */ + public boolean isAutomaticNotificationFromIssueAllowed() { + if (mType == SAFETY_SOURCE_TYPE_STATIC) { + throw new UnsupportedOperationException( + "automaticNotificationFromIssueAllowed unsupported for static safety source"); + } + return mAutomaticNotificationFromIssueAllowed; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -328,7 +340,9 @@ public final class SafetySource implements Parcelable { && mSearchTermsResId == that.mSearchTermsResId && Objects.equals(mBroadcastReceiverClassName, that.mBroadcastReceiverClassName) && mLoggingAllowed == that.mLoggingAllowed - && mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed; + && mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed + && mAutomaticNotificationFromIssueAllowed + == that.mAutomaticNotificationFromIssueAllowed; } @Override @@ -336,7 +350,7 @@ public final class SafetySource implements Parcelable { return Objects.hash(mType, mId, mPackageName, mTitleResId, mTitleForWorkResId, mSummaryResId, mIntentAction, mProfile, mInitialDisplayState, mMaxSeverityLevel, mSearchTermsResId, mBroadcastReceiverClassName, mLoggingAllowed, - mRefreshOnPageOpenAllowed); + mRefreshOnPageOpenAllowed, mAutomaticNotificationFromIssueAllowed); } @Override @@ -356,6 +370,8 @@ public final class SafetySource implements Parcelable { + ", mBroadcastReceiverClassName='" + mBroadcastReceiverClassName + '\'' + ", mLoggingAllowed=" + mLoggingAllowed + ", mRefreshOnPageOpenAllowed=" + mRefreshOnPageOpenAllowed + + ", mAutomaticNotificationFromIssueAllowed=" + + mAutomaticNotificationFromIssueAllowed + '}'; } @@ -380,6 +396,7 @@ public final class SafetySource implements Parcelable { dest.writeString(mBroadcastReceiverClassName); dest.writeBoolean(mLoggingAllowed); dest.writeBoolean(mRefreshOnPageOpenAllowed); + dest.writeBoolean(mAutomaticNotificationFromIssueAllowed); } @NonNull @@ -387,24 +404,22 @@ public final class SafetySource implements Parcelable { new Parcelable.Creator<SafetySource>() { @Override public SafetySource createFromParcel(Parcel in) { - int type = in.readInt(); - String id = in.readString(); - String packageName = in.readString(); - int titleResId = in.readInt(); - int titleForWorkResId = in.readInt(); - int summaryResId = in.readInt(); - String intentAction = in.readString(); - int profile = in.readInt(); - int initialDisplayState = in.readInt(); - int maxSeverityLevel = in.readInt(); - int searchTermsResId = in.readInt(); - String broadcastReceiverClassName = in.readString(); - boolean loggingAllowed = in.readBoolean(); - boolean refreshOnPageOpenAllowed = in.readBoolean(); - return new SafetySource(type, id, packageName, titleResId, titleForWorkResId, - summaryResId, intentAction, profile, initialDisplayState, - maxSeverityLevel, searchTermsResId, broadcastReceiverClassName, - loggingAllowed, refreshOnPageOpenAllowed); + return new Builder(in.readInt()) + .setId(in.readString()) + .setPackageName(in.readString()) + .setTitleResId(in.readInt()) + .setTitleForWorkResId(in.readInt()) + .setSummaryResId(in.readInt()) + .setIntentAction(in.readString()) + .setProfile(in.readInt()) + .setInitialDisplayState(in.readInt()) + .setMaxSeverityLevel(in.readInt()) + .setSearchTermsResId(in.readInt()) + .setBroadcastReceiverClassName(in.readString()) + .setLoggingAllowed(in.readBoolean()) + .setRefreshOnPageOpenAllowed(in.readBoolean()) + .setAutomaticNotificationFromIssueAllowed(in.readBoolean()) + .build(); } @Override @@ -449,6 +464,8 @@ public final class SafetySource implements Parcelable { private Boolean mLoggingAllowed; @Nullable private Boolean mRefreshOnPageOpenAllowed; + @Nullable + private Boolean mAutomaticNotificationFromIssueAllowed; /** Creates a {@link Builder} for a {@link SafetySource}. */ public Builder(@SafetySourceType int type) { @@ -504,14 +521,14 @@ public final class SafetySource implements Parcelable { return this; } - /** Sets the initial display state of this safety source. */ + /** Sets the initial display state of this safety source. Defaults to enabled. */ @NonNull public Builder setInitialDisplayState(@InitialDisplayState int initialDisplayState) { mInitialDisplayState = initialDisplayState; return this; } - /** Sets the maximum severity level of this safety source. */ + /** Sets the maximum severity level of this safety source. Defaults to no maximum. */ @NonNull public Builder setMaxSeverityLevel(int maxSeverityLevel) { mMaxSeverityLevel = maxSeverityLevel; @@ -532,20 +549,34 @@ public final class SafetySource implements Parcelable { return this; } - /** Sets the logging allowed property of this safety source. */ + /** Sets the logging allowed property of this safety source. Defaults to {@code true}. */ @NonNull public Builder setLoggingAllowed(boolean loggingAllowed) { mLoggingAllowed = loggingAllowed; return this; } - /** Sets the refresh on page open allowed property of this safety source. */ + /** + * Sets the refresh on page open allowed property of this safety source. Defaults to {@code + * false}. + */ @NonNull public Builder setRefreshOnPageOpenAllowed(boolean refreshOnPageOpenAllowed) { mRefreshOnPageOpenAllowed = refreshOnPageOpenAllowed; return this; } + /** + * Sets the automatic notification from issue allowed property of this safety source. + * Defaults to {@code false}. + */ + @NonNull + public Builder setAutomaticNotificationFromIssueAllowed( + boolean automaticNotificationFromIssueAllowed) { + mAutomaticNotificationFromIssueAllowed = automaticNotificationFromIssueAllowed; + return this; + } + /** Creates the {@link SafetySource} defined by this {@link Builder}. */ @NonNull public SafetySource build() { @@ -587,10 +618,13 @@ public final class SafetySource implements Parcelable { false, isStatic, true); boolean refreshOnPageOpenAllowed = BuilderUtils.validateBoolean( mRefreshOnPageOpenAllowed, "refreshOnPageOpenAllowed", false, isStatic, false); + boolean automaticNotificationFromIssueAllowed = BuilderUtils.validateBoolean( + mAutomaticNotificationFromIssueAllowed, "automaticNotificationFromIssueAllowed", + false, isStatic, false); return new SafetySource(mType, mId, mPackageName, titleResId, titleForWorkResId, summaryResId, mIntentAction, profile, initialDisplayState, maxSeverityLevel, searchTermsResId, mBroadcastReceiverClassName, loggingAllowed, - refreshOnPageOpenAllowed); + refreshOnPageOpenAllowed, automaticNotificationFromIssueAllowed); } } diff --git a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java index 84ce67748..e76d9a634 100644 --- a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java +++ b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java @@ -163,7 +163,7 @@ public final class SafetySourcesGroup implements Parcelable { return mStatelessIconType; } - /** Returns the list of safety sources in this safety sources group. */ + /** Returns the list of {@link SafetySource}s in this safety sources group. */ @NonNull public List<SafetySource> getSafetySources() { return mSafetySources; @@ -208,7 +208,7 @@ public final class SafetySourcesGroup implements Parcelable { dest.writeInt(mTitleResId); dest.writeInt(mSummaryResId); dest.writeInt(mStatelessIconType); - dest.writeParcelableList(mSafetySources, flags); + dest.writeTypedList(mSafetySources); } @NonNull @@ -216,14 +216,18 @@ public final class SafetySourcesGroup implements Parcelable { new Parcelable.Creator<SafetySourcesGroup>() { @Override public SafetySourcesGroup createFromParcel(Parcel in) { - String id = in.readString(); - int titleResId = in.readInt(); - int summaryResId = in.readInt(); - int statelessIconType = in.readInt(); - List<SafetySource> safetySources = new ArrayList<>(); - in.readParcelableList(safetySources, SafetySource.class.getClassLoader()); - return new SafetySourcesGroup(id, titleResId, summaryResId, statelessIconType, - Collections.unmodifiableList(safetySources)); + Builder builder = new Builder() + .setId(in.readString()) + .setTitleResId(in.readInt()) + .setSummaryResId(in.readInt()) + .setStatelessIconType(in.readInt()); + List<SafetySource> safetySources = + in.createTypedArrayList(SafetySource.CREATOR); + // TODO(b/224513050): Consider simplifying by adding a new API to the builder. + for (int i = 0; i < safetySources.size(); i++) { + builder.addSafetySource(safetySources.get(i)); + } + return builder.build(); } @Override @@ -280,7 +284,7 @@ public final class SafetySourcesGroup implements Parcelable { return this; } - /** Adds a safety source to this safety sources group. */ + /** Adds a {@link SafetySource} to this safety sources group. */ @NonNull public Builder addSafetySource(@NonNull SafetySource safetySource) { mSafetySources.add(requireNonNull(safetySource)); diff --git a/service/java/com/android/safetycenter/SafetyCenterDataTracker.java b/service/java/com/android/safetycenter/SafetyCenterDataTracker.java index 1cce49a86..308f3b161 100644 --- a/service/java/com/android/safetycenter/SafetyCenterDataTracker.java +++ b/service/java/com/android/safetycenter/SafetyCenterDataTracker.java @@ -91,7 +91,7 @@ final class SafetyCenterDataTracker { @Nullable SafetyCenterData setSafetySourceData( @NonNull String safetySourceId, - @NonNull SafetySourceData safetySourceData, + @Nullable SafetySourceData safetySourceData, @NonNull String packageName, @UserIdInt int userId) { if (!configContains(safetySourceId, packageName)) { @@ -101,7 +101,7 @@ final class SafetyCenterDataTracker { Key key = Key.of(safetySourceId, packageName, userId); SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key); - if (safetySourceData.equals(existingSafetySourceData)) { + if (Objects.equals(safetySourceData, existingSafetySourceData)) { return null; } @@ -152,6 +152,25 @@ final class SafetyCenterDataTracker { return getSafetyCenterData(safetyCenterConfig, userId); } + /** + * Returns a default {@link SafetyCenterData} object to be returned when the API is disabled. + */ + @NonNull + static SafetyCenterData getDefaultSafetyCenterData() { + return new SafetyCenterData( + new SafetyCenterStatus.Builder() + .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) + .setTitle(getSafetyCenterStatusTitle( + SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)) + .setSummary(getSafetyCenterStatusSummary( + SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)) + .build(), + emptyList(), + emptyList(), + emptyList() + ); + } + // TODO(b/219702252): Create a more efficient data structure for this, and update it when the // config changes. private boolean configContains( @@ -479,22 +498,6 @@ final class SafetyCenterDataTracker { } } - @NonNull - private static SafetyCenterData getDefaultSafetyCenterData() { - return new SafetyCenterData( - new SafetyCenterStatus.Builder() - .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) - .setTitle(getSafetyCenterStatusTitle( - SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)) - .setSummary(getSafetyCenterStatusSummary( - SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)) - .build(), - emptyList(), - emptyList(), - emptyList() - ); - } - @Nullable private static SafetySourceStatus getSafetySourceStatus( @Nullable SafetySourceData safetySourceData) { diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java index 184d23429..f1bb012e3 100644 --- a/service/java/com/android/safetycenter/SafetyCenterListeners.java +++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java @@ -21,11 +21,11 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.os.Binder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.safetycenter.IOnSafetyCenterDataChangedListener; import android.safetycenter.SafetyCenterData; +import android.safetycenter.SafetyCenterErrorDetails; import android.util.Log; import android.util.SparseArray; @@ -56,13 +56,10 @@ final class SafetyCenterListeners { static void deliverUpdate( @NonNull IOnSafetyCenterDataChangedListener listener, @NonNull SafetyCenterData safetyCenterData) { - final long identity = Binder.clearCallingIdentity(); try { listener.onSafetyCenterDataChanged(safetyCenterData); } catch (RemoteException e) { - Log.e(TAG, "Error delivering SafetyCenterData update to listener", e); - } finally { - Binder.restoreCallingIdentity(identity); + Log.e(TAG, "Error delivering SafetyCenterData to listener", e); } } @@ -85,6 +82,39 @@ final class SafetyCenterListeners { listeners.finishBroadcast(); } + /** + * Delivers a {@link SafetyCenterErrorDetails} update to a single {@link + * IOnSafetyCenterDataChangedListener}. + */ + private static void deliverError( + @NonNull IOnSafetyCenterDataChangedListener listener, + @NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) { + try { + listener.onError(safetyCenterErrorDetails); + } catch (RemoteException e) { + Log.e(TAG, "Error delivering SafetyCenterErrorDetails to listener", e); + } + } + + /** + * Delivers a {@link SafetyCenterErrorDetails} update to a {@link RemoteCallbackList} of {@link + * IOnSafetyCenterDataChangedListener}. + * + * <p>Registering or unregistering {@link IOnSafetyCenterDataChangedListener} on the underlying + * {@link RemoteCallbackList} on another thread while an update is happening is safe as this is + * handled by the {@link RemoteCallbackList} already (as well as listeners death). + */ + static void deliverError( + @NonNull RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners, + @NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) { + int i = listeners.beginBroadcast(); + while (i > 0) { + i--; + deliverError(listeners.getBroadcastItem(i), safetyCenterErrorDetails); + } + listeners.finishBroadcast(); + } + /** Adds a {@link IOnSafetyCenterDataChangedListener} for the given {@code userId}. */ void addListener( @NonNull IOnSafetyCenterDataChangedListener listener, diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index 98edc8a62..6096925b8 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -39,12 +39,14 @@ import android.provider.DeviceConfig; import android.safetycenter.IOnSafetyCenterDataChangedListener; import android.safetycenter.ISafetyCenterManager; import android.safetycenter.SafetyCenterData; +import android.safetycenter.SafetyCenterErrorDetails; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; -import android.safetycenter.SafetySourceError; +import android.safetycenter.SafetySourceErrorDetails; import android.safetycenter.config.SafetyCenterConfig; import android.safetycenter.config.SafetySource; import android.safetycenter.config.SafetySourcesGroup; +import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.RequiresApi; @@ -111,17 +113,7 @@ public final class SafetyCenterService extends SystemService { // TODO(b/214568975): Decide if we should disable safety center if there is a problem // reading the config. - // We don't require the caller to have READ_DEVICE_CONFIG permission. - final long callingId = Binder.clearCallingIdentity(); - try { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_CENTER_ENABLED, - /* defaultValue = */ false) - && getSafetyCenterConfigValue(); - } finally { - Binder.restoreCallingIdentity(callingId); - } + return isApiEnabled(); } @Override @@ -138,6 +130,9 @@ public final class SafetyCenterService extends SystemService { // TODO(b/205706756): Security: check certs? getContext().enforceCallingOrSelfPermission(SEND_SAFETY_CENTER_UPDATE, "setSafetySourceData"); + if (!checkApiEnabled("setSafetySourceData")) { + return; + } // TODO(b/218812582): Validate the SafetySourceData. SafetyCenterData safetyCenterData; @@ -175,6 +170,9 @@ public final class SafetyCenterService extends SystemService { // TODO(b/205706756): Security: check certs? getContext().enforceCallingOrSelfPermission( SEND_SAFETY_CENTER_UPDATE, "getSafetySourceData"); + if (!checkApiEnabled("getSafetySourceData")) { + return null; + } synchronized (mApiLock) { return mSafetyCenterDataTracker.getSafetySourceData(safetySourceId, packageName, @@ -185,7 +183,7 @@ public final class SafetyCenterService extends SystemService { @Override public void reportSafetySourceError( @NonNull String safetySourceId, - @NonNull SafetySourceError error, + @NonNull SafetySourceErrorDetails errorDetails, @NonNull String packageName, @UserIdInt int userId) { mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName); @@ -194,7 +192,17 @@ public final class SafetyCenterService extends SystemService { userId, false, "reportSafetySourceError", getContext()); getContext().enforceCallingOrSelfPermission( SEND_SAFETY_CENTER_UPDATE, "reportSafetySourceError"); + if (!checkApiEnabled("reportSafetySourceError")) { + return; + } + // TODO(b/218379298): Add implementation + RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners; + synchronized (mApiLock) { + listeners = mSafetyCenterListeners.getListeners(userId); + } + + SafetyCenterListeners.deliverError(listeners, new SafetyCenterErrorDetails("Error")); } @Override @@ -206,6 +214,9 @@ public final class SafetyCenterService extends SystemService { userId, false, "refreshSafetySources", getContext()); getContext().enforceCallingPermission( MANAGE_SAFETY_CENTER, "refreshSafetySources"); + if (!checkApiEnabled("refreshSafetySources")) { + return; + } // We don't require the caller to have INTERACT_ACROSS_USERS and // START_FOREGROUND_SERVICES_FROM_BACKGROUND permissions. @@ -227,6 +238,9 @@ public final class SafetyCenterService extends SystemService { userId, false, "getSafetyCenterData", getContext()); getContext().enforceCallingOrSelfPermission( MANAGE_SAFETY_CENTER, "getSafetyCenterData"); + if (!checkApiEnabled("getSafetyCenterData")) { + return SafetyCenterDataTracker.getDefaultSafetyCenterData(); + } synchronized (mApiLock) { return mSafetyCenterDataTracker.getSafetyCenterData(userId); @@ -242,6 +256,9 @@ public final class SafetyCenterService extends SystemService { userId, false, "addOnSafetyCenterDataChangedListener", getContext()); getContext().enforceCallingOrSelfPermission( MANAGE_SAFETY_CENTER, "addOnSafetyCenterDataChangedListener"); + if (!checkApiEnabled("addOnSafetyCenterDataChangedListener")) { + return; + } SafetyCenterData safetyCenterData; synchronized (mApiLock) { @@ -263,6 +280,9 @@ public final class SafetyCenterService extends SystemService { userId, false, "removeOnSafetyCenterDataChangedListener", getContext()); getContext().enforceCallingOrSelfPermission( MANAGE_SAFETY_CENTER, "removeOnSafetyCenterDataChangedListener"); + if (!checkApiEnabled("removeOnSafetyCenterDataChangedListener")) { + return; + } synchronized (mApiLock) { mSafetyCenterListeners.removeListener(listener, userId); @@ -270,26 +290,32 @@ public final class SafetyCenterService extends SystemService { } @Override - public void dismissSafetyIssue(String issueId, @UserIdInt int userId) { + public void dismissSafetyCenterIssue(String issueId, @UserIdInt int userId) { // TODO(b/217235899): Finalize cross-user behavior. PermissionUtils.enforceCrossUserPermission( - userId, false, "dismissSafetyIssue", getContext()); + userId, false, "dismissSafetyCenterIssue", getContext()); getContext().enforceCallingOrSelfPermission( - MANAGE_SAFETY_CENTER, "dismissSafetyIssue"); + MANAGE_SAFETY_CENTER, "dismissSafetyCenterIssue"); + if (!checkApiEnabled("dismissSafetyCenterIssue")) { + return; + } // TODO(b/202387059): Implement issue dismissal. } @Override - public void executeAction( + public void executeSafetyCenterIssueAction( @NonNull String safetyCenterIssueId, @NonNull String safetyCenterActionId, @UserIdInt int userId) { // TODO(b/217235899): Finalize cross-user behavior. PermissionUtils.enforceCrossUserPermission( - userId, false, "executeAction", getContext()); + userId, false, "executeSafetyCenterIssueAction", getContext()); getContext().enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, - "executeAction"); + "executeSafetyCenterIssueAction"); + if (!checkApiEnabled("executeSafetyCenterIssueAction")) { + return; + } // TODO(b/218379298): Add implementation } @@ -297,6 +323,9 @@ public final class SafetyCenterService extends SystemService { public void clearAllSafetySourceData() { getContext().enforceCallingOrSelfPermission( MANAGE_SAFETY_CENTER, "clearAllSafetySourceData"); + if (!checkApiEnabled("clearAllSafetySourceData")) { + return; + } synchronized (mApiLock) { mSafetyCenterDataTracker.clear(); @@ -308,6 +337,9 @@ public final class SafetyCenterService extends SystemService { @NonNull SafetyCenterConfig safetyCenterConfig) { getContext().enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, "setSafetyCenterConfigOverride"); + if (!checkApiEnabled("setSafetyCenterConfigOverride")) { + return; + } synchronized (mRefreshLock) { // TODO(b/217944317): Implement properly by overriding config in @@ -335,6 +367,9 @@ public final class SafetyCenterService extends SystemService { public void clearSafetyCenterConfigOverride() { getContext().enforceCallingOrSelfPermission( MANAGE_SAFETY_CENTER, "clearSafetyCenterConfigOverride"); + if (!checkApiEnabled("clearSafetyCenterConfigOverride")) { + return; + } synchronized (mRefreshLock) { mSafetyCenterRefreshManager @@ -342,6 +377,23 @@ public final class SafetyCenterService extends SystemService { } } + private boolean isApiEnabled() { + return getSafetyCenterConfigValue() && getDeviceConfigSafetyCenterEnabledProperty(); + } + + private boolean getDeviceConfigSafetyCenterEnabledProperty() { + // This call requires the READ_DEVICE_CONFIG permission. + final long callingId = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_SAFETY_CENTER_ENABLED, + /* defaultValue = */ false); + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + private boolean getSafetyCenterConfigValue() { return getContext().getResources().getBoolean(Resources.getSystem().getIdentifier( "config_enableSafetyCenter", @@ -363,5 +415,13 @@ public final class SafetyCenterService extends SystemService { throw new SecurityException(message + " requires any of: " + Arrays.toString(permissions) + ", but none were granted"); } + + private boolean checkApiEnabled(@NonNull String message) { + if (!isApiEnabled()) { + Log.w(TAG, String.format("Called %s, but Safety Center is disabled", message)); + return false; + } + return true; + } } } diff --git a/tests/cts/safetycenter/AndroidManifest.xml b/tests/cts/safetycenter/AndroidManifest.xml index cfa22f72b..4a784753b 100644 --- a/tests/cts/safetycenter/AndroidManifest.xml +++ b/tests/cts/safetycenter/AndroidManifest.xml @@ -18,13 +18,14 @@ package="android.safetycenter.cts"> <application> - <uses-library android:name="android.test.runner"/> - - <receiver android:name=".SafetySourceBroadcastReceiver" android:exported="false"> + <receiver android:name="android.safetycenter.testing.SafetySourceBroadcastReceiver" + android:exported="false"> <intent-filter> <action android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES"/> </intent-filter> </receiver> + + <uses-library android:name="android.test.runner"/> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:label="CTS tests for SafetyCenter" diff --git a/tests/cts/safetycenter/res/xml/config_static_safety_source_with_notification.xml b/tests/cts/safetycenter/res/xml/config_static_safety_source_with_notification.xml new file mode 100644 index 000000000..478fabd4d --- /dev/null +++ b/tests/cts/safetycenter/res/xml/config_static_safety_source_with_notification.xml @@ -0,0 +1,16 @@ +<safety-center-config> + <safety-sources-config> + <safety-sources-group + id="id" + title="@string/reference" + summary="@string/reference"> + <static-safety-source + id="id" + title="@string/reference" + summary="@string/reference" + intentAction="intent" + profile="primary_profile_only" + automaticNotificationFromIssueAllowed="true"/> + </safety-sources-group> + </safety-sources-config> +</safety-center-config> diff --git a/tests/cts/safetycenter/res/xml/config_valid.xml b/tests/cts/safetycenter/res/xml/config_valid.xml index e2f9e7703..e25135b41 100644 --- a/tests/cts/safetycenter/res/xml/config_valid.xml +++ b/tests/cts/safetycenter/res/xml/config_valid.xml @@ -25,7 +25,8 @@ searchTerms="@string/reference" broadcastReceiverClassName="broadcast" loggingAllowed="false" - refreshOnPageOpenAllowed="true"/> + refreshOnPageOpenAllowed="true" + automaticNotificationFromIssueAllowed="true"/> <dynamic-safety-source id="dynamic_disabled" packageName="package" @@ -70,7 +71,8 @@ maxSeverityLevel="300" broadcastReceiverClassName="broadcast" loggingAllowed="false" - refreshOnPageOpenAllowed="true"/> + refreshOnPageOpenAllowed="true" + automaticNotificationFromIssueAllowed="true"/> </safety-sources-group> <safety-sources-group id="mixed" diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParseExceptionTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParseExceptionTest.kt new file mode 100644 index 000000000..3a973e382 --- /dev/null +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParseExceptionTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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 android.safetycenter.config.cts + +import android.os.Build.VERSION_CODES.TIRAMISU +import android.safetycenter.config.ParseException +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** CTS tests for [ParseException]. */ +@RunWith(AndroidJUnit4::class) +@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu") +class ParseExceptionTest { + @Test + fun propagatesMessage() { + val message = "error message" + + val exception = ParseException(message) + + assertThat(exception).hasMessageThat().isEqualTo(message) + assertThat(exception).hasCauseThat().isNull() + } + + @Test + fun propagatesMessageAndCause() { + val message = "error message" + val cause = Exception("error message for cause") + + val exception = ParseException(message, cause) + + assertThat(exception).hasMessageThat().isEqualTo(message) + assertThat(exception).hasCauseThat().isEqualTo(cause) + } +}
\ No newline at end of file diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigInvalidTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigInvalidTest.kt index d276865e4..0ef65ac20 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigInvalidTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigInvalidTest.kt @@ -17,10 +17,12 @@ package android.safetycenter.config.cts import android.content.Context +import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.config.ParseException import android.safetycenter.config.SafetyCenterConfig import android.safetycenter.cts.R import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Test @@ -28,6 +30,7 @@ import org.junit.runners.Parameterized import org.junit.runner.RunWith @RunWith(Parameterized::class) +@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu") class ParserConfigInvalidTest { private val context: Context = getApplicationContext() @@ -354,6 +357,12 @@ class ParserConfigInvalidTest { "Prohibited attribute loggingAllowed present" ), Params( + "ConfigStaticSafetySourceWithNotification", + R.xml.config_static_safety_source_with_notification, + "Element static-safety-source invalid", + "Prohibited attribute automaticNotificationFromIssueAllowed present" + ), + Params( "ConfigStaticSafetySourceWithPackage", R.xml.config_static_safety_source_with_package, "Element static-safety-source invalid", diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigValidTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigValidTest.kt index 9190b6d0a..299f7d9e7 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigValidTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/ParserConfigValidTest.kt @@ -17,17 +17,20 @@ package android.safetycenter.config.cts import android.content.Context +import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.config.SafetyCenterConfig import android.safetycenter.config.SafetySource import android.safetycenter.config.SafetySourcesGroup import android.safetycenter.cts.R import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertEquals +import androidx.test.filters.SdkSuppress +import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) +@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu") class ParserConfigValidTest { private val context: Context = getApplicationContext() @@ -62,6 +65,7 @@ class ParserConfigValidTest { .setBroadcastReceiverClassName("broadcast") .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build()) .addSafetySource(SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC) .setId("dynamic_disabled") @@ -113,6 +117,7 @@ class ParserConfigValidTest { .setBroadcastReceiverClassName("broadcast") .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build()) .build()) .addSafetySourcesGroup(SafetySourcesGroup.Builder() @@ -140,6 +145,6 @@ class ParserConfigValidTest { .build()) .build()) .build() - assertEquals(expected, SafetyCenterConfig.fromXml(parser)) + assertThat(SafetyCenterConfig.fromXml(parser)).isEqualTo(expected) } } diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetyCenterConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetyCenterConfigTest.kt index 4c5723343..c42192ed8 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetyCenterConfigTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetyCenterConfigTest.kt @@ -18,8 +18,8 @@ package android.safetycenter.config.cts import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.config.SafetyCenterConfig -import android.safetycenter.testers.AnyTester -import android.safetycenter.testers.ParcelableTester +import android.safetycenter.testing.AnyTester +import android.safetycenter.testing.ParcelableTester import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourceTest.kt index f2a96df7d..830de10f4 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourceTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourceTest.kt @@ -19,8 +19,8 @@ package android.safetycenter.config.cts import android.content.res.Resources import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.config.SafetySource -import android.safetycenter.testers.AnyTester -import android.safetycenter.testers.ParcelableTester +import android.safetycenter.testing.AnyTester +import android.safetycenter.testing.ParcelableTester import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat @@ -262,6 +262,22 @@ class SafetySourceTest { } @Test + fun isAutomaticNotificationFromIssueAllowed_returnsAutoNotificationFromIssueAllowedOrThrows() { + assertThat(DYNAMIC_BAREBONE.isAutomaticNotificationFromIssueAllowed).isEqualTo(false) + assertThat(DYNAMIC_ALL_OPTIONAL.isAutomaticNotificationFromIssueAllowed).isEqualTo(true) + assertThat(DYNAMIC_DISABLED.isAutomaticNotificationFromIssueAllowed).isEqualTo(false) + assertThat(DYNAMIC_HIDDEN.isAutomaticNotificationFromIssueAllowed).isEqualTo(false) + assertThrows(UnsupportedOperationException::class.java) { + STATIC_BAREBONE.isAutomaticNotificationFromIssueAllowed + } + assertThrows(UnsupportedOperationException::class.java) { + STATIC_ALL_OPTIONAL.isAutomaticNotificationFromIssueAllowed + } + assertThat(ISSUE_ONLY_BAREBONE.isAutomaticNotificationFromIssueAllowed).isEqualTo(false) + assertThat(ISSUE_ONLY_ALL_OPTIONAL.isAutomaticNotificationFromIssueAllowed).isEqualTo(true) + } + + @Test fun describeContents_returns0() { assertThat(DYNAMIC_BAREBONE.describeContents()).isEqualTo(0) assertThat(DYNAMIC_ALL_OPTIONAL.describeContents()).isEqualTo(0) @@ -329,6 +345,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalCopy) } @@ -362,6 +379,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -382,6 +400,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -402,6 +421,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -422,6 +442,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -442,6 +463,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -462,6 +484,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -493,6 +516,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -513,6 +537,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -533,6 +558,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -553,12 +579,13 @@ class SafetySourceTest { .setBroadcastReceiverClassName("other") .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @Test - fun hashCode_equals_toString_withDifferentLoggingAlloweds_areNotEqual() { + fun hashCode_equals_toString_withDifferentLoggingAllowed_areNotEqual() { val dynamicAllOptionalAlt = SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC) .setId(DYNAMIC_ALL_OPTIONAL_ID) .setPackageName(PACKAGE_NAME) @@ -573,12 +600,13 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(true) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @Test - fun hashCode_equals_toString_withDifferentRefreshOnPageOpenAlloweds_areNotEqual() { + fun hashCode_equals_toString_withDifferentRefreshOnPageOpenAllowed_areNotEqual() { val dynamicAllOptionalAlt = SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC) .setId(DYNAMIC_ALL_OPTIONAL_ID) .setPackageName(PACKAGE_NAME) @@ -593,6 +621,28 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(false) + .setAutomaticNotificationFromIssueAllowed(true) + .build() + AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) + } + + @Test + fun hashCode_equals_toString_withDifferentAutomaticNotificationFromIssueAllowed_areNotEqual() { + val dynamicAllOptionalAlt = SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC) + .setId(DYNAMIC_ALL_OPTIONAL_ID) + .setPackageName(PACKAGE_NAME) + .setTitleResId(REFERENCE_RES_ID) + .setTitleForWorkResId(REFERENCE_RES_ID) + .setSummaryResId(REFERENCE_RES_ID) + .setIntentAction(INTENT_ACTION) + .setProfile(SafetySource.PROFILE_ALL) + .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED) + .setMaxSeverityLevel(MAX_SEVERITY_LEVEL) + .setSearchTermsResId(REFERENCE_RES_ID) + .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) + .setLoggingAllowed(false) + .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(false) .build() AnyTester.assertThatRepresentationsAreNotEqual(DYNAMIC_ALL_OPTIONAL, dynamicAllOptionalAlt) } @@ -638,6 +688,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() private val DYNAMIC_DISABLED = @@ -694,6 +745,7 @@ class SafetySourceTest { .setBroadcastReceiverClassName(BROADCAST_RECEIVER_CLASS_NAME) .setLoggingAllowed(false) .setRefreshOnPageOpenAllowed(true) + .setAutomaticNotificationFromIssueAllowed(true) .build() } } diff --git a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourcesGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourcesGroupTest.kt index 2e61411bc..09c0de91f 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourcesGroupTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/config/cts/SafetySourcesGroupTest.kt @@ -19,8 +19,8 @@ package android.safetycenter.config.cts import android.content.res.Resources import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.config.SafetySourcesGroup -import android.safetycenter.testers.AnyTester -import android.safetycenter.testers.ParcelableTester +import android.safetycenter.testing.AnyTester +import android.safetycenter.testing.ParcelableTester import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt index 0adf45852..57a1c2c71 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt @@ -20,11 +20,15 @@ import android.content.Context import android.content.Intent import android.content.Intent.ACTION_SAFETY_CENTER import android.os.Build.VERSION_CODES.TIRAMISU +import android.safetycenter.testing.SafetyCenterFlags +import android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import android.support.test.uiautomator.By import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject +import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -33,13 +37,36 @@ import org.junit.runner.RunWith class SafetyCenterActivityTest { private val context: Context = getApplicationContext() + @Before + fun assumeDeviceSupportsSafetyCenterToRunTests() { + assumeTrue(context.deviceSupportsSafetyCenter()) + } + @Test - fun launchActivity_showsSecurityAndPrivacyTitle() { - context.startActivity( - Intent(ACTION_SAFETY_CENTER).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ) + fun launchActivity_withFlagEnabled_showsSecurityAndPrivacyTitle() { + SafetyCenterFlags.setSafetyCenterEnabled(true) + + startSafetyCenterActivity() // CollapsingToolbar title can't be found by text, so using description instead. waitFindObject(By.desc("Security & Privacy")) } + + @Test + fun launchActivity_withFlagDisabled_showsSecurityTitle() { + SafetyCenterFlags.setSafetyCenterEnabled(false) + + startSafetyCenterActivity() + + // CollapsingToolbar title can't be found by text, so using description instead. + waitFindObject(By.desc("Security")) + } + + private fun startSafetyCenterActivity() { + context.startActivity( + Intent(ACTION_SAFETY_CENTER) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + } } diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterApisWithShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterApisWithShellPermissions.kt deleted file mode 100644 index c3c7e7266..000000000 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterApisWithShellPermissions.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2022 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 android.safetycenter.cts - -import android.Manifest.permission.MANAGE_SAFETY_CENTER -import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE -import android.safetycenter.SafetyCenterData -import android.safetycenter.SafetyCenterManager -import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener -import android.safetycenter.SafetySourceData -import android.safetycenter.SafetyEvent -import android.safetycenter.config.SafetyCenterConfig -import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity -import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity -import java.util.concurrent.Executor - -/** - * Call {@link SafetyCenterManager#setSafetySourceData} adopting Shell's - * {@link SEND_SAFETY_CENTER_UPDATE} permission. - */ -fun SafetyCenterManager.setSafetySourceDataWithPermission( - safetySourceId: String, - safetySourceData: SafetySourceData, - safetyEvent: SafetyEvent -) = - runWithShellPermissionIdentity({ - setSafetySourceData(safetySourceId, safetySourceData, safetyEvent) - }, SEND_SAFETY_CENTER_UPDATE) - -/** - * Call {@link SafetyCenterManager#getSafetySourceData} adopting Shell's - * {@link SEND_SAFETY_CENTER_UPDATE} permission. - */ -fun SafetyCenterManager.getSafetySourceDataWithPermission(id: String): SafetySourceData? = - callWithShellPermissionIdentity({ - getSafetySourceData(id) - }, SEND_SAFETY_CENTER_UPDATE) - -/** - * Call {@link SafetyCenterManager#isSafetyCenterEnabled} adopting Shell's - * {@link SEND_SAFETY_CENTER_UPDATE} permission. - */ -fun SafetyCenterManager.isSafetyCenterEnabledWithPermission(): Boolean = - callWithShellPermissionIdentity({ - isSafetyCenterEnabled - }, SEND_SAFETY_CENTER_UPDATE) - -/** - * Call {@link SafetyCenterManager#refreshSafetySources} adopting Shell's - * {@link MANAGE_SAFETY_CENTER} permission (required for - * {@link SafetyCenterManager#refreshSafetySources}). - */ -fun SafetyCenterManager.refreshSafetySourcesWithPermission(refreshReason: Int) = - runWithShellPermissionIdentity({ - refreshSafetySources(refreshReason) - }, MANAGE_SAFETY_CENTER) - -fun SafetyCenterManager.getSafetyCenterDataWithPermission(): SafetyCenterData = - runWithShellPermissionIdentity(::getSafetyCenterData, MANAGE_SAFETY_CENTER) - -fun SafetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( - executor: Executor, - listener: OnSafetyCenterDataChangedListener -) = - runWithShellPermissionIdentity({ - addOnSafetyCenterDataChangedListener(executor, listener) - }, MANAGE_SAFETY_CENTER) - -fun SafetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission( - listener: OnSafetyCenterDataChangedListener -) = - runWithShellPermissionIdentity({ - removeOnSafetyCenterDataChangedListener(listener) - }, MANAGE_SAFETY_CENTER) - -/** - * Call {@link SafetyCenterManager#clearAllSafetySourceData} adopting Shell's - * {@link MANAGE_SAFETY_CENTER} permission. - */ -fun SafetyCenterManager.clearAllSafetySourceDataWithPermission() = - runWithShellPermissionIdentity({ - clearAllSafetySourceData() - }, MANAGE_SAFETY_CENTER) - -fun SafetyCenterManager.setSafetyCenterConfigOverrideWithPermission( - safetyCenterConfig: SafetyCenterConfig -) = - runWithShellPermissionIdentity({ - setSafetyCenterConfigOverride(safetyCenterConfig) - }, MANAGE_SAFETY_CENTER) - -fun SafetyCenterManager.clearSafetyCenterConfigOverrideWithPermission() = - runWithShellPermissionIdentity({ - clearSafetyCenterConfigOverride() - }, MANAGE_SAFETY_CENTER) diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt new file mode 100644 index 000000000..e3e5d9c6c --- /dev/null +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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 android.safetycenter.cts + +import android.os.Build.VERSION_CODES.TIRAMISU +import android.os.Parcel +import android.safetycenter.SafetyCenterErrorDetails +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu") +class SafetyCenterErrorDetailsTest { + + val errorDetails1 = SafetyCenterErrorDetails("an error message") + val errorDetails2 = SafetyCenterErrorDetails("another error message") + + @Test + fun getErrorMessage_returnsErrorMessage() { + assertThat(errorDetails1.errorMessage).isEqualTo("an error message") + assertThat(errorDetails2.errorMessage).isEqualTo("another error message") + } + + @Test + fun describeContents_returns0() { + assertThat(errorDetails1.describeContents()).isEqualTo(0) + } + + @Test + fun createFromParcel_withWriteToParcel_returnsEquivalentObject() { + val parcel = Parcel.obtain() + + errorDetails1.writeToParcel(parcel, /* flags= */ 0) + parcel.setDataPosition(0) + + val fromParcel = SafetyCenterErrorDetails.CREATOR.createFromParcel(parcel) + parcel.recycle() + + assertThat(fromParcel).isEqualTo(errorDetails1) + } + + @Test + fun equals_hashCode_toString_equalByReference_areEqual() { + assertThat(errorDetails1).isEqualTo(errorDetails1) + assertThat(errorDetails1.hashCode()).isEqualTo(errorDetails1.hashCode()) + assertThat(errorDetails1.toString()).isEqualTo(errorDetails1.toString()) + } + + @Test + fun equals_hashCode_toString_equalByValue_areEqual() { + val errorDetails = SafetyCenterErrorDetails("an error message") + val equivalentErrorDetails = SafetyCenterErrorDetails("an error message") + + assertThat(errorDetails).isEqualTo(equivalentErrorDetails) + assertThat(errorDetails.hashCode()).isEqualTo(equivalentErrorDetails.hashCode()) + assertThat(errorDetails.toString()).isEqualTo(equivalentErrorDetails.toString()) + } + + @Test + fun equals_toString_withDifferentErrorMessages_areNotEqual() { + val errorDetails = SafetyCenterErrorDetails("an error message") + val differentErrorDetails = SafetyCenterErrorDetails("a different error message") + + assertThat(errorDetails).isNotEqualTo(differentErrorDetails) + assertThat(errorDetails.toString()).isNotEqualTo(differentErrorDetails.toString()) + } +}
\ No newline at end of file diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorTest.kt deleted file mode 100644 index 06fd6d5cd..000000000 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2022 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 android.safetycenter.cts - -import android.os.Parcel -import android.safetycenter.SafetyCenterError -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SafetyCenterErrorTest { - - val error1 = SafetyCenterError("an error message") - val error2 = SafetyCenterError("another error message") - - @Test - fun getErrorMessage_returnsErrorMessage() { - assertThat(error1.errorMessage).isEqualTo("an error message") - assertThat(error2.errorMessage).isEqualTo("another error message") - } - - @Test - fun describeContents_returns0() { - assertThat(error1.describeContents()).isEqualTo(0) - } - - @Test - fun createFromParcel_withWriteToParcel_returnsEquivalentObject() { - val parcel = Parcel.obtain() - - error1.writeToParcel(parcel, /* flags= */ 0) - parcel.setDataPosition(0) - - val fromParcel = SafetyCenterError.CREATOR.createFromParcel(parcel) - parcel.recycle() - - assertThat(fromParcel).isEqualTo(error1) - } - - @Test - fun equals_hashCode_toString_equalByReference_areEqual() { - assertThat(error1).isEqualTo(error1) - assertThat(error1.hashCode()).isEqualTo(error1.hashCode()) - assertThat(error1.toString()).isEqualTo(error1.toString()) - } - - @Test - fun equals_hashCode_toString_equalByValue_areEqual() { - val error = SafetyCenterError("an error message") - val equivalentError = SafetyCenterError("an error message") - - assertThat(error).isEqualTo(equivalentError) - assertThat(error.hashCode()).isEqualTo(equivalentError.hashCode()) - assertThat(error.toString()).isEqualTo(equivalentError.toString()) - } - - @Test - fun equals_toString_withDifferentErrorMessages_areNotEqual() { - val error = SafetyCenterError("an error message") - val differentError = SafetyCenterError("a different error message") - - assertThat(error).isNotEqualTo(differentError) - assertThat(error.toString()).isNotEqualTo(differentError.toString()) - } -}
\ No newline at end of file diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt index a8428049c..819124611 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt @@ -48,15 +48,15 @@ class SafetyCenterIssueTest { val action1 = SafetyCenterIssue.Action.Builder("action_id_1") .setLabel("an action") .setPendingIntent(pendingIntent1) - .setResolving(true) - .setInFlight(true) + .setWillResolve(true) + .setIsInFlight(true) .setSuccessMessage("a success message") .build() val action2 = SafetyCenterIssue.Action.Builder("action_id_2") .setLabel("another action") .setPendingIntent(pendingIntent2) - .setResolving(false) - .setInFlight(false) + .setWillResolve(false) + .setIsInFlight(false) .build() val issue1 = SafetyCenterIssue.Builder("issue_id") @@ -330,9 +330,9 @@ class SafetyCenterIssueTest { } @Test - fun action_isResolving_returnsIsResolving() { - assertThat(action1.isResolving).isTrue() - assertThat(action2.isResolving).isFalse() + fun action_willResolve_returnsWillResolve() { + assertThat(action1.willResolve()).isTrue() + assertThat(action2.willResolve()).isFalse() } @Test @@ -377,15 +377,15 @@ class SafetyCenterIssueTest { val action = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(true) - .setInFlight(true) + .setWillResolve(true) + .setIsInFlight(true) .setSuccessMessage("a success message") .build() val equivalentAction = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(true) - .setInFlight(true) + .setWillResolve(true) + .setIsInFlight(true) .setSuccessMessage("a success message") .build() @@ -445,17 +445,17 @@ class SafetyCenterIssueTest { } @Test - fun action_equals_toString_differentResovlingValues_areNotEqual() { + fun action_equals_toString_differentWillResolveValues_areNotEqual() { val action = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(true) + .setWillResolve(true) .setSuccessMessage("a success message") .build() val differentAction = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(false) + .setWillResolve(false) .setSuccessMessage("a success message") .build() @@ -468,15 +468,15 @@ class SafetyCenterIssueTest { val action = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(true) - .setInFlight(true) + .setWillResolve(true) + .setIsInFlight(true) .setSuccessMessage("a success message") .build() val differentAction = SafetyCenterIssue.Action.Builder("an_id") .setLabel("a label") .setPendingIntent(pendingIntent1) - .setResolving(true) - .setInFlight(false) + .setWillResolve(true) + .setIsInFlight(false) .setSuccessMessage("a success message") .build() diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt index bd58849cc..af785eecf 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt @@ -23,29 +23,47 @@ import android.app.PendingIntent.FLAG_IMMUTABLE import android.content.Context import android.content.Intent import android.content.Intent.ACTION_SAFETY_CENTER -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.res.Resources import android.os.Build.VERSION_CODES.TIRAMISU -import android.provider.DeviceConfig import android.safetycenter.SafetyCenterData +import android.safetycenter.SafetyCenterErrorDetails import android.safetycenter.SafetyCenterManager +import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK -import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener -import android.safetycenter.SafetySourceData -import android.safetycenter.SafetySourceIssue +import android.safetycenter.SafetyCenterStatus +import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN import android.safetycenter.SafetyEvent import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED +import android.safetycenter.SafetySourceData +import android.safetycenter.SafetySourceErrorDetails +import android.safetycenter.SafetySourceIssue import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_CRITICAL_WARNING import android.safetycenter.SafetySourceStatus import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_CRITICAL_WARNING +import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_NONE +import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_OK import android.safetycenter.config.SafetyCenterConfig import android.safetycenter.config.SafetySource +import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC import android.safetycenter.config.SafetySourcesGroup +import android.safetycenter.testing.SafetyCenterFlags +import android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter +import android.safetycenter.testing.SafetySourceBroadcastReceiver +import android.safetycenter.testing.addOnSafetyCenterDataChangedListenerWithPermission +import android.safetycenter.testing.clearAllSafetySourceDataWithPermission +import android.safetycenter.testing.clearSafetyCenterConfigOverrideWithPermission +import android.safetycenter.testing.getSafetyCenterDataWithPermission +import android.safetycenter.testing.getSafetySourceDataWithPermission +import android.safetycenter.testing.isSafetyCenterEnabledWithPermission +import android.safetycenter.testing.refreshSafetySourcesWithPermission +import android.safetycenter.testing.removeOnSafetyCenterDataChangedListenerWithPermission +import android.safetycenter.testing.reportSafetySourceErrorWithPermission +import android.safetycenter.testing.setSafetyCenterConfigOverrideWithPermission +import android.safetycenter.testing.setSafetySourceDataWithPermission import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress -import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import kotlinx.coroutines.TimeoutCancellationException @@ -53,6 +71,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import org.junit.After +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -64,295 +83,406 @@ import kotlin.test.assertFailsWith class SafetyCenterManagerTest { private val context: Context = getApplicationContext() private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! - private val somePendingIntent = PendingIntent.getActivity( - context, 0 /* requestCode */, - Intent(ACTION_SAFETY_CENTER).addFlags(FLAG_ACTIVITY_NEW_TASK), - FLAG_IMMUTABLE - ) - private val safetySourceDataOnPageOpen = SafetySourceData.Builder() - .setStatus( - SafetySourceStatus.Builder( - "safetySourceDataOnPageOpen status title", - "safetySourceDataOnPageOpen status summary", - SafetySourceStatus.STATUS_LEVEL_NONE, - somePendingIntent - ) - .build() + private val somePendingIntent = + PendingIntent.getActivity( + context, + 0 /* requestCode */, + Intent(ACTION_SAFETY_CENTER), + FLAG_IMMUTABLE ) - .build() - private val safetySourceDataOnRescanClick = SafetySourceData.Builder() - .setStatus( - SafetySourceStatus.Builder( - "safetySourceDataOnRescanClick status title", - "safetySourceDataOnRescanClick status summary", - SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION, - somePendingIntent + private val safetySourceDataNone = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder( + "None title", + "None summary", + STATUS_LEVEL_NONE, + somePendingIntent + ) + .build() ) - .build() - ).build() - private val listenerChannel = Channel<SafetyCenterData>() - // The lambda has to be wrapped to the right type because kotlin wraps lambdas in a new Java - // functional interface implementation each time they are referenced/cast to a Java interface: - // b/215569072. - private val listener = OnSafetyCenterDataChangedListener { - runBlockingWithTimeout { - listenerChannel.send(it) + .build() + private val safetySourceDataOk = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder("Ok title", "Ok summary", STATUS_LEVEL_OK, + somePendingIntent) + .build() + ) + .build() + private val safetySourceDataCritical = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder( + "Critical title", + "Critical summary", + STATUS_LEVEL_CRITICAL_WARNING, + somePendingIntent + ) + .build() + ) + .addIssue( + SafetySourceIssue.Builder( + "critical_issue_id", + "Critical issue title", + "Critical issue summary", + SEVERITY_LEVEL_CRITICAL_WARNING, + "issue_type_id" + ) + .addAction( + SafetySourceIssue.Action.Builder("critical_action_id", "Solve issue", + somePendingIntent) + .build() + ) + .build() + ) + .build() + private val listener = object : OnSafetyCenterDataChangedListener { + private val dataChannel = Channel<SafetyCenterData>() + private val errorChannel = Channel<SafetyCenterErrorDetails>() + + override fun onSafetyCenterDataChanged(data: SafetyCenterData) { + runBlockingWithTimeout { dataChannel.send(data) } } + + override fun onError(errorDetails: SafetyCenterErrorDetails) { + runBlockingWithTimeout { errorChannel.send(errorDetails) } + } + + fun receiveSafetyCenterData(timeout: Duration = TIMEOUT_LONG) = + runBlockingWithTimeout(timeout) { dataChannel.receive() } + + fun receiveSafetyCenterErrorDetails(timeout: Duration = TIMEOUT_LONG) = + runBlockingWithTimeout(timeout) { errorChannel.receive() } + + fun cancelChannels() { + dataChannel.cancel() + errorChannel.cancel() + } + } + + @Before + fun assumeDeviceSupportsSafetyCenterToRunTests() { + assumeTrue(context.deviceSupportsSafetyCenter()) } @Before @After fun clearDataBetweenTest() { + SafetyCenterFlags.setSafetyCenterEnabled(true) safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(listener) safetyCenterManager.clearAllSafetySourceDataWithPermission() + safetyCenterManager.clearSafetyCenterConfigOverrideWithPermission() SafetySourceBroadcastReceiver.reset() } - @Before - fun setSafetyCenterConfigOverrideBeforeTest() { - safetyCenterManager.clearSafetyCenterConfigOverrideWithPermission() - // TODO(b/217944317): When the test API impl is finalized to override XML config, only - // override config in select test cases that require it. This is to ensure that we do have - // some test cases running with the XML config. - safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_ONLY_CONFIG) + @After + fun cancelChannelsAfterTest() { + listener.cancelChannels() } - @After - fun clearSafetyCenterConfigOverrideAfterTest() { - safetyCenterManager.clearSafetyCenterConfigOverrideWithPermission() + @Test + fun isSafetyCenterEnabled_withFlagEnabled_returnsTrue() { + val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + + assertThat(isSafetyCenterEnabled).isTrue() } @Test - fun getSafetySourceData_notSet_returnsNull() { - val safetySourceData = - safetyCenterManager.getSafetySourceDataWithPermission("some_unknown_id") + fun isSafetyCenterEnabled_withFlagDisabled_returnsFalse() { + SafetyCenterFlags.setSafetyCenterEnabled(false) - assertThat(safetySourceData).isNull() + val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + + assertThat(isSafetyCenterEnabled).isFalse() + } + + @Test + fun isSafetyCenterEnabled_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { safetyCenterManager.isSafetyCenterEnabled } } @Test - fun setSafetySourceData_getSafetySourceDataReturnsNewValue() { - val safetySourceData = SafetySourceData.Builder().build() + fun setSafetySourceData_validId_setsValue() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.setSafetySourceDataWithPermission( - CTS_SOURCE_ID, - safetySourceData, - EVENT_SOURCE_STATE_CHANGED + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED ) - val safetySourceDataResult = - safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - - assertThat(safetySourceDataResult).isEqualTo(safetySourceData) + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isEqualTo(safetySourceDataNone) } @Test - fun setSafetySourceData_withSameId_replacesValue() { - val firstSafetySourceData = SafetySourceData.Builder().build() + fun setSafetySourceData_twice_replacesValue() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) safetyCenterManager.setSafetySourceDataWithPermission( - CTS_SOURCE_ID, - firstSafetySourceData, - EVENT_SOURCE_STATE_CHANGED + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED ) - val secondSafetySourceData = SafetySourceData.Builder().setStatus( - SafetySourceStatus.Builder( - "Status title", "Summary of the status", STATUS_LEVEL_CRITICAL_WARNING, - somePendingIntent - ).build() - ).addIssue( - SafetySourceIssue.Builder( - "Issue id", "Issue title", "Summary of the issue", - SEVERITY_LEVEL_CRITICAL_WARNING, - "issue_type_id" - ) - .addAction( - SafetySourceIssue.Action.Builder( - "action_id", - "Solve issue", - somePendingIntent - ).build() - ).build() - ).build() safetyCenterManager.setSafetySourceDataWithPermission( - CTS_SOURCE_ID, - secondSafetySourceData, - EVENT_SOURCE_STATE_CHANGED + CTS_SOURCE_ID, + safetySourceDataCritical, + EVENT_SOURCE_STATE_CHANGED ) - val safetySourceData = safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - - assertThat(safetySourceData).isEqualTo(secondSafetySourceData) + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isEqualTo(safetySourceDataCritical) } @Test - fun isSafetyCenterEnabled_whenConfigEnabled_andFlagEnabled_returnsTrue() { - if (!deviceSupportsSafetyCenter()) { - return - } - - runWithShellPermissionIdentity { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_CENTER_ENABLED, - /* value = */ true.toString(), - /* makeDefault = */ false - ) - } + fun setSafetySourceData_null_clearsValue() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED + ) - val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceData = null, + EVENT_SOURCE_STATE_CHANGED + ) - assertThat(isSafetyCenterEnabled).isTrue() + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isNull() } @Test - fun isSafetyCenterEnabled_whenConfigEnabled_andFlagDisabled_returnsFalse() { - if (!deviceSupportsSafetyCenter()) { - return - } - - runWithShellPermissionIdentity { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_CENTER_ENABLED, - /* value = */ false.toString(), - /* makeDefault = */ false - ) - } + fun setSafetySourceData_withFlagDisabled_noOp() { + SafetyCenterFlags.setSafetyCenterEnabled(false) - val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED + ) - assertThat(isSafetyCenterEnabled).isFalse() + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isNull() } @Test - fun isSafetyCenterEnabled_whenConfigDisabled_andFlagEnabled_returnsFalse() { - if (deviceSupportsSafetyCenter()) { - return - } - - runWithShellPermissionIdentity { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_CENTER_ENABLED, - /* value = */ true.toString(), - /* makeDefault = */ false + fun setSafetySourceData_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { + safetyCenterManager.setSafetySourceData( + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED ) } - - val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() - - assertThat(isSafetyCenterEnabled).isFalse() } @Test - fun isSafetyCenterEnabled_whenConfigDisabled_andFlagDisabled_returnsFalse() { - if (deviceSupportsSafetyCenter()) { - return - } + fun getSafetySourceData_validId_noData_returnsNull() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) - runWithShellPermissionIdentity { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_CENTER_ENABLED, - /* value = */ false.toString(), - /* makeDefault = */ false - ) - } - - val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - assertThat(isSafetyCenterEnabled).isFalse() + assertThat(apiSafetySourceData).isNull() } @Test - fun isSafetyCenterEnabled_whenAppDoesNotHoldPermission_methodThrows() { + fun getSafetySourceData_withoutPermission_throwsSecurityException() { assertFailsWith(SecurityException::class) { - safetyCenterManager.isSafetyCenterEnabled + safetyCenterManager.getSafetySourceData(CTS_SOURCE_ID) } } @Test - fun refreshSafetySources_withoutManageSafetyCenterPermission_throwsSecurityException() { + fun reportSafetySourceError_callsErrorListener() { + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener + ) + + safetyCenterManager.reportSafetySourceErrorWithPermission( + CTS_SOURCE_ID, + SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED) + ) + val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails() + + assertThat(safetyCenterErrorDetailsFromListener).isEqualTo( + SafetyCenterErrorDetails("Error")) + } + + @Test + fun reportSafetySourceError_withoutPermission_throwsSecurityException() { assertFailsWith(SecurityException::class) { - safetyCenterManager.refreshSafetySources(REFRESH_REASON_RESCAN_BUTTON_CLICK) + safetyCenterManager.reportSafetySourceError( + CTS_SOURCE_ID, + SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED) + ) } } @Test - fun refreshSafetySources_whenReceiverDoesNotHaveSendingPermission_sourceDoesNotSendData() { + fun refreshSafetySources_withRefreshReasonRescanButtonClick_sourceSendsRescanData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) SafetySourceBroadcastReceiver.safetySourceId = CTS_SOURCE_ID - SafetySourceBroadcastReceiver.safetySourceDataOnRescanClick = safetySourceDataOnRescanClick + SafetySourceBroadcastReceiver.safetySourceDataOnRescanClick = safetySourceDataCritical - safetyCenterManager.refreshSafetySourcesWithPermission( + safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( REFRESH_REASON_RESCAN_BUTTON_CLICK ) - assertFailsWith(TimeoutCancellationException::class) { - SafetySourceBroadcastReceiver.waitTillOnReceiveComplete(TIMEOUT_SHORT) - } - val safetySourceData = + val apiSafetySourceData = safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - assertThat(safetySourceData).isNull() + assertThat(apiSafetySourceData).isEqualTo(safetySourceDataCritical) } @Test - fun refreshSafetySources_withRefreshReasonRescanButtonClick_sourceSendsRescanData() { + fun refreshSafetySources_withRefreshReasonPageOpen_sourceSendsPageOpenData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) SafetySourceBroadcastReceiver.safetySourceId = CTS_SOURCE_ID - SafetySourceBroadcastReceiver.safetySourceDataOnRescanClick = safetySourceDataOnRescanClick + SafetySourceBroadcastReceiver.safetySourceDataOnPageOpen = safetySourceDataOk safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( - REFRESH_REASON_RESCAN_BUTTON_CLICK - ) + REFRESH_REASON_PAGE_OPEN) - val safetySourceData = safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - assertThat(safetySourceData).isEqualTo(safetySourceDataOnRescanClick) + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isEqualTo(safetySourceDataOk) } @Test - fun refreshSafetySources_withRefreshReasonPageOpen_sourceSendsPageOpenData() { + fun refreshSafetySources_whenReceiverDoesNotHaveSendingPermission_sourceDoesNotSendData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) SafetySourceBroadcastReceiver.safetySourceId = CTS_SOURCE_ID - SafetySourceBroadcastReceiver.safetySourceDataOnPageOpen = safetySourceDataOnPageOpen + SafetySourceBroadcastReceiver.safetySourceDataOnRescanClick = safetySourceDataCritical - safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( - REFRESH_REASON_PAGE_OPEN - ) + safetyCenterManager.refreshSafetySourcesWithPermission(REFRESH_REASON_RESCAN_BUTTON_CLICK) - val safetySourceData = safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) - assertThat(safetySourceData).isEqualTo(safetySourceDataOnPageOpen) + assertFailsWith(TimeoutCancellationException::class) { + SafetySourceBroadcastReceiver.waitTillOnReceiveComplete(TIMEOUT_SHORT) + } + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isNull() } @Test - fun refreshSafetySources_withInvalidRefreshSeason_throwsIllegalArgumentException() { + fun refreshSafetySources_withFlagDisabled_noOp() { + SafetyCenterFlags.setSafetyCenterEnabled(false) + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) SafetySourceBroadcastReceiver.safetySourceId = CTS_SOURCE_ID - SafetySourceBroadcastReceiver.safetySourceDataOnPageOpen = safetySourceDataOnPageOpen - SafetySourceBroadcastReceiver.safetySourceDataOnRescanClick = safetySourceDataOnRescanClick + SafetySourceBroadcastReceiver.safetySourceDataOnPageOpen = safetySourceDataOk - assertFailsWith(IllegalArgumentException::class) { - safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(500) + assertFailsWith(TimeoutCancellationException::class) { + safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( + REFRESH_REASON_PAGE_OPEN, TIMEOUT_SHORT) } + val apiSafetySourceData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + assertThat(apiSafetySourceData).isNull() } @Test - fun getSafetyCenterData_returnsSafetyCenterData() { - val safetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() - - // TODO(b/218830137): Assert on content. - assertThat(safetyCenterData).isNotNull() + fun refreshSafetySources_withInvalidRefreshSeason_throwsIllegalArgumentException() { + val thrown = + assertFailsWith(IllegalArgumentException::class) { + safetyCenterManager.refreshSafetySourcesWithPermission(500) + } + assertThat(thrown).hasMessageThat().isEqualTo("Invalid refresh reason: 500") } @Test - fun getSafetyCenterData_whenAppDoesNotHoldPermission_methodThrows() { + fun refreshSafetySources_withoutPermission_throwsSecurityException() { assertFailsWith(SecurityException::class) { - safetyCenterManager.safetyCenterData + safetyCenterManager.refreshSafetySources(REFRESH_REASON_RESCAN_BUTTON_CLICK) } } @Test - fun addOnSafetyCenterDataChangedListener_listenerCalledWithSafetyCenterData() { + fun getSafetyCenterData_withoutDataProvided_returnsDataFromConfig() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + // TODO(b/218830137): Assert on content. + assertThat(apiSafetyCenterData).isNotNull() + } + + @Test + fun getSafetyCenterData_withSomeDataProvided_returnsDataProvided() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataNone, + EVENT_SOURCE_STATE_CHANGED + ) + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + // TODO(b/218830137): Assert on content. + assertThat(apiSafetyCenterData).isNotNull() + } + + @Test + fun getSafetyCenterData_withUpdatedData_returnsUpdatedData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + val previousApiSafetyCenterData = + safetyCenterManager.getSafetySourceDataWithPermission(CTS_SOURCE_ID) + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataCritical, + EVENT_SOURCE_STATE_CHANGED + ) + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + // TODO(b/218830137): Assert on content. + assertThat(apiSafetyCenterData).isNotEqualTo(previousApiSafetyCenterData) + } + + @Test + fun getSafetyCenterData_withFlagDisabled_returnsDefaultData() { + SafetyCenterFlags.setSafetyCenterEnabled(false) + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() + + assertThat(apiSafetyCenterData).isEqualTo(SafetyCenterData( + SafetyCenterStatus.Builder() + .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN) + .setTitle("Unknown") + .setSummary("Unknown safety status") + .build(), + emptyList(), + emptyList(), + emptyList() + )) + } + + @Test + fun getSafetyCenterData_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { safetyCenterManager.safetyCenterData } + } + + @Test + fun addOnSafetyCenterDataChangedListener_listenerCalledWithSafetyCenterDataFromConfig() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( - directExecutor(), listener + directExecutor(), + listener ) - val safetyCenterDataFromListener = receiveListenerUpdate() + val safetyCenterDataFromListener = listener.receiveSafetyCenterData() // TODO(b/218830137): Assert on content. assertThat(safetyCenterDataFromListener).isNotNull() @@ -360,58 +490,199 @@ class SafetyCenterManagerTest { @Test fun addOnSafetyCenterDataChangedListener_listenerCalledOnSafetySourceData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( - directExecutor(), listener + directExecutor(), + listener ) // Receive initial data. - receiveListenerUpdate() + listener.receiveSafetyCenterData() - val safetySourceData = SafetySourceData.Builder().build() safetyCenterManager.setSafetySourceDataWithPermission( - CTS_SOURCE_ID, - safetySourceData, - EVENT_SOURCE_STATE_CHANGED + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED ) - val safetyCenterDataFromListener = receiveListenerUpdate() + val safetyCenterDataFromListener = listener.receiveSafetyCenterData() // TODO(b/218830137): Assert on content. assertThat(safetyCenterDataFromListener).isNotNull() } @Test - fun removeOnSafetyCenterDataChangedListener_listenerNotCalledOnSafetySourceData() { + fun addOnSafetyCenterDataChangedListener_listenerCalledWhenSafetySourceDataChanges() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( - directExecutor(), listener + directExecutor(), + listener ) // Receive initial data. - receiveListenerUpdate() + listener.receiveSafetyCenterData() + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + // Receive update from #setSafetySourceData call. + listener.receiveSafetyCenterData() - safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(listener) - val safetySourceData = SafetySourceData.Builder().build() safetyCenterManager.setSafetySourceDataWithPermission( - CTS_SOURCE_ID, - safetySourceData, - EVENT_SOURCE_STATE_CHANGED + CTS_SOURCE_ID, + safetySourceDataCritical, + EVENT_SOURCE_STATE_CHANGED + ) + val safetyCenterDataFromListener = listener.receiveSafetyCenterData() + + // TODO(b/218830137): Assert on content. + assertThat(safetyCenterDataFromListener).isNotNull() + } + + @Test + fun addOnSafetyCenterDataChangedListener_listenerCalledWhenSafetySourceDataCleared() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener + ) + // Receive initial data. + listener.receiveSafetyCenterData() + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + // Receive update from #setSafetySourceData call. + listener.receiveSafetyCenterData() + + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceData = null, + EVENT_SOURCE_STATE_CHANGED + ) + val safetyCenterDataFromListener = listener.receiveSafetyCenterData() + + // TODO(b/218830137): Assert on content. + assertThat(safetyCenterDataFromListener).isNotNull() + } + + @Test + fun addOnSafetyCenterDataChangedListener_listenerNotCalledWhenSafetySourceDataStaysNull() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener + ) + // Receive initial data. + listener.receiveSafetyCenterData() + + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceData = null, + EVENT_SOURCE_STATE_CHANGED + ) + + assertFailsWith(TimeoutCancellationException::class) { + listener.receiveSafetyCenterData(TIMEOUT_SHORT) + } + } + + @Test + fun addOnSafetyCenterDataChangedListener_listenerNotCalledWhenSafetySourceDataDoesntChange() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener + ) + // Receive initial data. + listener.receiveSafetyCenterData() + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + // Receive update from #setSafetySourceData call. + listener.receiveSafetyCenterData() + + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + + assertFailsWith(TimeoutCancellationException::class) { + listener.receiveSafetyCenterData(TIMEOUT_SHORT) + } + } + + @Test + fun addOnSafetyCenterDataChangedListener_withFlagDisabled_listenerNotCalled() { + SafetyCenterFlags.setSafetyCenterEnabled(false) + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) + + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener ) assertFailsWith(TimeoutCancellationException::class) { - receiveListenerUpdate(TIMEOUT_SHORT) + listener.receiveSafetyCenterData(TIMEOUT_SHORT) } } @Test - fun addOnSafetyCenterDataChangedListener_whenAppDoesNotHoldPermission_methodThrows() { + fun addOnSafetyCenterDataChangedListener_withoutPermission_throwsSecurityException() { assertFailsWith(SecurityException::class) { - safetyCenterManager.addOnSafetyCenterDataChangedListener( - directExecutor(), listener - ) + safetyCenterManager.addOnSafetyCenterDataChangedListener(directExecutor(), listener) } } @Test - fun removeOnSafetyCenterDataChangedListener_whenAppDoesNotHoldPermission_methodThrows() { + fun addOnSafetyCenterDataChangedListener_oneShot_doesntDeadlock() { + val oneShotListener = + object : OnSafetyCenterDataChangedListener { + override fun onSafetyCenterDataChanged(safetyCenterData: SafetyCenterData) { + safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(this) + listener.onSafetyCenterDataChanged(safetyCenterData) + } + } + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + oneShotListener + ) + + // Check that we don't deadlock when using a one-shot listener: this is because adding the + // listener could call the listener while holding a lock on the binder thread-pool; causing + // a deadlock when attempting to call the `SafetyCenterManager` from that listener. + listener.receiveSafetyCenterData() + } + + @Test + fun removeOnSafetyCenterDataChangedListener_listenerNotCalledOnSafetySourceData() { + safetyCenterManager.setSafetyCenterConfigOverrideWithPermission(CTS_CONFIG) safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( - directExecutor(), listener + directExecutor(), + listener + ) + // Receive initial data. + listener.receiveSafetyCenterData() + + safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(listener) + safetyCenterManager.setSafetySourceDataWithPermission( + CTS_SOURCE_ID, + safetySourceDataOk, + EVENT_SOURCE_STATE_CHANGED + ) + + assertFailsWith(TimeoutCancellationException::class) { + listener.receiveSafetyCenterData(TIMEOUT_SHORT) + } + } + + @Test + fun removeOnSafetyCenterDataChangedListener_withoutPermission_throwsSecurityException() { + safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + directExecutor(), + listener ) assertFailsWith(SecurityException::class) { @@ -420,79 +691,91 @@ class SafetyCenterManagerTest { } @Test - fun dismissSafetyIssue_whenAppDoesNotHoldPermission_methodThrows() { + fun dismissSafetyCenterIssue_withoutPermission_throwsSecurityException() { assertFailsWith(SecurityException::class) { - safetyCenterManager.dismissSafetyIssue("bleh") + safetyCenterManager.dismissSafetyCenterIssue("bleh") } } - private fun deviceSupportsSafetyCenter() = - context.resources.getBoolean( - Resources.getSystem().getIdentifier( - "config_enableSafetyCenter", - "bool", - "android" - ) - ) + @Test + fun executeSafetyCenterIssueAction_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { + safetyCenterManager.executeSafetyCenterIssueAction("bleh", "blah") + } + } - private fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( - refreshReason: Int - ) { - try { - runWithShellPermissionIdentity({ - refreshSafetySources(refreshReason) - SafetySourceBroadcastReceiver.waitTillOnReceiveComplete(TIMEOUT_LONG) - }, SEND_SAFETY_CENTER_UPDATE, MANAGE_SAFETY_CENTER) - } catch (e: RuntimeException) { - throw e.cause ?: e + @Test + fun clearAllSafetySourceData_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { safetyCenterManager.clearAllSafetySourceData() } + } + + @Test + fun setSafetyCenterConfigOverride_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { + safetyCenterManager.setSafetyCenterConfigOverride(CTS_CONFIG) } } - private fun receiveListenerUpdate(timeout: Duration = TIMEOUT_LONG): SafetyCenterData = - runBlockingWithTimeout(timeout) { - listenerChannel.receive() + @Test + fun clearSafetyCenterConfigOverride_withoutPermission_throwsSecurityException() { + assertFailsWith(SecurityException::class) { + safetyCenterManager.clearSafetyCenterConfigOverride() } + } + + private fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( + refreshReason: Int, + timeout: Duration = TIMEOUT_LONG + ) { + callWithShellPermissionIdentity( + { + refreshSafetySources(refreshReason) + SafetySourceBroadcastReceiver.waitTillOnReceiveComplete(timeout) + }, + SEND_SAFETY_CENTER_UPDATE, + MANAGE_SAFETY_CENTER + ) + } private fun <T> runBlockingWithTimeout( timeout: Duration = TIMEOUT_LONG, block: suspend () -> T ) = runBlocking { - withTimeout(timeout.toMillis()) { - block() - } + withTimeout(timeout.toMillis()) { block() } } companion object { - /** Name of the flag that determines whether SafetyCenter is enabled. */ - private const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled" private const val CTS_PACKAGE_NAME = "android.safetycenter.cts" private const val CTS_BROADCAST_RECEIVER_NAME = - "android.safetycenter.cts.SafetySourceBroadcastReceiver" + "android.safetycenter.testing.SafetySourceBroadcastReceiver" private val TIMEOUT_LONG: Duration = Duration.ofMillis(5000) private val TIMEOUT_SHORT: Duration = Duration.ofMillis(1000) private val EVENT_SOURCE_STATE_CHANGED = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() private const val CTS_SOURCE_ID = "cts_source_id" + private const val CTS_SOURCE_GROUP_ID = "cts_source_group" + // TODO(b/217944317): Consider moving the following to a file where they can be used by // other tests. - private val CTS_SOURCE = SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC) + private val CTS_SOURCE = + SafetySource.Builder(SAFETY_SOURCE_TYPE_DYNAMIC) .setId(CTS_SOURCE_ID) .setPackageName(CTS_PACKAGE_NAME) - .setTitleResId(R.string.reference) - .setSummaryResId(R.string.reference) - .setIntentAction("SafetyCenterManagerTest.INTENT_ACTION") + .setTitleResId(android.R.string.ok) + .setSummaryResId(android.R.string.ok) + .setIntentAction(ACTION_SAFETY_CENTER) .setBroadcastReceiverClassName(CTS_BROADCAST_RECEIVER_NAME) .setProfile(SafetySource.PROFILE_PRIMARY) .build() - private val CTS_SOURCE_GROUP = SafetySourcesGroup.Builder() - .setId("cts_source_group") - .setTitleResId(R.string.reference) - .setSummaryResId(R.string.reference) + private val CTS_SOURCE_GROUP = + SafetySourcesGroup.Builder() + .setId(CTS_SOURCE_GROUP_ID) + .setTitleResId(android.R.string.ok) + .setSummaryResId(android.R.string.ok) .addSafetySource(CTS_SOURCE) .build() - private val CTS_ONLY_CONFIG = SafetyCenterConfig.Builder() - .addSafetySourcesGroup(CTS_SOURCE_GROUP) - .build() + private val CTS_CONFIG = + SafetyCenterConfig.Builder().addSafetySourcesGroup(CTS_SOURCE_GROUP).build() } } diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt new file mode 100644 index 000000000..5265a4fab --- /dev/null +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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 android.safetycenter.cts + +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_SAFETY_CENTER +import android.os.Build.VERSION_CODES.TIRAMISU +import android.safetycenter.SafetyCenterManager +import android.safetycenter.testing.SafetyCenterFlags +import android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter +import android.safetycenter.testing.isSafetyCenterEnabledWithPermission +import android.support.test.uiautomator.By +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress +import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject +import com.google.common.truth.Truth.assertThat +import org.junit.Assume.assumeFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu") +class SafetyCenterUnsupportedTest { + private val context: Context = getApplicationContext() + private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! + + @Before + fun assumeDeviceDoesntSupportSafetyCenterToRunTests() { + assumeFalse(context.deviceSupportsSafetyCenter()) + } + + @Test + fun launchActivity_showsSecurityTitle() { + startSafetyCenterActivity() + + // CollapsingToolbar title can't be found by text, so using description instead. + waitFindObject(By.desc("Security")) + } + + @Test + fun isSafetyCenterEnabled_withFlagEnabled_returnsFalse() { + SafetyCenterFlags.setSafetyCenterEnabled(true) + + val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + + assertThat(isSafetyCenterEnabled).isFalse() + } + + @Test + fun isSafetyCenterEnabled_withFlagDisabled_returnsFalse() { + SafetyCenterFlags.setSafetyCenterEnabled(false) + + val isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabledWithPermission() + + assertThat(isSafetyCenterEnabled).isFalse() + } + + private fun startSafetyCenterActivity() { + context.startActivity( + Intent(ACTION_SAFETY_CENTER) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + } +} diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt index bc9d5384a..a13b4e425 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt @@ -18,13 +18,13 @@ package android.safetycenter.cts import android.os.Build import android.safetycenter.SafetyEvent -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED +import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED -import android.safetycenter.testers.AnyTester.assertThatRepresentationsAreEqual -import android.safetycenter.testers.AnyTester.assertThatRepresentationsAreNotEqual -import android.safetycenter.testers.ParcelableTester.assertThatRoundTripReturnsOriginal +import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED +import android.safetycenter.testing.AnyTester.assertThatRepresentationsAreEqual +import android.safetycenter.testing.AnyTester.assertThatRepresentationsAreNotEqual +import android.safetycenter.testing.ParcelableTester.assertThatRoundTripReturnsOriginal import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat @@ -36,18 +36,18 @@ import org.junit.runner.RunWith @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") class SafetyEventTest { @Test - fun getSafetyEventType_returnsSafetyEventType() { + fun getType_returnsType() { val safetyEvent = SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() - assertThat(safetyEvent.safetyEventType).isEqualTo(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) + assertThat(safetyEvent.type).isEqualTo(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) } @Test fun getRefreshBroadcastId_returnsRefreshBroadcastId() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(REFRESH_BROADCAST_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(REFRESH_BROADCAST_ID) + .build() assertThat(safetyEvent.refreshBroadcastId).isEqualTo(REFRESH_BROADCAST_ID) } @@ -55,9 +55,9 @@ class SafetyEventTest { @Test fun getSafetySourceIssueId_returnsSafetySourceIssueId() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .build() assertThat(safetyEvent.safetySourceIssueId).isEqualTo(SAFETY_SOURCE_ISSUE_ID) } @@ -65,9 +65,9 @@ class SafetyEventTest { @Test fun getSafetySourceIssueActionId_returnsSafetySourceIssueActionId() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() assertThat(safetyEvent.safetySourceIssueActionId).isEqualTo(SAFETY_SOURCE_ISSUE_ACTION_ID) } @@ -75,10 +75,10 @@ class SafetyEventTest { @Test fun describeContents_returns0() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() assertThat(safetyEvent.describeContents()).isEqualTo(0) } @@ -86,10 +86,10 @@ class SafetyEventTest { @Test fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceData() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() assertThatRoundTripReturnsOriginal(safetyEvent, SafetyEvent.CREATOR) } @@ -98,9 +98,9 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withEqualByReference_areEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(REFRESH_BROADCAST_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(REFRESH_BROADCAST_ID) + .build() val otherSafetyEvent = safetyEvent assertThatRepresentationsAreEqual(safetyEvent, otherSafetyEvent) @@ -109,15 +109,15 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withAllFieldsEqual_areEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() val otherSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() assertThatRepresentationsAreEqual(safetyEvent, otherSafetyEvent) } @@ -125,11 +125,11 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withDifferentSafetyEventTypes_areNotEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .build() val otherSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED) + .build() assertThatRepresentationsAreNotEqual(safetyEvent, otherSafetyEvent) } @@ -137,13 +137,13 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withDifferentRefreshBroadcastIds_areNotEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(REFRESH_BROADCAST_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(REFRESH_BROADCAST_ID) + .build() val otherSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(OTHER_REFRESH_BROADCAST_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(OTHER_REFRESH_BROADCAST_ID) + .build() assertThatRepresentationsAreNotEqual(safetyEvent, otherSafetyEvent) } @@ -151,13 +151,13 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withDifferentIssueIds_areNotEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .build() val otherSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(OTHER_SAFETY_SOURCE_ISSUE_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(OTHER_SAFETY_SOURCE_ISSUE_ID) + .build() assertThatRepresentationsAreNotEqual(safetyEvent, otherSafetyEvent) } @@ -165,15 +165,15 @@ class SafetyEventTest { @Test fun hashCode_equals_toString_withDifferentActionIds_areNotEqual() { val safetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() val otherSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) - .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) - .setSafetySourceIssueActionId(OTHER_SAFETY_SOURCE_ISSUE_ACTION_ID) - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) + .setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID) + .setSafetySourceIssueActionId(OTHER_SAFETY_SOURCE_ISSUE_ACTION_ID) + .build() assertThatRepresentationsAreNotEqual(safetyEvent, otherSafetyEvent) } diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt index 0d69f2c93..ed9136f04 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt @@ -18,61 +18,61 @@ package android.safetycenter.cts import android.os.Build import android.safetycenter.SafetyEvent -import android.safetycenter.SafetySourceError -import android.safetycenter.testers.AnyTester.assertThatRepresentationsAreEqual -import android.safetycenter.testers.AnyTester.assertThatRepresentationsAreNotEqual -import android.safetycenter.testers.ParcelableTester.assertThatRoundTripReturnsOriginal +import android.safetycenter.SafetySourceErrorDetails +import android.safetycenter.testing.AnyTester.assertThatRepresentationsAreEqual +import android.safetycenter.testing.AnyTester.assertThatRepresentationsAreNotEqual +import android.safetycenter.testing.ParcelableTester.assertThatRoundTripReturnsOriginal import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -/** CTS tests for [SafetySourceError]. */ +/** CTS tests for [SafetySourceErrorDetails]. */ @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") -class SafetySourceErrorTest { +class SafetySourceErrorDetailsTest { @Test fun getSafetyEvent_returnsSafetyEvent() { - val safetySourceError = SafetySourceError(SAFETY_EVENT) + val errorDetails = SafetySourceErrorDetails(SAFETY_EVENT) - assertThat(safetySourceError.safetyEvent).isEqualTo(SAFETY_EVENT) + assertThat(errorDetails.safetyEvent).isEqualTo(SAFETY_EVENT) } @Test fun createFromParcel_withWriteToParcel_returnsEquivalentObject() { - val safetySourceError = SafetySourceError(SAFETY_EVENT) + val errorDetails = SafetySourceErrorDetails(SAFETY_EVENT) - assertThatRoundTripReturnsOriginal(safetySourceError, SafetySourceError.CREATOR) + assertThatRoundTripReturnsOriginal(errorDetails, SafetySourceErrorDetails.CREATOR) } @Test fun equals_hashCode_toString_equalByReference_areEqual() { - val safetySourceError = SafetySourceError(SAFETY_EVENT) + val errorDetails = SafetySourceErrorDetails(SAFETY_EVENT) - assertThatRepresentationsAreEqual(safetySourceError, safetySourceError) + assertThatRepresentationsAreEqual(errorDetails, errorDetails) } @Test fun equals_hashCode_toString_equalByValue_areEqual() { - val safetySourceError = SafetySourceError(SAFETY_EVENT) - val equivalentSafetySourceError = SafetySourceError(SAFETY_EVENT) + val errorDetails = SafetySourceErrorDetails(SAFETY_EVENT) + val equivalentSafetySourceErrorDetails = SafetySourceErrorDetails(SAFETY_EVENT) - assertThatRepresentationsAreEqual(safetySourceError, equivalentSafetySourceError) + assertThatRepresentationsAreEqual(errorDetails, equivalentSafetySourceErrorDetails) } @Test fun equals_toString_withDifferentSafetyEvents_areNotEqual() { - val safetySourceError = SafetySourceError( - SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()) - val otherSafetySourceError = SafetySourceError( - SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build()) + val errorDetails = SafetySourceErrorDetails( + SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()) + val otherErrorDetails = SafetySourceErrorDetails( + SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build()) - assertThatRepresentationsAreNotEqual(safetySourceError, otherSafetySourceError) + assertThatRepresentationsAreNotEqual(errorDetails, otherErrorDetails) } companion object { private val SAFETY_EVENT = - SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() + SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() } }
\ No newline at end of file diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt index ad3e44155..890e6902c 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt @@ -33,7 +33,7 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.google.common.truth.Truth.assertThat -import org.junit.Assert.assertThrows +import kotlin.test.assertFailsWith import org.junit.Test import org.junit.runner.RunWith @@ -70,19 +70,19 @@ class SafetySourceIssueTest { } @Test - fun action_isResolving_withDefaultBuilder_returnsFalse() { + fun action_willResolve_withDefaultBuilder_returnsFalse() { val action = Action.Builder("action_id", "Action label", pendingIntent1).build() - assertThat(action.isResolving).isFalse() + assertThat(action.willResolve()).isFalse() } @Test - fun action_isResolving_whenSetExplicitly_returnsResolving() { + fun action_willResolve_whenSetExplicitly_returnsWillResolve() { val action = Action.Builder("action_id", "Action label", pendingIntent1) - .setResolving(true) + .setWillResolve(true) .build() - assertThat(action.isResolving).isTrue() + assertThat(action.willResolve()).isTrue() } @Test @@ -172,12 +172,12 @@ class SafetySourceIssueTest { } @Test - fun action_hashCode_equals_toString_withDifferentResolving_areNotEqual() { + fun action_hashCode_equals_toString_withDifferentWillResolve_areNotEqual() { val action = - Action.Builder("action_id", "Action label", pendingIntent1).setResolving(false) + Action.Builder("action_id", "Action label", pendingIntent1).setWillResolve(false) .build() val otherAction = - Action.Builder("action_id", "Action label", pendingIntent1).setResolving(true).build() + Action.Builder("action_id", "Action label", pendingIntent1).setWillResolve(true).build() assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode()) assertThat(action).isNotEqualTo(otherAction) @@ -429,10 +429,13 @@ class SafetySourceIssueTest { SEVERITY_LEVEL_INFORMATION, "issue_type_id" ) - assertThrows( - "Safety source issue must contain at least 1 action", - IllegalArgumentException::class.java - ) { safetySourceIssueBuilder.build() } + + val exception = assertFailsWith(IllegalArgumentException::class) { + safetySourceIssueBuilder.build() + } + assertThat(exception) + .hasMessageThat() + .isEqualTo("Safety source issue must contain at least 1 action") } @Test @@ -447,10 +450,12 @@ class SafetySourceIssueTest { .addAction(action2) .addAction(action1) - assertThrows( - "Safety source issue must not contain more than 2 actions", - IllegalArgumentException::class.java - ) { safetySourceIssueBuilder.build() } + val exception = assertFailsWith(IllegalArgumentException::class) { + safetySourceIssueBuilder.build() + } + assertThat(exception) + .hasMessageThat() + .isEqualTo("Safety source issue must not contain more than 2 actions") } @Test @@ -530,7 +535,7 @@ class SafetySourceIssueTest { .setIssueCategory(ISSUE_CATEGORY_ACCOUNT) .addAction( Action.Builder("action_id", "Action label 1", pendingIntent1) - .setResolving(false) + .setWillResolve(false) .build() ) .setOnDismissPendingIntent(pendingIntent1) @@ -545,7 +550,7 @@ class SafetySourceIssueTest { .setIssueCategory(ISSUE_CATEGORY_ACCOUNT) .addAction( Action.Builder("action_id", "Action label 1", pendingIntent1) - .setResolving(false) + .setWillResolve(false) .build() ) .setOnDismissPendingIntent(pendingIntent1) diff --git a/tests/cts/safetycenter/src/android/safetycenter/testers/AnyTester.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/AnyTester.kt index f3fbf77a8..50c37a39a 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/testers/AnyTester.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/testing/AnyTester.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.safetycenter.testers +package android.safetycenter.testing import com.google.common.truth.Truth.assertThat diff --git a/tests/cts/safetycenter/src/android/safetycenter/testers/ParcelableTester.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/ParcelableTester.kt index f2c2482a1..0ba599993 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/testers/ParcelableTester.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/testing/ParcelableTester.kt @@ -1,4 +1,4 @@ -package android.safetycenter.testers +package android.safetycenter.testing import android.os.Parcel import android.os.Parcelable diff --git a/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt new file mode 100644 index 000000000..b874b8219 --- /dev/null +++ b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2022 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 android.safetycenter.testing + +import android.Manifest.permission.MANAGE_SAFETY_CENTER +import android.Manifest.permission.READ_SAFETY_CENTER_STATUS +import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE +import android.safetycenter.SafetyCenterManager +import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener +import android.safetycenter.SafetyEvent +import android.safetycenter.SafetySourceData +import android.safetycenter.SafetySourceErrorDetails +import android.safetycenter.config.SafetyCenterConfig +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import java.util.concurrent.Executor + +/** + * Calls [SafetyCenterManager.isSafetyCenterEnabled] adopting Shell's [READ_SAFETY_CENTER_STATUS] + * permission. + */ +fun SafetyCenterManager.isSafetyCenterEnabledWithPermission() = + callWithShellPermissionIdentity({ isSafetyCenterEnabled }, READ_SAFETY_CENTER_STATUS) + +/** + * Calls [SafetyCenterManager.setSafetySourceData] adopting Shell's [SEND_SAFETY_CENTER_UPDATE] + * permission. + */ +fun SafetyCenterManager.setSafetySourceDataWithPermission( + safetySourceId: String, + safetySourceData: SafetySourceData?, + safetyEvent: SafetyEvent +) = + callWithShellPermissionIdentity( + { setSafetySourceData(safetySourceId, safetySourceData, safetyEvent) }, + SEND_SAFETY_CENTER_UPDATE + ) + +/** + * Calls [SafetyCenterManager.getSafetySourceData] adopting Shell's [SEND_SAFETY_CENTER_UPDATE] + * permission. + */ +fun SafetyCenterManager.getSafetySourceDataWithPermission(id: String) = + callWithShellPermissionIdentity({ getSafetySourceData(id) }, SEND_SAFETY_CENTER_UPDATE) + +/** + * Calls [SafetyCenterManager.reportSafetySourceError] adopting Shell's [SEND_SAFETY_CENTER_UPDATE] + * permission. + */ +fun SafetyCenterManager.reportSafetySourceErrorWithPermission( + safetySourceId: String, + safetySourceErrorDetails: SafetySourceErrorDetails +) = + callWithShellPermissionIdentity( + { reportSafetySourceError(safetySourceId, safetySourceErrorDetails) }, + SEND_SAFETY_CENTER_UPDATE + ) + +/** + * Calls [SafetyCenterManager.refreshSafetySources] adopting Shell's [MANAGE_SAFETY_CENTER] + * permission. + */ +fun SafetyCenterManager.refreshSafetySourcesWithPermission(refreshReason: Int) = + callWithShellPermissionIdentity({ refreshSafetySources(refreshReason) }, MANAGE_SAFETY_CENTER) + +/** + * Calls [SafetyCenterManager.getSafetyCenterData] adopting Shell's [MANAGE_SAFETY_CENTER] + * permission. + */ +fun SafetyCenterManager.getSafetyCenterDataWithPermission() = + callWithShellPermissionIdentity(::getSafetyCenterData, MANAGE_SAFETY_CENTER) + +/** + * Calls [SafetyCenterManager.addOnSafetyCenterDataChangedListener] adopting Shell's + * [MANAGE_SAFETY_CENTER] permission. + */ +fun SafetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission( + executor: Executor, + listener: OnSafetyCenterDataChangedListener +) = + callWithShellPermissionIdentity( + { addOnSafetyCenterDataChangedListener(executor, listener) }, + MANAGE_SAFETY_CENTER + ) + +/** + * Calls [SafetyCenterManager.removeOnSafetyCenterDataChangedListener] adopting Shell's + * [MANAGE_SAFETY_CENTER] permission. + */ +fun SafetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission( + listener: OnSafetyCenterDataChangedListener +) = + callWithShellPermissionIdentity( + { removeOnSafetyCenterDataChangedListener(listener) }, + MANAGE_SAFETY_CENTER + ) + +/** + * Calls [SafetyCenterManager.dismissSafetyCenterIssue] adopting Shell's [MANAGE_SAFETY_CENTER] + * permission. + */ +fun SafetyCenterManager.dismissSafetyCenterIssueWithPermission(safetyCenterIssueId: String) = + callWithShellPermissionIdentity({ dismissSafetyCenterIssue(safetyCenterIssueId) }, + MANAGE_SAFETY_CENTER) + +/** + * Calls [SafetyCenterManager.executeSafetyCenterIssueAction] adopting Shell's + * [MANAGE_SAFETY_CENTER] permission. + */ +fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermission( + safetyCenterIssueId: String, + safetyCenterIssueActionId: String +) = + callWithShellPermissionIdentity( + { executeSafetyCenterIssueAction(safetyCenterIssueId, safetyCenterIssueActionId) }, + MANAGE_SAFETY_CENTER + ) + +/** + * Calls [SafetyCenterManager.clearAllSafetySourceData] adopting Shell's [MANAGE_SAFETY_CENTER] + * permission. + */ +fun SafetyCenterManager.clearAllSafetySourceDataWithPermission() = + callWithShellPermissionIdentity({ clearAllSafetySourceData() }, MANAGE_SAFETY_CENTER) + +/** + * Calls [SafetyCenterManager.setSafetyCenterConfigOverride] adopting Shell's [MANAGE_SAFETY_CENTER] + * permission. + */ +fun SafetyCenterManager.setSafetyCenterConfigOverrideWithPermission( + safetyCenterConfig: SafetyCenterConfig +) = + callWithShellPermissionIdentity( + { setSafetyCenterConfigOverride(safetyCenterConfig) }, + MANAGE_SAFETY_CENTER + ) + +/** + * Calls [SafetyCenterManager.clearSafetyCenterConfigOverride] adopting Shell's + * [MANAGE_SAFETY_CENTER] permission. + */ +fun SafetyCenterManager.clearSafetyCenterConfigOverrideWithPermission() = + callWithShellPermissionIdentity({ clearSafetyCenterConfigOverride() }, MANAGE_SAFETY_CENTER) diff --git a/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterFlags.kt new file mode 100644 index 000000000..8ae335c7c --- /dev/null +++ b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterFlags.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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 android.safetycenter.testing + +import android.Manifest.permission.WRITE_DEVICE_CONFIG +import android.content.Context +import android.content.res.Resources +import android.provider.DeviceConfig +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity + +/** A class that facilitates working with Safety Center flags. */ +object SafetyCenterFlags { + + /** Name of the flag that determines whether SafetyCenter is enabled. */ + private const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled" + + /** Returns whether the device supports Safety Center. */ + fun Context.deviceSupportsSafetyCenter() = + resources.getBoolean( + Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android") + ) + + /** Sets the Safety Center device config flag to the given boolean [value]. */ + fun setSafetyCenterEnabled(value: Boolean) { + callWithShellPermissionIdentity( + { + val valueWasSet = + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_SAFETY_CENTER_ENABLED, + /* value = */ value.toString(), + /* makeDefault = */ false + ) + if (!valueWasSet) { + throw IllegalStateException("Could not set Safety Center flag value to: $value") + } + }, + WRITE_DEVICE_CONFIG + ) + } +}
\ No newline at end of file diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceBroadcastReceiver.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetySourceBroadcastReceiver.kt index 5f8e8b890..8097d9dbb 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceBroadcastReceiver.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetySourceBroadcastReceiver.kt @@ -14,19 +14,19 @@ * limitations under the License. */ -package android.safetycenter.cts +package android.safetycenter.testing import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.safetycenter.SafetyCenterManager import android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES -import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA +import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE -import android.safetycenter.SafetyCenterManager -import android.safetycenter.SafetySourceData import android.safetycenter.SafetyEvent import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED +import android.safetycenter.SafetySourceData import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout @@ -48,46 +48,49 @@ class SafetySourceBroadcastReceiver : BroadcastReceiver() { when (intent.getIntExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, -1)) { EXTRA_REFRESH_REQUEST_TYPE_GET_DATA -> safetyCenterManager.setSafetySourceDataWithPermission( - safetySourceId!!, - safetySourceDataOnPageOpen!!, - EVENT_REFRESH_REQUESTED + safetySourceId!!, + safetySourceDataOnPageOpen!!, + EVENT_REFRESH_REQUESTED ) EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA -> safetyCenterManager.setSafetySourceDataWithPermission( - safetySourceId!!, - safetySourceDataOnRescanClick!!, - EVENT_REFRESH_REQUESTED + safetySourceId!!, + safetySourceDataOnRescanClick!!, + EVENT_REFRESH_REQUESTED ) } - runBlocking { - updateChannel.send(Unit) - } + runBlocking { updateChannel.send(Unit) } } companion object { - private var updateChannel = Channel<Unit>() private val EVENT_REFRESH_REQUESTED = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId("refresh_id") - .build() + SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("refresh_id") + .build() + + @Volatile + private var updateChannel = Channel<Unit>() + + @Volatile var safetySourceId: String? = null + + @Volatile var safetySourceDataOnPageOpen: SafetySourceData? = null + + @Volatile var safetySourceDataOnRescanClick: SafetySourceData? = null fun reset() { safetySourceId = null safetySourceDataOnRescanClick = null safetySourceDataOnPageOpen = null + updateChannel.cancel() updateChannel = Channel() } fun waitTillOnReceiveComplete(duration: Duration) { - runBlocking { - withTimeout(duration.toMillis()) { - updateChannel.receive() - } - } + runBlocking { withTimeout(duration.toMillis()) { updateChannel.receive() } } } } } |