diff options
54 files changed, 870 insertions, 758 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java index cb6d21aec..cf9a66e68 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java @@ -31,7 +31,6 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.PERM import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN; import android.Manifest; -import android.app.ActionBar; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -547,15 +546,6 @@ public final class ManagePermissionsActivity extends SettingsActivity { } @Override - public ActionBar getActionBar() { - ActionBar ab = super.getActionBar(); - if (ab != null) { - ab.setHomeActionContentDescription(R.string.back); - } - return ab; - } - - @Override public boolean onOptionsItemSelected(MenuItem item) { // in automotive mode, there's no system wide back button, so need to add that if (DeviceUtils.isAuto(this)) { diff --git a/service/java/com/android/safetycenter/PendingIntentFactory.java b/service/java/com/android/safetycenter/PendingIntentFactory.java index 8c447c477..4f54e0b82 100644 --- a/service/java/com/android/safetycenter/PendingIntentFactory.java +++ b/service/java/com/android/safetycenter/PendingIntentFactory.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.Objects.requireNonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.Context; @@ -32,6 +31,7 @@ import android.os.Binder; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.safetycenter.resources.SafetyCenterResourcesContext; @@ -235,7 +235,8 @@ public final class PendingIntentFactory { // This call requires the INTERACT_ACROSS_USERS permission. final long callingId = Binder.clearCallingIdentity(); try { - return context.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)); + return context.createPackageContextAsUser( + packageName, /* flags= */ 0, UserHandle.of(userId)); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Package name " + packageName + " not found", e); return null; diff --git a/service/java/com/android/safetycenter/RefreshReasons.java b/service/java/com/android/safetycenter/RefreshReasons.java index ee318c7fd..af15d1968 100644 --- a/service/java/com/android/safetycenter/RefreshReasons.java +++ b/service/java/com/android/safetycenter/RefreshReasons.java @@ -35,8 +35,6 @@ import android.util.Log; import androidx.annotation.RequiresApi; -import com.android.modules.utils.build.SdkLevel; - /** Helpers to do with {@link RefreshReason}. */ @RequiresApi(TIRAMISU) final class RefreshReasons { @@ -49,6 +47,7 @@ final class RefreshReasons { * Validates the given {@link RefreshReason}, and throws an {@link IllegalArgumentException} in * case of unexpected value. */ + @TargetApi(UPSIDE_DOWN_CAKE) static void validate(@RefreshReason int refreshReason) { switch (refreshReason) { case REFRESH_REASON_RESCAN_BUTTON_CLICK: @@ -57,11 +56,9 @@ final class RefreshReasons { case REFRESH_REASON_DEVICE_LOCALE_CHANGE: case REFRESH_REASON_SAFETY_CENTER_ENABLED: case REFRESH_REASON_OTHER: + case REFRESH_REASON_PERIODIC: return; } - if (SdkLevel.isAtLeastU() && refreshReason == REFRESH_REASON_PERIODIC) { - return; - } throw new IllegalArgumentException("Unexpected refresh reason: " + refreshReason); } diff --git a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java index e8e6befe5..bc386f3f8 100644 --- a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java +++ b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java @@ -32,8 +32,6 @@ import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CEN import static java.util.Collections.unmodifiableList; -import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.BroadcastOptions; import android.content.Context; @@ -48,6 +46,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.PackageUtils; @@ -198,7 +197,11 @@ final class SafetyCenterBroadcastDispatcher { } Intent implicitIntent = createImplicitEnabledChangedIntent(); - sendBroadcast(implicitIntent, UserHandle.SYSTEM, READ_SAFETY_CENTER_STATUS, null); + sendBroadcast( + implicitIntent, + UserHandle.SYSTEM, + READ_SAFETY_CENTER_STATUS, + /* broadcastOptions= */ null); } private void sendEnabledChangedBroadcast( @@ -206,15 +209,17 @@ final class SafetyCenterBroadcastDispatcher { BroadcastOptions broadcastOptions, List<UserProfileGroup> userProfileGroups) { Intent intent = createExplicitEnabledChangedIntent(broadcast.getPackageName()); - // The same ENABLED reason is used here for both enable and disable events. It is not sent - // externally and is only used internally to filter safety sources in the methods of the - // Broadcast class. - int refreshReason = REFRESH_REASON_SAFETY_CENTER_ENABLED; for (int i = 0; i < userProfileGroups.size(); i++) { UserProfileGroup userProfileGroup = userProfileGroups.get(i); SparseArray<List<String>> userIdsToSourceIds = - getUserIdsToSourceIds(broadcast, userProfileGroup, refreshReason); + getUserIdsToSourceIds( + broadcast, + userProfileGroup, + // The same ENABLED reason is used here for both enable and disable + // events. It is not sent externally and is only used internally to + // filter safety sources in the methods of the Broadcast class. + REFRESH_REASON_SAFETY_CENTER_ENABLED); for (int j = 0; j < userIdsToSourceIds.size(); j++) { int userId = userIdsToSourceIds.keyAt(j); @@ -229,24 +234,22 @@ final class SafetyCenterBroadcastDispatcher { if (!doesBroadcastResolve(intent, userHandle)) { Log.w( TAG, - "No receiver for intent targeting " + "No receiver for intent targeting: " + intent.getPackage() - + " and user " - + userHandle); + + ", and user id: " + + userHandle.getIdentifier()); return false; } Log.v( TAG, - "Found receiver for intent targeting " + "Found receiver for intent targeting: " + intent.getPackage() - + " and user " - + userHandle); + + ", and user id: " + + userHandle.getIdentifier()); sendBroadcast(intent, userHandle, SEND_SAFETY_CENTER_UPDATE, broadcastOptions); return true; } - // TODO(b/193460475): Remove when tooling supports SystemApi to public API. - @SuppressLint("NewApi") private void sendBroadcast( Intent intent, UserHandle userHandle, @@ -267,7 +270,7 @@ final class SafetyCenterBroadcastDispatcher { private boolean doesBroadcastResolve(Intent broadcastIntent, UserHandle userHandle) { return !PackageUtils.queryUnfilteredBroadcastReceiversAsUser( - broadcastIntent, 0, userHandle.getIdentifier(), mContext) + broadcastIntent, /* flags= */ 0, userHandle.getIdentifier(), mContext) .isEmpty(); } @@ -296,8 +299,6 @@ final class SafetyCenterBroadcastDispatcher { return new Intent(intentAction).setFlags(FLAG_RECEIVER_FOREGROUND); } - // TODO(b/193460475): Remove when tooling supports SystemApi to public API. - @SuppressLint("NewApi") private static BroadcastOptions createBroadcastOptions() { BroadcastOptions broadcastOptions = BroadcastOptions.makeBasic(); Duration allowListDuration = SafetyCenterFlags.getFgsAllowlistDuration(); diff --git a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java index 92959a47d..ecb741a4b 100644 --- a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java +++ b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java @@ -21,7 +21,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; -import android.annotation.Nullable; import android.content.res.Resources; import android.safetycenter.config.SafetyCenterConfig; import android.safetycenter.config.SafetySource; @@ -29,6 +28,7 @@ import android.safetycenter.config.SafetySourcesGroup; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.safetycenter.config.ParseException; @@ -114,7 +114,7 @@ public final class SafetyCenterConfigReader { /** * Returns the groups of {@link SafetySource}, filtering out any sources where {@link - * SafetySources#isLoggable(SafetySource)} is false (and any resultingly empty groups). + * SafetySources#isLoggable(SafetySource)} is {@code false} (and any resulting empty groups). */ public List<SafetySourcesGroup> getLoggableSafetySourcesGroups() { return getCurrentConfigInternal().getLoggableSourcesGroups(); @@ -228,23 +228,23 @@ public final class SafetyCenterConfigReader { private SafetyCenterConfig readSafetyCenterConfig() { InputStream in = mSafetyCenterResourcesContext.getSafetyCenterConfig(); if (in == null) { - Log.e(TAG, "Cannot get safety center config file, safety center will be disabled."); + Log.e(TAG, "Cannot get safety center config file, Safety Center will be disabled"); return null; } Resources resources = mSafetyCenterResourcesContext.getResources(); if (resources == null) { - Log.e(TAG, "Cannot get safety center resources, safety center will be disabled."); + Log.e(TAG, "Cannot get safety center resources, Safety Center will be disabled"); return null; } try { SafetyCenterConfig safetyCenterConfig = SafetyCenterConfigParser.parseXmlResource(in, resources); - Log.i(TAG, "SafetyCenterConfig read successfully"); + Log.d(TAG, "SafetyCenterConfig read successfully"); return safetyCenterConfig; } catch (ParseException e) { - Log.e(TAG, "Cannot read SafetyCenterConfig, safety center will be disabled.", e); + Log.e(TAG, "Cannot read SafetyCenterConfig, Safety Center will be disabled", e); return null; } } diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java index fe4a1ee43..69c8a58c9 100644 --- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java +++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java @@ -17,13 +17,14 @@ package com.android.safetycenter; import static android.os.Build.VERSION_CODES.TIRAMISU; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY; import static com.android.safetycenter.internaldata.SafetyCenterBundles.STATIC_ENTRIES_TO_IDS_BUNDLE_KEY; import static java.util.Collections.emptyList; -import android.annotation.Nullable; +import android.annotation.TargetApi; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.Context; @@ -50,6 +51,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -400,8 +402,8 @@ public final class SafetyCenterDataFactory { safetySource, defaultPackageName, userProfileGroup.getProfileParentUserId(), - false, - false)); + /* isUserManaged= */ false, + /* isManagedUserRunning= */ false)); if (!SafetySources.supportsManagedProfiles(safetySource)) { continue; @@ -422,7 +424,7 @@ public final class SafetyCenterDataFactory { safetySource, defaultPackageName, managedProfileUserId, - true, + /* isUserManaged= */ true, isManagedUserRunning)); } } @@ -509,10 +511,7 @@ public final class SafetyCenterDataFactory { for (int i = 0; i < entries.size(); i++) { SafetySourceKey key = toSafetySourceKey(entries.get(i).getId()); if (mSafetyCenterDataManager.sourceHasError(key)) { - // We always use the singular form of the error string for groups because - // they appear as single entries in the UI and this ensures consistency, - // especially when subpages are enabled. - return getRefreshErrorString(1); + return getRefreshErrorString(); } } return mSafetyCenterResourcesContext.getStringByName("group_unknown_summary"); @@ -717,7 +716,7 @@ public final class SafetyCenterDataFactory { CharSequence summary = mSafetyCenterDataManager.sourceHasError( SafetySourceKey.of(safetySource.getId(), userId)) - ? getRefreshErrorString(1) + ? getRefreshErrorString() : mSafetyCenterResourcesContext.getOptionalString( safetySource.getSummaryResId()); if (isQuietModeEnabled) { @@ -754,8 +753,8 @@ public final class SafetyCenterDataFactory { safetySource, defaultPackageName, userProfileGroup.getProfileParentUserId(), - false, - false); + /* isUserManaged= */ false, + /* isManagedUserRunning= */ false); if (!SafetySources.supportsManagedProfiles(safetySource)) { continue; @@ -774,7 +773,7 @@ public final class SafetyCenterDataFactory { safetySource, defaultPackageName, managedProfileUserId, - true, + /* isUserManaged= */ true, isManagedUserRunning); } } @@ -921,7 +920,7 @@ public final class SafetyCenterDataFactory { CharSequence summary = mSafetyCenterDataManager.sourceHasError( SafetySourceKey.of(safetySource.getId(), userId)) - ? getRefreshErrorString(1) + ? getRefreshErrorString() : mSafetyCenterResourcesContext.getOptionalString( safetySource.getSummaryResId()); if (isQuietModeEnabled) { @@ -1117,6 +1116,7 @@ public final class SafetyCenterDataFactory { return ""; } + @TargetApi(UPSIDE_DOWN_CAKE) private String getStatusTitleFromIssueCategories( @Nullable SafetySourceIssueInfo topNonDismissedIssueInfo, String deviceResourceName, @@ -1138,17 +1138,12 @@ public final class SafetyCenterDataFactory { return mSafetyCenterResourcesContext.getStringByName(accountResourceName); case SafetySourceIssue.ISSUE_CATEGORY_GENERAL: return generalString; - } - if (SdkLevel.isAtLeastU()) { - switch (issueCategory) { - case SafetySourceIssue.ISSUE_CATEGORY_DATA: - return mSafetyCenterResourcesContext.getStringByName(dataResourceName); - case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS: - return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName); - case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY: - return mSafetyCenterResourcesContext.getStringByName( - personalSafetyResourceName); - } + case SafetySourceIssue.ISSUE_CATEGORY_DATA: + return mSafetyCenterResourcesContext.getStringByName(dataResourceName); + case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS: + return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName); + case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY: + return mSafetyCenterResourcesContext.getStringByName(personalSafetyResourceName); } Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory); @@ -1207,8 +1202,8 @@ public final class SafetyCenterDataFactory { == SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC; } - private String getRefreshErrorString(int numberOfErrorEntries) { - return getIcuPluralsString("refresh_error", numberOfErrorEntries); + private String getRefreshErrorString() { + return getIcuPluralsString("refresh_error", /* count= */ 1); } private String getIcuPluralsString(String name, int count, Object... formatArgs) { diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index 1fc88d4b0..62855f532 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -19,7 +19,6 @@ package com.android.safetycenter; import static android.os.Build.VERSION_CODES.TIRAMISU; import static android.safetycenter.SafetyCenterManager.RefreshReason; -import android.annotation.Nullable; import android.os.Binder; import android.provider.DeviceConfig; import android.safetycenter.SafetySourceData; @@ -27,6 +26,7 @@ import android.safetycenter.SafetySourceIssue; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -442,7 +442,7 @@ public final class SafetyCenterFlags { if (allowlistedCertString == null) { return new ArraySet<>(); } - return new ArraySet<String>(allowlistedCertString.split("\\|")); + return new ArraySet<>(allowlistedCertString.split("\\|")); } /** diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java index 9e07c3d17..6a83b5e81 100644 --- a/service/java/com/android/safetycenter/SafetyCenterListeners.java +++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java @@ -18,7 +18,6 @@ package com.android.safetycenter; import static android.os.Build.VERSION_CODES.TIRAMISU; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.IBinder; import android.os.RemoteCallbackList; @@ -30,6 +29,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import java.io.PrintWriter; @@ -67,7 +67,7 @@ final class SafetyCenterListeners { try { listener.onSafetyCenterDataChanged(safetyCenterData); } catch (RemoteException e) { - Log.e(TAG, "Error delivering SafetyCenterData to listener", e); + Log.w(TAG, "Error delivering SafetyCenterData to listener", e); } } @@ -81,7 +81,7 @@ final class SafetyCenterListeners { try { listener.onError(safetyCenterErrorDetails); } catch (RemoteException e) { - Log.e(TAG, "Error delivering SafetyCenterErrorDetails to listener", e); + Log.w(TAG, "Error delivering SafetyCenterErrorDetails to listener", e); } } @@ -94,7 +94,11 @@ final class SafetyCenterListeners { int[] relevantUserIds = userProfileGroup.getProfileParentAndManagedRunningProfilesUserIds(); for (int i = 0; i < relevantUserIds.length; i++) { deliverUpdateForUser( - relevantUserIds[i], userProfileGroup, safetyCenterDataCache, true, null); + relevantUserIds[i], + userProfileGroup, + safetyCenterDataCache, + /* updateSafetyCenterData= */ true, + /* safetyCenterErrorDetails= */ null); } } @@ -111,7 +115,7 @@ final class SafetyCenterListeners { relevantUserIds[i], userProfileGroup, safetyCenterDataCache, - false, + /* updateSafetyCenterData= */ false, safetyCenterErrorDetails); } } diff --git a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java index a28ce7d22..2a0fa786b 100644 --- a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java +++ b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java @@ -23,7 +23,6 @@ import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVE import static com.android.safetycenter.logging.SafetyCenterStatsdLogger.toSystemEventResult; import android.annotation.ElapsedRealtimeLong; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.os.SystemClock; @@ -34,6 +33,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.UserUtils; @@ -78,15 +78,15 @@ public final class SafetyCenterRefreshTracker { String reportRefreshInProgress( @RefreshReason int refreshReason, UserProfileGroup userProfileGroup) { if (mRefreshInProgress != null) { - Log.w(TAG, "Replacing an ongoing refresh"); + Log.i(TAG, "Replacing an ongoing refresh"); } String refreshBroadcastId = UUID.randomUUID() + "_" + mRefreshCounter++; - Log.v( + Log.d( TAG, - "Starting a new refresh with refreshReason:" + "Starting a new refresh with reason: " + refreshReason - + " refreshBroadcastId:" + + ", and id: " + refreshBroadcastId); mRefreshInProgress = @@ -237,7 +237,7 @@ public final class SafetyCenterRefreshTracker { */ void clearRefreshForUser(@UserIdInt int userId) { if (mRefreshInProgress == null) { - Log.v(TAG, "Clear refresh for user called but no refresh in progress"); + Log.d(TAG, "Clear refresh for user called but no refresh in progress"); return; } if (mRefreshInProgress.clearForUser(userId)) { @@ -298,7 +298,7 @@ public final class SafetyCenterRefreshTracker { } /** - * Clears the any refresh in progress and returns it for the caller to do what it needs to. + * Clears the refresh in progress and returns it for the caller to do what it needs to. * * <p>If there was no refresh in progress then {@code null} is returned. */ @@ -306,11 +306,11 @@ public final class SafetyCenterRefreshTracker { private RefreshInProgress clearRefreshInternal() { RefreshInProgress refreshToClear = mRefreshInProgress; if (refreshToClear == null) { - Log.v(TAG, "Clear refresh called but no refresh in progress"); + Log.d(TAG, "Clear refresh called but no refresh in progress"); return null; } - Log.v(TAG, "Clearing refresh with refreshBroadcastId:" + refreshToClear.getId()); + Log.v(TAG, "Clearing refresh with id: " + refreshToClear.getId()); mRefreshInProgress = null; return refreshToClear; } @@ -324,13 +324,7 @@ public final class SafetyCenterRefreshTracker { String methodName, String refreshBroadcastId) { RefreshInProgress refreshInProgress = mRefreshInProgress; if (refreshInProgress == null || !refreshInProgress.getId().equals(refreshBroadcastId)) { - Log.i( - TAG, - methodName - + " called for invalid refresh broadcast id: " - + refreshBroadcastId - + "; no such refresh in" - + " progress"); + Log.i(TAG, methodName + " called with invalid refresh id: " + refreshBroadcastId); return null; } return refreshInProgress; @@ -435,19 +429,19 @@ public final class SafetyCenterRefreshTracker { } Log.v( TAG, - "Refresh started for sourceId:" + "Refresh with id: " + + mId + + " started for source id: " + safetySourceKey.getSourceId() - + " userId:" + + ", user id: " + safetySourceKey.getUserId() - + " with refreshBroadcastId:" - + mId - + " at currentElapsedMillis:" + + ", elapsed millis: " + currentElapsedMillis - + " & tracking:" + + ", tracking: " + tracked + ", now " + mSourceRefreshesInFlight.size() - + " tracked sources in flight."); + + " tracked sources in flight"); } @Nullable @@ -464,23 +458,23 @@ public final class SafetyCenterRefreshTracker { : Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis); Log.v( TAG, - "Refresh completed for sourceId:" + "Refresh with id: " + + mId + + " completed for source id: " + safetySourceKey.getSourceId() - + " userId:" + + ", user id: " + safetySourceKey.getUserId() - + " with refreshBroadcastId:" - + mId - + " duration:" + + ", duration: " + duration - + " successful:" + + ", successful: " + successful - + " dataChanged:" + + ", data changed: " + dataChanged - + " & tracking:" + + ", tracking: " + tracked - + ", " + + ", now " + mSourceRefreshesInFlight.size() - + " tracked sources still in flight."); + + " tracked sources in flight"); return duration; } diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index f23f041bd..3400dad2f 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -35,7 +35,6 @@ import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriend import static java.util.Objects.requireNonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.StatsManager; @@ -69,6 +68,7 @@ import android.util.ArraySet; import android.util.Log; import androidx.annotation.Keep; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; @@ -192,7 +192,7 @@ public final class SafetyCenterService extends SystemService { .getIdentifier( "config_enableSafetyCenter", "bool", "android")); if (!mDeviceSupportsSafetyCenter) { - Log.i(TAG, "Device does not support safety center, safety center will be disabled."); + Log.i(TAG, "Device does not support Safety Center, it will be disabled"); } } @@ -208,7 +208,7 @@ public final class SafetyCenterService extends SystemService { mSafetyCenterDataManager.loadPersistableDataStateFromFile(); new UserBroadcastReceiver().register(getContext()); new SafetyCenterNotificationReceiver( - this, + /* service= */ this, mSafetyCenterDataManager, mSafetyCenterDataChangeNotifier, mApiLock) @@ -241,7 +241,10 @@ public final class SafetyCenterService extends SystemService { StatsManager statsManager = requireNonNull(getContext().getSystemService(StatsManager.class)); statsManager.setPullAtomCallback( - SAFETY_STATE, null, BackgroundThread.getExecutor(), mPullAtomCallback); + SAFETY_STATE, + /* metadata= */ null, + BackgroundThread.getExecutor(), + mPullAtomCallback); } /** Service implementation of {@link ISafetyCenterManager.Stub}. */ @@ -392,7 +395,7 @@ public final class SafetyCenterService extends SystemService { // search works by adding all the entries very rarely (and relies on filtering them out // instead). if (!canUseSafetyCenter()) { - Log.w(TAG, "Called getSafetyCenterConfig, but Safety Center is not supported"); + Log.i(TAG, "Called getSafetyCenterConfig, but Safety Center is not supported"); return null; } @@ -504,7 +507,7 @@ public final class SafetyCenterService extends SystemService { PendingIntent onDismissPendingIntent = safetySourceIssue.getOnDismissPendingIntent(); if (onDismissPendingIntent != null - && !dispatchPendingIntent(onDismissPendingIntent, null)) { + && !dispatchPendingIntent(onDismissPendingIntent)) { Log.w( TAG, "Error dispatching dismissal for issue: " @@ -634,13 +637,14 @@ public final class SafetyCenterService extends SystemService { /** Enforces cross user permission and returns whether the user is valid. */ private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, message, getContext()); + UserUtils.enforceCrossUserPermission( + userId, /* allowAll= */ false, message, getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.w( TAG, "Called " + message - + " with user id " + + " with user id: " + userId + ", which does not correspond to an existing user"); return false; @@ -650,7 +654,7 @@ public final class SafetyCenterService extends SystemService { TAG, "Called " + message - + " with user id " + + " with user id: " + userId + ", which is an unsupported user"); return false; @@ -676,7 +680,7 @@ public final class SafetyCenterService extends SystemService { packageManager.getPackageUidAsUser( packageName, PackageInfoFlags.of(0), userId); } catch (NameNotFoundException e) { - Log.e(TAG, "packageName=" + packageName + ", not found for userId=" + userId, e); + Log.w(TAG, "Package: " + packageName + ", not found for user id: " + userId, e); return false; } if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { @@ -684,9 +688,9 @@ public final class SafetyCenterService extends SystemService { } if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(actualUid)) { throw new SecurityException( - "packageName=" + "Package: " + packageName - + ", does not belong to callingUid=" + + ", does not belong to calling uid: " + callingUid); } return true; @@ -719,9 +723,11 @@ public final class SafetyCenterService extends SystemService { ParcelFileDescriptor err, String[] args) { return new SafetyCenterShellCommandHandler( - getContext(), this, mDeviceSupportsSafetyCenter) + getContext(), + /* safetyCenterManager= */ this, + mDeviceSupportsSafetyCenter) .exec( - this, + /* target= */ this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), @@ -816,11 +822,11 @@ public final class SafetyCenterService extends SystemService { private void setInitialState() { mSafetyCenterEnabled = SafetyCenterFlags.getSafetyCenterEnabled(); - Log.w(TAG, "SafetyCenter is " + (mSafetyCenterEnabled ? "enabled." : "disabled.")); + Log.i(TAG, "Safety Center is " + (mSafetyCenterEnabled ? "enabled" : "disabled")); } private void onSafetyCenterEnabledChanged(boolean safetyCenterEnabled) { - Log.w(TAG, "SafetyCenter is now " + (safetyCenterEnabled ? "enabled." : "disabled.")); + Log.i(TAG, "Safety Center is now " + (safetyCenterEnabled ? "enabled" : "disabled")); if (safetyCenterEnabled) { onApiEnabled(); @@ -889,9 +895,7 @@ public final class SafetyCenterService extends SystemService { } } - Log.v( - TAG, - "Cleared refresh with broadcastId:" + mRefreshBroadcastId + " after a timeout"); + Log.w(TAG, "Timeout for refresh with id: " + mRefreshBroadcastId); } @Override @@ -966,7 +970,11 @@ public final class SafetyCenterService extends SystemService { void register(Context context) { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_LOCALE_CHANGED); - context.registerReceiverForAllUsers(this, filter, null, null); + context.registerReceiverForAllUsers( + /* receiver= */ this, + filter, + /* broadcastPermission= */ null, + /* scheduler= */ null); } @Override @@ -992,42 +1000,46 @@ public final class SafetyCenterService extends SystemService { filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - context.registerReceiverForAllUsers(this, filter, null, null); + context.registerReceiverForAllUsers( + /* receiver= */ this, + filter, + /* broadcastPermission= */ null, + /* scheduler= */ null); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { - Log.w(TAG, "Received broadcast with null action!"); + Log.w(TAG, "Received broadcast with null action"); return; } UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); if (userHandle == null) { - Log.w(TAG, "Received " + action + " broadcast missing user extra!"); + Log.w(TAG, "Received action: " + action + ", but missing user extra"); return; } int userId = userHandle.getIdentifier(); + Log.d(TAG, "Received action: " + action + ", for user id: " + userId); if (!UserProfileGroup.isSupported(userId, context)) { Log.i( TAG, - "Received broadcast for user id " + "Received broadcast for user id: " + userId + ", which is an unsupported user"); return; } - Log.d(TAG, "Received " + action + " broadcast for user " + userId); switch (action) { case Intent.ACTION_USER_REMOVED: case Intent.ACTION_MANAGED_PROFILE_REMOVED: - removeUser(userId, true); + removeUserAndData(userId); break; case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: - removeUser(userId, false); - // fall through! + removeUser(userId); + break; case Intent.ACTION_USER_ADDED: case Intent.ACTION_MANAGED_PROFILE_ADDED: case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: @@ -1038,6 +1050,14 @@ public final class SafetyCenterService extends SystemService { } } + private void removeUserAndData(@UserIdInt int userId) { + removeUser(userId, /* clearDataPermanently= */ true); + } + + private void removeUser(@UserIdInt int userId) { + removeUser(userId, /* clearDataPermanently= */ false); + } + private void removeUser(@UserIdInt int userId, boolean clearDataPermanently) { UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); synchronized (mApiLock) { @@ -1055,7 +1075,7 @@ public final class SafetyCenterService extends SystemService { private void startRefreshingSafetySources( @RefreshReason int refreshReason, @UserIdInt int userId) { - startRefreshingSafetySources(refreshReason, userId, null); + startRefreshingSafetySources(refreshReason, userId, /* selectedSafetySourceIds= */ null); } private void startRefreshingSafetySources( @@ -1091,7 +1111,7 @@ public final class SafetyCenterService extends SystemService { safetyCenterIssueActionId.getSafetyCenterIssueKey(); UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), safetyCenterIssueKey.getUserId()); - executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, null); + executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, /* taskId= */ null); } private void executeIssueActionInternal( @@ -1141,6 +1161,10 @@ public final class SafetyCenterService extends SystemService { } } + private boolean dispatchPendingIntent(PendingIntent pendingIntent) { + return dispatchPendingIntent(pendingIntent, /* launchTaskId= */ null); + } + private boolean dispatchPendingIntent( PendingIntent pendingIntent, @Nullable Integer launchTaskId) { if (launchTaskId != null diff --git a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java index 87e3372f7..9d6b40bfa 100644 --- a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java +++ b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java @@ -27,13 +27,13 @@ import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CEN import static java.util.Collections.unmodifiableMap; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.os.RemoteException; import android.safetycenter.ISafetyCenterManager; import android.safetycenter.SafetyCenterManager.RefreshReason; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.BasicShellCommandHandler; diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java index 8d3adc573..f0407f6b3 100644 --- a/service/java/com/android/safetycenter/UserProfileGroup.java +++ b/service/java/com/android/safetycenter/UserProfileGroup.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.Objects.requireNonNull; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.PackageManager; @@ -30,6 +29,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.UserUtils; @@ -141,9 +141,9 @@ public final class UserProfileGroup { Arrays.copyOf( managedRunningProfilesUserIds, managedRunningProfilesUserIdsLen)); if (!userProfileGroup.contains(userId)) { - Log.w( + Log.i( TAG, - "User id " + userId + " does not belong to " + userProfileGroup, + "User id: " + userId + " does not belong to: " + userProfileGroup, new Exception()); } return userProfileGroup; @@ -167,7 +167,8 @@ public final class UserProfileGroup { return context; } else { try { - return context.createPackageContextAsUser(context.getPackageName(), 0, userHandle); + return context.createPackageContextAsUser( + context.getPackageName(), /* flags= */ 0, userHandle); } catch (PackageManager.NameNotFoundException doesNotHappen) { throw new IllegalStateException(doesNotHappen); } @@ -232,9 +233,9 @@ public final class UserProfileGroup { profileParentAndManagedRunningProfilesUserIds[0] = mProfileParentUserId; System.arraycopy( mManagedRunningProfilesUserIds, - 0, + /* srcPos= */ 0, profileParentAndManagedRunningProfilesUserIds, - 1, + /* destPos= */ 1, mManagedRunningProfilesUserIds.length); return profileParentAndManagedRunningProfilesUserIds; } diff --git a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java index 5db3cfbad..a52ad0aca 100644 --- a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java +++ b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java @@ -18,7 +18,6 @@ package com.android.safetycenter.data; import static android.os.Build.VERSION_CODES.TIRAMISU; -import android.annotation.Nullable; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -30,6 +29,7 @@ import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -119,7 +119,9 @@ final class AndroidLockScreenFix { safetySourceStatus.getSeverityLevel()) .setPendingIntent( overridePendingIntent( - context, safetySourceStatus.getPendingIntent(), false)) + context, + safetySourceStatus.getPendingIntent(), + /* isIconAction= */ false)) .setEnabled(safetySourceStatus.isEnabled()); SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction(); if (iconAction != null) { @@ -134,7 +136,8 @@ final class AndroidLockScreenFix { Context context, SafetySourceStatus.IconAction iconAction) { return new SafetySourceStatus.IconAction( iconAction.getIconType(), - overridePendingIntent(context, iconAction.getPendingIntent(), true)); + overridePendingIntent( + context, iconAction.getPendingIntent(), /* isIconAction= */ true)); } private static SafetySourceIssue overrideTiramisuSafetySourceIssue( @@ -163,7 +166,8 @@ final class AndroidLockScreenFix { return new SafetySourceIssue.Action.Builder( action.getId(), action.getLabel(), - overridePendingIntent(context, action.getPendingIntent(), false)) + overridePendingIntent( + context, action.getPendingIntent(), /* isIconAction= */ false)) .setWillResolve(action.willResolve()) .setSuccessMessage(action.getSuccessMessage()) .build(); @@ -206,7 +210,7 @@ final class AndroidLockScreenFix { // This is important because there are scenarios where the Settings app provides different // pending intents (e.g. in the work profile), and in this case we shouldn't override them. if (isIconAction) { - Log.w( + Log.i( TAG, "Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " icon action pending intent"); return PendingIntentFactory.getActivityPendingIntent( @@ -215,7 +219,7 @@ final class AndroidLockScreenFix { newLockScreenIconActionIntent(settingsPackageName), PendingIntent.FLAG_IMMUTABLE); } - Log.w(TAG, "Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " entry or issue pending intent"); + Log.i(TAG, "Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " entry or issue pending intent"); return PendingIntentFactory.getActivityPendingIntent( settingsPackageContext, ANDROID_LOCK_SCREEN_ENTRY_REQ_CODE, diff --git a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java index 734732401..917af9b91 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.android.safetycenter.logging.SafetyCenterStatsdLogger.toSystemEventResult; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.safetycenter.SafetyCenterData; @@ -32,6 +31,7 @@ import android.safetycenter.config.SafetyCenterConfig; import android.safetycenter.config.SafetySourcesGroup; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.safetycenter.ApiLock; @@ -151,7 +151,12 @@ public final class SafetyCenterDataManager { mSafetySourceDataRepository.setSafetySourceData( safetySourceData, safetySourceId, userId); boolean eventCausedChange = - processSafetyEvent(safetySourceId, safetyEvent, userId, false, sourceDataDiffers); + processSafetyEvent( + safetySourceId, + safetyEvent, + userId, + /* isError= */ false, + sourceDataDiffers); boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange; if (safetyCenterDataChanged) { @@ -199,7 +204,7 @@ public final class SafetyCenterDataManager { String packageName, @UserIdInt int userId) { if (!mSafetySourceDataValidator.validateRequest( - null, safetySourceId, packageName, userId)) { + /* safetySourceData= */ null, safetySourceId, packageName, userId)) { return false; } SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent(); @@ -217,7 +222,12 @@ public final class SafetyCenterDataManager { mSafetySourceDataRepository.reportSafetySourceError( safetySourceErrorDetails, safetySourceId, userId); boolean eventCausedChange = - processSafetyEvent(safetySourceId, safetyEvent, userId, true, sourceDataDiffers); + processSafetyEvent( + safetySourceId, + safetyEvent, + userId, + /* isError= */ true, + sourceDataDiffers); boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange; if (safetyCenterDataChanged) { @@ -225,7 +235,12 @@ public final class SafetyCenterDataManager { } mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom( - key, null, refreshReason, sourceDataDiffers, userId, safetyEvent); + key, + /* safetySourceData= */ null, + refreshReason, + sourceDataDiffers, + userId, + safetyEvent); return safetyCenterDataChanged; } @@ -407,7 +422,7 @@ public final class SafetyCenterDataManager { public SafetySourceData getSafetySourceData( String safetySourceId, String packageName, @UserIdInt int userId) { if (!mSafetySourceDataValidator.validateRequest( - null, safetySourceId, packageName, userId)) { + /* safetySourceData= */ null, safetySourceId, packageName, userId)) { return null; } return mSafetySourceDataRepository.getSafetySourceData( @@ -482,7 +497,7 @@ public final class SafetyCenterDataManager { case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED: String refreshBroadcastId = safetyEvent.getRefreshBroadcastId(); if (refreshBroadcastId == null) { - Log.w(TAG, "No refresh broadcast id in SafetyEvent of type " + type); + Log.w(TAG, "No refresh broadcast id in SafetyEvent of type: " + type); return false; } return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted( @@ -491,12 +506,12 @@ public final class SafetyCenterDataManager { case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED: String safetySourceIssueId = safetyEvent.getSafetySourceIssueId(); if (safetySourceIssueId == null) { - Log.w(TAG, "No safety source issue id in SafetyEvent of type " + type); + Log.w(TAG, "No safety source issue id in SafetyEvent of type: " + type); return false; } String safetySourceIssueActionId = safetyEvent.getSafetySourceIssueActionId(); if (safetySourceIssueActionId == null) { - Log.w(TAG, "No safety source issue action id in SafetyEvent of type " + type); + Log.w(TAG, "No safety source issue action id in SafetyEvent of type: " + type); return false; } SafetyCenterIssueKey safetyCenterIssueKey = diff --git a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java index 4fa6e5363..b4db99069 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.os.SystemClock; @@ -28,6 +27,7 @@ import android.safetycenter.SafetySourceIssue; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.UserUtils; diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java index d5218a0aa..2353f6ce6 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java @@ -27,13 +27,13 @@ import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.safetycenter.config.SafetySourcesGroup; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.safetycenter.SafetySourceIssueInfo; @@ -72,7 +72,7 @@ final class SafetyCenterIssueDeduplicator { * <p>In case any issue, in the bucket of duplicate issues, was dismissed, all issues of the * same or lower severity will be dismissed as well. * - * @return deduplicated list of issues, and some other information gathere in the deduplication + * @return deduplicated list of issues, and some other information gathered in the deduplication * process */ @RequiresApi(UPSIDE_DOWN_CAKE) @@ -123,7 +123,7 @@ final class SafetyCenterIssueDeduplicator { for (int i = 0; i < dedupBuckets.size(); i++) { List<SafetySourceIssueInfo> duplicates = dedupBuckets.valueAt(i); if (duplicates.isEmpty()) { - Log.w(TAG, "List of duplicates in a dedupBucket is empty"); + Log.w(TAG, "List of duplicates in a deduplication bucket is empty"); continue; } @@ -164,7 +164,7 @@ final class SafetyCenterIssueDeduplicator { } /** - * Handles dismissals logic: in each bucket, dismissal details of the top (highest priority) + * Handles dismissals logic: in each bucket, dismissal details of the highest priority (top) * dismissed issue will be copied to all other duplicate issues in that bucket, that are of * equal or lower severity (not priority). Notification-dismissal details are handled similarly. */ diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java index bc9bfb2d6..299da60a9 100644 --- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java +++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.content.ApexEnvironment; @@ -30,6 +29,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.BackgroundThread; @@ -130,11 +130,7 @@ final class SafetyCenterIssueDismissalRepository { Duration timeSinceLastDismissal = Duration.between(dismissedAt, Instant.now()); boolean isTimeToResurface = timeSinceLastDismissal.compareTo(delay) >= 0; - if (isTimeToResurface) { - return false; - } - - return true; + return !isTimeToResurface; } /** @@ -342,7 +338,7 @@ final class SafetyCenterIssueDismissalRepository { * and all following calls won't have any effect. */ void resurfaceHiddenIssueAfterPeriod(SafetyCenterIssueKey safetyCenterIssueKey) { - IssueData issueData = getOrWarn(safetyCenterIssueKey, "resurfaceIssueAfterPeriod"); + IssueData issueData = getOrWarn(safetyCenterIssueKey, "resurfacing hidden issue"); if (issueData == null) { return; } @@ -492,9 +488,9 @@ final class SafetyCenterIssueDismissalRepository { try { persistedSafetyCenterIssues = SafetyCenterIssuesPersistence.read(getIssueDismissalRepositoryFile()); - Log.i(TAG, "Safety Center persisted issues read successfully"); + Log.d(TAG, "Safety Center persisted issues read successfully"); } catch (PersistenceException e) { - Log.e(TAG, "Cannot read Safety Center persisted issues", e); + Log.w(TAG, "Cannot read Safety Center persisted issues", e); } load(persistedSafetyCenterIssues); diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java index b47f7925e..8e3364c33 100644 --- a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java +++ b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java @@ -24,7 +24,6 @@ import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLL import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED; import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR; -import android.annotation.Nullable; import android.annotation.UptimeMillisLong; import android.annotation.UserIdInt; import android.content.Context; @@ -38,6 +37,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.safetycenter.SafetySourceKey; diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java index cc265d7f9..acb4d95f0 100644 --- a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java +++ b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java @@ -18,7 +18,6 @@ package com.android.safetycenter.data; import static android.os.Build.VERSION_CODES.TIRAMISU; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.PackageManager; @@ -29,6 +28,7 @@ import android.safetycenter.SafetySourceStatus; import android.safetycenter.config.SafetySource; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -52,7 +52,7 @@ import javax.annotation.concurrent.NotThreadSafe; @NotThreadSafe final class SafetySourceDataValidator { - private static final String TAG = "SafetySourceDataValidator"; + private static final String TAG = "SafetySourceDataValidat"; private final Context mContext; private final SafetyCenterConfigReader mSafetyCenterConfigReader; @@ -192,13 +192,13 @@ final class SafetySourceDataValidator { && !checkCerts( packageName, SafetyCenterFlags.getAdditionalAllowedPackageCerts(packageName))) { - Log.e( + Log.w( TAG, - "Package " + "Package: " + packageName - + " for source " + + ", for source: " + safetySourceId - + " signed with invalid signature"); + + " is signed with invalid signature"); throw new IllegalArgumentException("Invalid signature for package " + packageName); } } @@ -210,7 +210,7 @@ final class SafetySourceDataValidator { byte[] certificate = new Signature(certHash).toByteArray(); if (mPackageManager.hasSigningCertificate( packageName, certificate, PackageManager.CERT_INPUT_SHA256)) { - Log.d(TAG, "Package " + packageName + " has expected signature"); + Log.v(TAG, "Package: " + packageName + " has expected signature"); hasMatchingCert = true; } } catch (IllegalArgumentException e) { diff --git a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java index b6bf280ae..2e1235c96 100644 --- a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java +++ b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java @@ -19,7 +19,6 @@ package com.android.safetycenter.data; import static android.os.Build.VERSION_CODES.TIRAMISU; import android.annotation.ElapsedRealtimeLong; -import android.annotation.Nullable; import android.content.Context; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyEvent; @@ -27,6 +26,7 @@ import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.UserUtils; @@ -132,10 +132,11 @@ final class SafetySourceStateCollectedLogger { } } + Integer severityLevel = maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null; SafetyCenterStatsdLogger.writeSafetySourceStateCollected( sourceKey.getSourceId(), isManagedProfile, - maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null, + severityLevel, openIssuesCount, dismissedIssuesCount, getDuplicateCount(sourceKey), diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java index d9514f56e..af66cae5c 100644 --- a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java +++ b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java @@ -93,17 +93,17 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback return StatsManager.PULL_SKIP; } if (!SafetyCenterFlags.getSafetyCenterEnabled()) { - Log.w(TAG, "Attempt to pull SAFETY_STATE, but Safety Center is disabled"); + Log.i(TAG, "Attempt to pull SAFETY_STATE, but Safety Center is disabled"); return StatsManager.PULL_SKIP; } List<UserProfileGroup> userProfileGroups = UserProfileGroup.getAllUserProfileGroups(mContext); synchronized (mApiLock) { if (!SafetyCenterFlags.getAllowStatsdLogging()) { - Log.w(TAG, "Skipping pulling and writing atoms due to logging being disabled"); + Log.i(TAG, "Skipping pulling and writing atoms due to logging being disabled"); return StatsManager.PULL_SKIP; } - Log.i(TAG, "Pulling and writing atoms…"); + Log.d(TAG, "Pulling and writing atoms…"); for (int i = 0; i < userProfileGroups.size(); i++) { UserProfileGroup userProfileGroup = userProfileGroups.get(i); List<SafetySourcesGroup> loggableGroups = @@ -111,8 +111,8 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback statsEvents.add( createOverallSafetyStateAtomLocked(userProfileGroup, loggableGroups)); // The SAFETY_SOURCE_STATE_COLLECTED atoms are written instead of being pulled, - // they do not support pull but we want to collect them at the same time as - // the above pulled atom. + // as they do not support pull. We still want to collect them at the same time as + // the above pulled atom, which is why they're written here. writeSafetySourceStateCollectedAtomsLocked(userProfileGroup, loggableGroups); } } @@ -165,7 +165,8 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback int[] managedIds = userProfileGroup.getManagedRunningProfilesUserIds(); for (int k = 0; k < managedIds.length; k++) { - writeSafetySourceStateCollectedAtomLocked(loggableSource, managedIds[k], true); + writeSafetySourceStateCollectedAtomLocked( + loggableSource, managedIds[k], /* isUserManaged= */ true); } } } diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java index 8ca662d27..0d026e767 100644 --- a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java +++ b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java @@ -74,7 +74,6 @@ import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SE import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; -import android.annotation.Nullable; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyCenterManager.RefreshRequestType; import android.safetycenter.SafetyCenterStatus; @@ -83,6 +82,7 @@ import android.safetycenter.SafetySourceData; import android.util.Log; import android.util.StatsEvent; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.PermissionStatsLog; diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java index e2df717a7..4de16feb6 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java @@ -20,7 +20,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.Objects.requireNonNull; -import android.annotation.Nullable; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -31,6 +30,7 @@ import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.permission.util.UserUtils; @@ -70,18 +70,25 @@ public final class SafetyCenterNotificationChannels { ? contextAsUser.getSystemService(NotificationManager.class) : null; if (notificationManager == null) { - Log.w(TAG, "Could not retrieve NotificationManager for user " + userHandle); + Log.w( + TAG, + "Could not retrieve NotificationManager for user id: " + + userHandle.getIdentifier()); } return notificationManager; } @Nullable - private static Context getContextAsUser(Context baseContext, UserHandle userHandle) { + static Context getContextAsUser(Context baseContext, UserHandle userHandle) { + // This call requires the INTERACT_ACROSS_USERS permission. + final long callingId = Binder.clearCallingIdentity(); try { - return baseContext.createContextAsUser(userHandle, 0); + return baseContext.createContextAsUser(userHandle, /* flags= */ 0); } catch (RuntimeException e) { - Log.w(TAG, "Could not create Context as user " + userHandle, e); + Log.w(TAG, "Could not create Context as user id: " + userHandle.getIdentifier(), e); return null; + } finally { + Binder.restoreCallingIdentity(callingId); } } @@ -123,23 +130,35 @@ public final class SafetyCenterNotificationChannels { requireNonNull(getNotificationManagerForUser(context, user)); createAllChannelsWithoutCallingIdentity(notificationManager); } catch (RuntimeException e) { - Log.w(TAG, "Error creating notification channels for user " + user.getIdentifier(), e); + Log.w( + TAG, + "Error creating notification channels for user id: " + user.getIdentifier(), + e); } } @Nullable private String getChannelIdForIssue(SafetySourceIssue issue) { - switch (issue.getSeverityLevel()) { + int issueSeverityLevel = issue.getSeverityLevel(); + switch (issueSeverityLevel) { case SafetySourceData.SEVERITY_LEVEL_INFORMATION: return CHANNEL_ID_INFORMATION; case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION: return CHANNEL_ID_RECOMMENDATION; case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING: return CHANNEL_ID_CRITICAL_WARNING; - default: - Log.w(TAG, "No applicable notification channel for issue " + issue); + case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED: + Log.w(TAG, "SafetySourceData.SeverityLevel is unspecified for issue: " + issue); return null; } + + Log.w( + TAG, + "Unexpected SafetySourceData.SeverityLevel: " + + issueSeverityLevel + + ", for issue: " + + issue); + return null; } /** diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java index 62da1fc7d..cc3d0c4f3 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java @@ -21,15 +21,16 @@ import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID; import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID; import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_USER_HANDLE; +import static com.android.safetycenter.notifications.SafetyCenterNotificationChannels.getContextAsUser; + import android.annotation.ColorInt; -import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.UserHandle; @@ -37,6 +38,7 @@ import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; import android.text.TextUtils; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -56,7 +58,6 @@ import java.util.List; @RequiresApi(TIRAMISU) final class SafetyCenterNotificationFactory { - private static final String TAG = "SafetyCenterNF"; private static final int OPEN_SAFETY_CENTER_REQUEST_CODE = 1221; private static final Duration SUCCESS_NOTIFICATION_TIMEOUT = Duration.ofSeconds(10); @@ -84,13 +85,18 @@ final class SafetyCenterNotificationFactory { Notification newNotificationForSuccessfulAction( NotificationManager notificationManager, SafetySourceIssue issue, - SafetySourceIssue.Action action) { + SafetySourceIssue.Action action, + @UserIdInt int userId) { String channelId = mNotificationChannels.getCreatedChannelId(notificationManager, issue); - if (channelId == null) { return null; } + PendingIntent contentIntent = newSafetyCenterPendingIntent(userId); + if (contentIntent == null) { + return null; + } + Notification.Builder builder = new Notification.Builder(mContext, channelId) .setSmallIcon( @@ -99,7 +105,7 @@ final class SafetyCenterNotificationFactory { .setContentTitle(action.getSuccessMessage()) .setShowWhen(true) .setTimeoutAfter(SUCCESS_NOTIFICATION_TIMEOUT.toMillis()) - .setContentIntent(newSafetyCenterPendingIntent(null)); + .setContentIntent(contentIntent); Integer color = getNotificationColor(SafetySourceData.SEVERITY_LEVEL_INFORMATION); if (color != null) { @@ -122,7 +128,6 @@ final class SafetyCenterNotificationFactory { SafetySourceIssue issue, SafetyCenterIssueKey issueKey) { String channelId = mNotificationChannels.getCreatedChannelId(notificationManager, issue); - if (channelId == null) { return null; } @@ -140,6 +145,11 @@ final class SafetyCenterNotificationFactory { } } + PendingIntent contentIntent = newSafetyCenterPendingIntent(issueKey); + if (contentIntent == null) { + return null; + } + Notification.Builder builder = new Notification.Builder(mContext, channelId) .setSmallIcon(getNotificationIcon(issue.getSeverityLevel())) @@ -147,7 +157,7 @@ final class SafetyCenterNotificationFactory { .setShowWhen(true) .setContentTitle(title) .setContentText(text) - .setContentIntent(newSafetyCenterPendingIntent(issueKey)) + .setContentIntent(contentIntent) .setDeleteIntent( SafetyCenterNotificationReceiver.newNotificationDismissedIntent( mContext, issueKey)); @@ -167,23 +177,51 @@ final class SafetyCenterNotificationFactory { } /** - * Returns a {@link PendingIntent} to open Safety Center, optionally navigating to and/or - * highlighting a specific issue if {@code issueKey} is given. + * Returns a {@link PendingIntent} to open Safety Center, navigating to a specific issue, or + * {@code null} if no such intent can be created. */ - private PendingIntent newSafetyCenterPendingIntent(@Nullable SafetyCenterIssueKey issueKey) { - Intent intent = new Intent(Intent.ACTION_SAFETY_CENTER); - if (issueKey != null) { - // Set the encoded issue key as the intent's identifier to ensure the PendingIntents of - // different notifications do not collide: - intent.setIdentifier(SafetyCenterIds.encodeToString(issueKey)); - intent.putExtra(EXTRA_SAFETY_SOURCE_ID, issueKey.getSafetySourceId()); - intent.putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, issueKey.getSafetySourceIssueId()); - intent.putExtra(EXTRA_SAFETY_SOURCE_USER_HANDLE, UserHandle.of(issueKey.getUserId())); + @Nullable + private PendingIntent newSafetyCenterPendingIntent(SafetyCenterIssueKey issueKey) { + UserHandle userHandle = UserHandle.of(issueKey.getUserId()); + Context userContext = getContextAsUser(mContext, userHandle); + if (userContext == null) { + return null; } + + Intent intent = newSafetyCenterIntent(); + // Set the encoded issue key as the intent's identifier to ensure the PendingIntents of + // different notifications do not collide: + intent.setIdentifier(SafetyCenterIds.encodeToString(issueKey)); + intent.putExtra(EXTRA_SAFETY_SOURCE_ID, issueKey.getSafetySourceId()); + intent.putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, issueKey.getSafetySourceIssueId()); + intent.putExtra(EXTRA_SAFETY_SOURCE_USER_HANDLE, userHandle); + + return PendingIntentFactory.getActivityPendingIntent( + userContext, OPEN_SAFETY_CENTER_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE); + } + + /** + * Returns a {@link PendingIntent} to open Safety Center, or {@code null} if no such intent can + * be created. + */ + @Nullable + private PendingIntent newSafetyCenterPendingIntent(@UserIdInt int userId) { + Context userContext = getContextAsUser(mContext, UserHandle.of(userId)); + if (userContext == null) { + return null; + } + return PendingIntentFactory.getActivityPendingIntent( + userContext, + OPEN_SAFETY_CENTER_REQUEST_CODE, + newSafetyCenterIntent(), + PendingIntent.FLAG_IMMUTABLE); + } + + private static Intent newSafetyCenterIntent() { + Intent intent = new Intent(Intent.ACTION_SAFETY_CENTER); // This extra is defined in the PermissionController APK, cannot be referenced directly: intent.putExtra("navigation_source_intent_extra", "NOTIFICATION"); - return PendingIntentFactory.getActivityPendingIntent( - mContext, OPEN_SAFETY_CENTER_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE); + return intent; } private Icon getNotificationIcon(@SafetySourceData.SeverityLevel int severityLevel) { @@ -222,7 +260,9 @@ final class SafetyCenterNotificationFactory { private Notification.Action toNotificationAction( SafetyCenterIssueKey issueKey, SafetySourceIssue.Action issueAction) { PendingIntent pendingIntent = getPendingIntentForAction(issueKey, issueAction); - return new Notification.Action.Builder(null, issueAction.getLabel(), pendingIntent).build(); + return new Notification.Action.Builder( + /* icon= */ null, issueAction.getLabel(), pendingIntent) + .build(); } private PendingIntent getPendingIntentForAction( @@ -254,9 +294,4 @@ final class SafetyCenterNotificationFactory { SafetyCenterIssueKey issueKey, SafetySourceIssue.Action issueAction) { return issueAction.getPendingIntent(); } - - private static boolean isDarkTheme(Context context) { - return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - } } diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java index 0b4ac5ab3..41c54a9fd 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java @@ -18,7 +18,6 @@ package com.android.safetycenter.notifications; import static android.os.Build.VERSION_CODES.TIRAMISU; -import android.annotation.Nullable; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -27,6 +26,7 @@ import android.content.IntentFilter; import android.safetycenter.SafetySourceIssue; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; @@ -120,13 +120,13 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver { private static SafetyCenterIssueActionId getIssueActionIdExtra(Intent intent) { String issueActionIdString = intent.getStringExtra(EXTRA_ISSUE_ACTION_ID); if (issueActionIdString == null) { - Log.w(TAG, "Received notification action broadcast with null issue action ID"); + Log.w(TAG, "Received notification action broadcast with null issue action id"); return null; } try { return SafetyCenterIds.issueActionIdFromString(issueActionIdString); } catch (IllegalArgumentException e) { - Log.w(TAG, "Could not decode the issue action ID", e); + Log.w(TAG, "Could not decode the issue action id", e); return null; } } @@ -162,22 +162,27 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_NOTIFICATION_DISMISSED); filter.addAction(ACTION_NOTIFICATION_ACTION_CLICKED); - context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED); + context.registerReceiver(/* receiver= */ this, filter, Context.RECEIVER_NOT_EXPORTED); } @Override public void onReceive(Context context, Intent intent) { - if (!SafetyCenterFlags.getSafetyCenterEnabled() - || !SafetyCenterFlags.getNotificationsEnabled()) { + if (!SafetyCenterFlags.getSafetyCenterEnabled()) { + Log.i(TAG, "Received notification broadcast but Safety Center is disabled"); + return; + } + + if (!SafetyCenterFlags.getNotificationsEnabled()) { + Log.i(TAG, "Received notification broadcast but notifications are disabled"); return; } - Log.d(TAG, "Received broadcast with action " + intent.getAction()); String action = intent.getAction(); if (action == null) { - Log.w(TAG, "Received broadcast with null action!"); + Log.w(TAG, "Received broadcast with null action"); return; } + Log.d(TAG, "Received broadcast with action: " + action); switch (action) { case ACTION_NOTIFICATION_DISMISSED: diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java index 668158097..77dd80c47 100644 --- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java +++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java @@ -22,7 +22,6 @@ import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTIO import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString; import android.annotation.IntDef; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; import android.app.NotificationManager; @@ -36,6 +35,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; @@ -137,7 +137,7 @@ public final class SafetyCenterNotificationSender { * <p>The given {@link SafetyEvent} have type {@link * SafetyEvent#SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} and include issue and action IDs * that correspond to a {@link SafetySourceIssue} for which a notification is currently - * displayed. Otherwise this method has no effect. + * displayed. Otherwise, this method has no effect. * * @param sourceId of the source which reported the issue * @param safetyEvent the source provided upon successful action resolution @@ -193,7 +193,7 @@ public final class SafetyCenterNotificationSender { Notification notification = mNotificationFactory.newNotificationForSuccessfulAction( - notificationManager, notifiedIssue, successfulAction); + notificationManager, notifiedIssue, successfulAction, userId); if (notification == null) { Log.w(TAG, "Could not create successful action notification"); return; @@ -286,7 +286,7 @@ public final class SafetyCenterNotificationSender { fout.println(); } - /** Get all of the key-issue pairs for which notifications should be posted or updated now. */ + /** Gets all the key-issue pairs for which notifications should be posted or updated now. */ private ArrayMap<SafetyCenterIssueKey, SafetySourceIssue> getIssuesToNotify( @UserIdInt int userId) { ArrayMap<SafetyCenterIssueKey, SafetySourceIssue> result = new ArrayMap<>(); @@ -325,14 +325,20 @@ public final class SafetyCenterNotificationSender { @NotificationBehaviorInternal private int getBehavior(SafetySourceIssue issue, SafetyCenterIssueKey issueKey) { if (SdkLevel.isAtLeastU()) { - switch (issue.getNotificationBehavior()) { + int notificationBehavior = issue.getNotificationBehavior(); + switch (notificationBehavior) { case SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER: return NOTIFICATION_BEHAVIOR_INTERNAL_NEVER; case SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED: return NOTIFICATION_BEHAVIOR_INTERNAL_DELAYED; case SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY: return NOTIFICATION_BEHAVIOR_INTERNAL_IMMEDIATELY; + case SafetySourceIssue.NOTIFICATION_BEHAVIOR_UNSPECIFIED: + return getBehaviorForIssueWithUnspecifiedBehavior(issue, issueKey); } + Log.w( + TAG, + "Unexpected SafetySourceIssue.NotificationBehavior: " + notificationBehavior); } // On Android T all issues are assumed to have "unspecified" behavior return getBehaviorForIssueWithUnspecifiedBehavior(issue, issueKey); diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt index b6bb475d8..3288f71ff 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt @@ -59,7 +59,6 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.set import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission import com.android.safetycenter.testing.SafetyCenterEnabledChangedReceiver import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_ALL_OPTIONAL_ID import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_BAREBONE_ID @@ -77,6 +76,7 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.STATIC import com.android.safetycenter.testing.SafetyCenterTestData import com.android.safetycenter.testing.SafetyCenterTestHelper import com.android.safetycenter.testing.SafetyCenterTestListener +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response import com.android.safetycenter.testing.SafetySourceReceiver @@ -89,15 +89,15 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.google.common.base.Preconditions.checkState import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import kotlin.test.assertFailsWith import kotlinx.coroutines.TimeoutCancellationException -import org.junit.After import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -111,30 +111,8 @@ class SafetyCenterManagerTest { private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } - - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) @Test fun isSafetyCenterEnabled_withFlagEnabled_returnsTrue() { diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt index b0860d751..4057410fa 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt @@ -22,7 +22,7 @@ import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compatibility.common.util.SystemUtil import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.isSafetyCenterEnabledWithPermission -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter +import com.android.safetycenter.testing.deviceSupportsSafetyCenter import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt index 695265059..a4d1ef436 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt @@ -42,12 +42,12 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.rep import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetyCenterConfigForTestsWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission import com.android.safetycenter.testing.SafetyCenterEnabledChangedReceiver -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID import com.android.safetycenter.testing.SafetyCenterTestData import com.android.safetycenter.testing.SafetyCenterTestHelper import com.android.safetycenter.testing.SafetyCenterTestListener +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceReceiver.Companion.executeSafetyCenterIssueActionWithPermissionAndWait import com.android.safetycenter.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithReceiverPermissionAndWait @@ -56,14 +56,13 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.waitDisplayed import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import kotlin.test.assertFailsWith import kotlinx.coroutines.TimeoutCancellationException -import org.junit.After import org.junit.Assume.assumeTrue -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -72,39 +71,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SafetyCenterUnsupportedTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - private val context: Context = getApplicationContext() private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetySourceTestData = SafetySourceTestData(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = !context.deviceSupportsSafetyCenter() - @Before - fun assumeDeviceDoesntSupportSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } - - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } + @get:Rule(order = 1) + val supportsSafetyCenterRule = SupportsSafetyCenterRule(context, requireSupportIs = false) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() @Test fun launchActivity_opensSettings() { diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt index 32959629c..81d1af6a7 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt @@ -26,13 +26,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.safetycenter.config.SafetyCenterConfigParser import com.android.safetycenter.resources.SafetyCenterResourcesContext import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter -import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import org.junit.After -import org.junit.Assume.assumeTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -41,32 +39,10 @@ import org.junit.runner.RunWith class XmlConfigTest { private val context: Context = getApplicationContext() private val safetyCenterContext = SafetyCenterResourcesContext.forTests(context) - private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } - - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(context) @Test fun safetyCenterConfigResource_validConfig() { diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt index 8fb56b09e..111f01243 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt @@ -24,15 +24,14 @@ import com.android.compatibility.common.util.DisableAnimationRule import com.android.compatibility.common.util.FreezeRotationRule import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.resetRotation import com.android.safetycenter.testing.UiTestHelper.waitDisplayed import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed import org.junit.After -import org.junit.Assume.assumeTrue -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -41,36 +40,16 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SafetyCenterActivityTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - private val context: Context = getApplicationContext() - private val safetyCenterTestHelper = SafetyCenterTestHelper(context) - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() @After fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() getUiDevice().resetRotation() } diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt index 07ae129ec..c534a9fb0 100644 --- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt +++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt @@ -33,6 +33,8 @@ import android.safetycenter.SafetyCenterEntryOrGroup import android.safetycenter.SafetyCenterManager import android.safetycenter.SafetyCenterStaticEntry import android.safetycenter.SafetyCenterStaticEntryGroup +import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS +import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_NONE import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceData import androidx.test.core.app.ApplicationProvider @@ -44,16 +46,19 @@ import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile import com.android.bedstead.harrier.annotations.Postsubmit import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner +import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner import com.android.bedstead.nene.TestApis import com.android.bedstead.nene.types.OptionalBoolean.TRUE import com.android.compatibility.common.util.DisableAnimationRule import com.android.compatibility.common.util.FreezeRotationRule import com.android.safetycenter.resources.SafetyCenterResourcesContext +import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT +import com.android.safetycenter.testing.NotificationCharacteristics import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterDataWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetySourceDataWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter +import com.android.safetycenter.testing.SafetyCenterFlags import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_BAREBONE_ID @@ -78,24 +83,25 @@ import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withoutEx import com.android.safetycenter.testing.SafetyCenterTestHelper import com.android.safetycenter.testing.SafetySourceTestData import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED +import com.android.safetycenter.testing.SafetySourceTestData.Companion.ISSUE_TYPE_ID import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity +import com.android.safetycenter.testing.SupportsSafetyCenterRule +import com.android.safetycenter.testing.TestNotificationListener import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed import com.android.safetycenter.testing.UiTestHelper.waitAllTextNotDisplayed import com.google.common.base.Preconditions.checkState import com.google.common.truth.Truth.assertThat import kotlin.test.assertFailsWith import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.ClassRule -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith /** * Functional tests for our APIs and UI on a device with multiple users. e.g. with a managed or - * secondary user(s). + * additional user(s). */ @RunWith(BedsteadJUnit4::class) class SafetyCenterMultiUsersTest { @@ -104,10 +110,6 @@ class SafetyCenterMultiUsersTest { @JvmField @ClassRule @Rule val deviceState: DeviceState = DeviceState() } - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - private val context: Context = ApplicationProvider.getApplicationContext() private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context) private val safetyCenterTestHelper = SafetyCenterTestHelper(context) @@ -116,9 +118,6 @@ class SafetyCenterMultiUsersTest { private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() private var inQuietMode = false private val primaryProfileOnlyIssues = @@ -181,12 +180,22 @@ class SafetyCenterMultiUsersTest { private val dynamicDisabledForWorkDefault get() = dynamicDisabledForWorkDefaultBuilder.build() - private val dynamicDisabledForWorkPaused + private val dynamicDisabledForWorkPausedUpdated get() = - dynamicDisabledForWorkDefaultBuilder + safetyCenterTestData + .safetyCenterEntryDefaultBuilder( + DYNAMIC_DISABLED_ID, + deviceState.workProfile().id(), + title = "Ok title for Work", + pendingIntent = null + ) // TODO(b/233188021): This needs to use the Enterprise API to override the "work" - // keyword. - .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused")) + // keyword. + .setSummary( + safetyCenterResourcesContext.getStringByName("work_profile_paused"), + ) + .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED) + .setEnabled(false) .build() private val dynamicDisabledForWorkUpdated @@ -198,6 +207,24 @@ class SafetyCenterMultiUsersTest { private val dynamicHiddenForWorkUpdated get() = safetyCenterEntryOkForWork(DYNAMIC_HIDDEN_ID, deviceState.workProfile().id()) + private val dynamicHiddenForWorkPausedUpdated + get() = + safetyCenterTestData + .safetyCenterEntryDefaultBuilder( + DYNAMIC_HIDDEN_ID, + deviceState.workProfile().id(), + title = "Ok title for Work", + pendingIntent = null + ) + // TODO(b/233188021): This needs to use the Enterprise API to override the "work" + // keyword. + .setSummary( + safetyCenterResourcesContext.getStringByName("work_profile_paused"), + ) + .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED) + .setEnabled(false) + .build() + private val staticGroupBuilder = SafetyCenterEntryGroup.Builder(STATIC_GROUP_ID, "OK") .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED) @@ -213,26 +240,26 @@ class SafetyCenterMultiUsersTest { private val staticAllOptional = safetyCenterTestData.safetyCenterEntryDefaultStaticBuilder(STATIC_ALL_OPTIONAL_ID).build() - private val staticAllOptionalForWorkBuilder - get() = - safetyCenterTestData - .safetyCenterEntryDefaultStaticBuilder( - STATIC_ALL_OPTIONAL_ID, - userId = deviceState.workProfile().id(), - title = "Paste" - ) - .setPendingIntent( - createTestActivityRedirectPendingIntentForUser( - deviceState.workProfile().userHandle() - ) + private fun staticAllOptionalForWorkBuilder(inQuietMode: Boolean = false) = + safetyCenterTestData + .safetyCenterEntryDefaultStaticBuilder( + STATIC_ALL_OPTIONAL_ID, + userId = deviceState.workProfile().id(), + title = "Paste" + ) + .setPendingIntent( + createTestActivityRedirectPendingIntentForUser( + deviceState.workProfile().userHandle(), + inQuietMode ) + ) private val staticAllOptionalForWork - get() = staticAllOptionalForWorkBuilder.build() + get() = staticAllOptionalForWorkBuilder().build() private val staticAllOptionalForWorkPaused get() = - staticAllOptionalForWorkBuilder + staticAllOptionalForWorkBuilder(inQuietMode = true) // TODO(b/233188021): This needs to use the Enterprise API to override the "work" // keyword. .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused")) @@ -251,22 +278,33 @@ class SafetyCenterMultiUsersTest { .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent) .build() - private val staticEntryForWorkBuilder - get() = - SafetyCenterStaticEntry.Builder("Paste") - .setSummary("OK") - .setPendingIntent( - createTestActivityRedirectPendingIntentForUser( - deviceState.workProfile().userHandle() - ) + private fun staticEntryForWorkBuilder( + title: CharSequence = "Paste", + inQuietMode: Boolean = false + ) = + SafetyCenterStaticEntry.Builder(title) + .setSummary("OK") + .setPendingIntent( + createTestActivityRedirectPendingIntentForUser( + deviceState.workProfile().userHandle(), + inQuietMode ) + ) private val staticEntryForWork - get() = staticEntryForWorkBuilder.build() + get() = staticEntryForWorkBuilder().build() private val staticEntryForWorkPaused get() = - staticEntryForWorkBuilder + staticEntryForWorkBuilder(inQuietMode = true) + // TODO(b/233188021): This needs to use the Enterprise API to override the "work" + // keyword. + .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused")) + .build() + + private val staticEntryForWorkPausedUpdated + get() = + staticEntryForWorkBuilder(title = "Unspecified title for Work", inQuietMode = true) // TODO(b/233188021): This needs to use the Enterprise API to override the "work" // keyword. .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused")) @@ -298,67 +336,73 @@ class SafetyCenterMultiUsersTest { emptyList() ) - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } + fun setTimeoutsBeforeTest() { + // TODO(b/283745908): Make TestNotificationListener compatible with SafetyCenterTestRule safetyCenterTestHelper.setup() + TestNotificationListener.setup(context) + SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) } @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } + fun resetQuietModeAfterTest() { + setQuietMode(false) + // It is important to reset the notification listener last because it waits/ensures that + // all notifications have been removed before returning. + // TODO(b/283745908): Make TestNotificationListener compatible with SafetyCenterTestRule safetyCenterTestHelper.reset() - resetQuietMode() + TestNotificationListener.reset(context) } @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile - @Ignore - // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits. - // TODO(b/242999951): Write this test using the APIs instead of checking the UI. - fun launchActivity_withProfileOwner_displaysWorkPolicyInfo() { + // TODO(b/242999951): Write these tests using the getSafetyCenterData() method instead. + fun getSafetyCenterData_withProfileOwner_hasWorkPolicyInfo() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig) findWorkPolicyInfo() } @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasDeviceOwner - @Ignore - // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits. - // TODO(b/242999951): Write this test using the APIs instead of checking the UI. - fun launchActivity_withDeviceOwner_displaysWorkPolicyInfo() { + // TODO(b/242999951): Write these tests using the getSafetyCenterData() method instead. + fun getSafetyCenterData_withDeviceOwner_hasWorkPolicyInfo() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig) findWorkPolicyInfo() } @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile - @Ignore - // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits. - // TODO(b/242999951): Write this test using the APIs instead of checking the UI. - fun launchActivity_withQuietModeEnabled_shouldNotDisplayWorkPolicyInfo() { + // TODO(b/242999951): Write these tests using the getSafetyCenterData() method instead. + fun launchActivity_withQuietModeEnabled_hasWorkPolicyInfo() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig) - findWorkPolicyInfo() setQuietMode(true) + + findWorkPolicyInfo() + } + + @Test + @Postsubmit(reason = "Test takes too much time to setup") + @EnsureHasNoWorkProfile + @EnsureHasNoDeviceOwner + // TODO(b/242999951): Write these tests using the getSafetyCenterData() method instead. + fun launchActivity_withoutWorkProfileOrDeviceOwner_doesntHaveWorkPolicyInfo() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig) + context.launchSafetyCenterActivity { waitAllTextNotDisplayed("Your work policy info") } } @Test - @Ignore - // Test involving toggling of quiet mode are flaky. - // TODO(b/237365018): Re-enable them back once we figure out a way to make them stable. + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile(installInstrumentedApp = TRUE) fun getSafetySourceData_withQuietModeEnabled_dataIsNotCleared() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) @@ -380,8 +424,8 @@ class SafetyCenterMultiUsersTest { } @Test - @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) @Postsubmit(reason = "Test takes too much time to setup") + @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) fun getSafetySourceData_afterAdditionalUserRemoved_returnsNull() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) val additionalUserSafetyCenterManager = @@ -468,8 +512,6 @@ class SafetyCenterMultiUsersTest { @Test @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile(installInstrumentedApp = TRUE) - @Ignore - // Test involving toggling of quiet mode are flaky. fun getSafetySourceData_withProfileInQuietMode_shouldReturnData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) val managedSafetyCenterManager = @@ -743,16 +785,14 @@ class SafetyCenterMultiUsersTest { } @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile(installInstrumentedApp = TRUE) - @Ignore - // Test involving toggling of quiet mode are flaky. - // TODO(b/237365018): Re-enable them back once we figure out a way to make them stable. fun getSafetyCenterData_withQuietMode_shouldHaveWorkProfilePausedSummaryAndNoWorkIssues() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig) updatePrimaryProfileSources() updateWorkProfileSources() - setQuietMode(true) + val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission() val safetyCenterDataFromComplexConfig = @@ -768,8 +808,9 @@ class SafetyCenterMultiUsersTest { listOf( dynamicBareboneUpdated, dynamicDisabledUpdated, - dynamicDisabledForWorkPaused, - dynamicHiddenUpdated + dynamicDisabledForWorkPausedUpdated, + dynamicHiddenUpdated, + dynamicHiddenForWorkPausedUpdated, ) ) .setSeverityUnspecifiedIconType( @@ -794,7 +835,7 @@ class SafetyCenterMultiUsersTest { "OK", listOf( staticEntryUpdated, - staticEntryForWorkPaused, + staticEntryForWorkPausedUpdated, staticEntry, staticEntryForWorkPaused ) @@ -805,9 +846,9 @@ class SafetyCenterMultiUsersTest { } @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) @EnsureHasWorkProfile(installInstrumentedApp = TRUE) - @Postsubmit(reason = "Test takes too much time to setup") fun getSafetyCenterData_withDataForDifferentUserProfileGroup_shouldBeUnaffected() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) val dataForPrimaryUser = safetySourceTestData.information @@ -830,7 +871,7 @@ class SafetyCenterMultiUsersTest { } @Test - @Ignore // Removing a managed profile causes a refresh, which makes some tests flaky. + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile(installInstrumentedApp = TRUE) fun getSafetyCenterData_afterManagedProfileRemoved_returnsDefaultData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) @@ -904,8 +945,8 @@ class SafetyCenterMultiUsersTest { } @Test - @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) @Postsubmit(reason = "Test takes too much time to setup") + @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) fun getSafetyCenterData_afterAdditionalUserRemoved_returnsDefaultData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) val additionalUserSafetyCenterManager = @@ -1029,8 +1070,6 @@ class SafetyCenterMultiUsersTest { @Test @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasWorkProfile(installInstrumentedApp = TRUE) - @Ignore - // Test involving toggling of quiet mode are flaky. fun setSafetySourceData_withProfileInQuietMode_shouldSetData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) val dataForWork = safetySourceTestData.informationWithIssueForWork @@ -1123,6 +1162,33 @@ class SafetyCenterMultiUsersTest { @Test @Postsubmit(reason = "Test takes too much time to setup") + @EnsureHasWorkProfile(installInstrumentedApp = TRUE) + fun setSafetySourceData_notificationsAllowed_workProfile_sendsNotification() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig) + SafetyCenterFlags.notificationsEnabled = true + SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ALL_PROFILE_ID) + SafetyCenterFlags.immediateNotificationBehaviorIssues = + setOf("$SINGLE_SOURCE_ALL_PROFILE_ID/$ISSUE_TYPE_ID") + val dataForWork = safetySourceTestData.informationWithIssueForWork + val managedSafetyCenterManager = + getSafetyCenterManagerForUser(deviceState.workProfile().userHandle()) + + managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission( + SINGLE_SOURCE_ALL_PROFILE_ID, + dataForWork + ) + + TestNotificationListener.waitForNotificationsMatching( + NotificationCharacteristics( + title = "Information issue title", + text = "Information issue summary", + actions = listOf("Review") + ) + ) + } + + @Test + @Postsubmit(reason = "Test takes too much time to setup") @EnsureHasAdditionalUser(installInstrumentedApp = TRUE) fun setSafetySourceData_forStoppedUser_shouldSetData() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) @@ -1207,11 +1273,15 @@ class SafetyCenterMultiUsersTest { } } - private fun createTestActivityRedirectPendingIntentForUser(user: UserHandle): PendingIntent { + private fun createTestActivityRedirectPendingIntentForUser( + user: UserHandle, + inQuietMode: Boolean = false + ): PendingIntent { return callWithShellPermissionIdentity(INTERACT_ACROSS_USERS) { SafetySourceTestData.createRedirectPendingIntent( getContextForUser(user), - Intent(ACTION_TEST_ACTIVITY) + Intent(ACTION_TEST_ACTIVITY), + inQuietMode ) } } @@ -1238,16 +1308,26 @@ class SafetyCenterMultiUsersTest { getSafetyCenterDataWithPermission() } - private fun setQuietMode(value: Boolean) { - deviceState.workProfile().setQuietMode(value) - inQuietMode = value + private fun setQuietMode(enableQuietMode: Boolean) { + if (inQuietMode == enableQuietMode) { + return + } + if (enableQuietMode) { + deviceState.workProfile().setQuietMode(true) + } else { + // This is needed to ensure the refresh broadcast doesn't leak onto other tests. + disableQuietModeAndWaitForRefreshToComplete() + } + inQuietMode = enableQuietMode } - private fun resetQuietMode() { - if (!inQuietMode) { - return + private fun disableQuietModeAndWaitForRefreshToComplete() { + val listener = safetyCenterTestHelper.addListener() + deviceState.workProfile().setQuietMode(false) + listener.receiveSafetyCenterData { + it.status.refreshStatus == REFRESH_STATUS_DATA_FETCH_IN_PROGRESS } - setQuietMode(false) + listener.receiveSafetyCenterData { it.status.refreshStatus == REFRESH_STATUS_NONE } } private fun safetyCenterEntryOkForWork(sourceId: String, managedUserId: Int) = diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt index ce8a70fbe..262bf4bab 100644 --- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt +++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt @@ -38,7 +38,6 @@ import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ISSUE_ONLY_ALL_OPTIONAL_ID import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID @@ -48,12 +47,14 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_4 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5 import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceTestData import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal @@ -73,8 +74,6 @@ import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed import org.junit.After import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -83,40 +82,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SafetyCenterActivityTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - - @get:Rule val screenRecordRule = ScreenRecordRule() - private val context: Context = getApplicationContext() - private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetySourceTestData = SafetySourceTestData(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) - // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() + @get:Rule(order = 5) val screenRecordRule = ScreenRecordRule() @After fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() getUiDevice().resetRotation() } diff --git a/tests/functional/safetycenter/singleuser/AndroidManifest.xml b/tests/functional/safetycenter/singleuser/AndroidManifest.xml index 7ca38dede..18a3b167a 100644 --- a/tests/functional/safetycenter/singleuser/AndroidManifest.xml +++ b/tests/functional/safetycenter/singleuser/AndroidManifest.xml @@ -17,14 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.safetycenter.functional"> <application> - <service android:name=".testing.TestNotificationListener" - android:label="TestNotificationListener" - android:exported="false" - android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> - <intent-filter> - <action android:name="android.service.notification.NotificationListenerService" /> - </intent-filter> - </service> + <uses-library android:name="android.test.runner"/> </application> diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt index 75d2fd57d..967f4296e 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt @@ -80,7 +80,6 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.get import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY_EXPORTED import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID @@ -116,6 +115,7 @@ import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withAttri import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withDismissedIssuesIfAtLeastU import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withoutExtras import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response import com.android.safetycenter.testing.SafetySourceReceiver @@ -130,15 +130,15 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.INFORMATI import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.google.common.base.Preconditions.checkState import com.google.common.truth.Truth.assertThat import java.time.Duration import kotlin.test.assertFailsWith import kotlinx.coroutines.TimeoutCancellationException -import org.junit.After import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue -import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -705,30 +705,8 @@ class SafetyCenterManagerTest { ) ) - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } - - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) @Test fun refreshSafetySources_withShowEntriesOnTimeout_marksSafetySourceAsError() { diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt index 70d6468fa..eb51df697 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt @@ -28,19 +28,17 @@ import android.safetycenter.SafetyCenterStatus import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceErrorDetails import android.safetycenter.SafetySourceIssue -import android.safetycenter.functional.testing.NotificationCharacteristics -import android.safetycenter.functional.testing.TestNotificationListener import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress import com.android.safetycenter.pendingintents.PendingIntentSender import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT +import com.android.safetycenter.testing.NotificationCharacteristics import com.android.safetycenter.testing.SafetyCenterActivityLauncher.executeBlockAndExit import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.clearAllSafetySourceDataForTestsWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1 @@ -54,14 +52,16 @@ import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceTestData import com.android.safetycenter.testing.SafetySourceTestData.Companion.ISSUE_TYPE_ID import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity +import com.android.safetycenter.testing.SupportsSafetyCenterRule +import com.android.safetycenter.testing.TestNotificationListener import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed import com.google.common.truth.Truth.assertThat import java.time.Duration import kotlin.test.assertFailsWith import kotlinx.coroutines.TimeoutCancellationException import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -77,22 +77,13 @@ class SafetyCenterNotificationTest { "Could not get system service" } - // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } + @get:Rule val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) @Before fun setUp() { - if (!shouldRunTests) { - return - } + // TODO(b/283745908): Make TestNotificationListener compatible with SafetyCenterTestRule safetyCenterTestHelper.setup() - TestNotificationListener.setup() + TestNotificationListener.setup(context) SafetyCenterFlags.notificationsEnabled = true setFlagsForImmediateNotifications(SINGLE_SOURCE_ID) safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) @@ -100,13 +91,11 @@ class SafetyCenterNotificationTest { @After fun tearDown() { - if (!shouldRunTests) { - return - } // It is important to reset the notification listener last because it waits/ensures that // all notifications have been removed before returning. + // TODO(b/283745908): Make TestNotificationListener compatible with SafetyCenterTestRule safetyCenterTestHelper.reset() - TestNotificationListener.reset() + TestNotificationListener.reset(context) } @Test diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt index 0c90029e0..8942d5bc8 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt @@ -31,12 +31,13 @@ import com.android.compatibility.common.util.UiAutomatorUtils2 import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExit import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.PRIVACY_SOURCE_ID_1 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1 import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceTestData +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL import com.android.safetycenter.testing.UiTestHelper.clickMoreIssuesCard import com.android.safetycenter.testing.UiTestHelper.resetRotation @@ -47,7 +48,6 @@ import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -58,39 +58,23 @@ import org.junit.runner.RunWith @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") class PrivacySubpageTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - private val context: Context = getApplicationContext() private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetySourceTestData = SafetySourceTestData(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) - // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() @Before fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() SafetyCenterFlags.showSubpages = true } @After fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() UiAutomatorUtils2.getUiDevice().resetRotation() } diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt index 9c4d720d3..857c3d7c3 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt @@ -16,29 +16,24 @@ package android.safetycenter.functional.ui -import android.Manifest.permission.MANAGE_SENSOR_PRIVACY -import android.Manifest.permission.OBSERVE_SENSOR_PRIVACY import android.content.Context -import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.Sensors.CAMERA import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE -import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE import android.platform.test.rule.ScreenRecordRule import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.By import com.android.compatibility.common.util.DisableAnimationRule import com.android.compatibility.common.util.FreezeRotationRule +import com.android.safetycenter.testing.EnableSensorRule import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterQsActivity -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter +import com.android.safetycenter.testing.SafetyCenterTestRule +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestHelper -import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed import com.android.safetycenter.testing.UiTestHelper.waitDisplayed import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed -import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -48,66 +43,22 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SafetyCenterQsActivityTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - - @get:Rule val screenRecordRule = ScreenRecordRule() - private val context: Context = getApplicationContext() private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) - private val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!! - private var shouldRunTests = - context.deviceSupportsSafetyCenter() && - deviceSupportsSensorToggle(CAMERA) && - deviceSupportsSensorToggle(MICROPHONE) - private var oldCameraState: Boolean = false - private var oldMicrophoneState: Boolean = false - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val enableCameraRule = EnableSensorRule(context, CAMERA) + @get:Rule(order = 3) val enableMicrophoneRule = EnableSensorRule(context, MICROPHONE) + @get:Rule(order = 4) val safetyCenterTestRule = SafetyCenterTestRule(context) + @get:Rule(order = 5) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 6) val freezeRotationRule = FreezeRotationRule() @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() + fun setTestConfigBeforeTest() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) } - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } - - @Before - fun enablePrivacyControlsBeforeTest() { - if (!shouldRunTests) { - return - } - oldCameraState = isSensorEnabled(CAMERA) - setSensorState(CAMERA, true) - - oldMicrophoneState = isSensorEnabled(MICROPHONE) - setSensorState(MICROPHONE, true) - } - - @After - fun restorePrivacyControlsAfterTest() { - if (!shouldRunTests) { - return - } - setSensorState(CAMERA, oldCameraState) - setSensorState(MICROPHONE, oldMicrophoneState) - } - @Test fun launchActivity_fromQuickSettings_hasContentDescriptions() { context.launchSafetyCenterQsActivity { @@ -134,25 +85,4 @@ class SafetyCenterQsActivityTest { waitDisplayed(By.desc("Switch. Mic access. Blocked")) } } - - private fun deviceSupportsSensorToggle(sensor: Int): Boolean { - return sensorPrivacyManager.supportsSensorToggle(sensor) && - sensorPrivacyManager.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor) - } - - private fun isSensorEnabled(sensor: Int): Boolean { - val isSensorDisabled = - callWithShellPermissionIdentity(OBSERVE_SENSOR_PRIVACY) { - sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor) - } - return !isSensorDisabled - } - - private fun setSensorState(sensor: Int, enabled: Boolean) { - val disableSensor = !enabled - // The sensor is enabled iff the privacy control is disabled. - callWithShellPermissionIdentity(MANAGE_SENSOR_PRIVACY, OBSERVE_SENSOR_PRIVACY) { - sensorPrivacyManager.setSensorPrivacy(sensor, disableSensor) - } - } } diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt index f4ed328ae..326e805a3 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt @@ -24,24 +24,22 @@ import com.android.compatibility.common.util.DisableAnimationRule import com.android.compatibility.common.util.FreezeRotationRule import com.android.safetycenter.resources.SafetyCenterResourcesContext import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID import com.android.safetycenter.testing.SafetyCenterTestData import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceTestData +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed import com.android.safetycenter.testing.UiTestHelper.waitButtonDisplayed import com.android.safetycenter.testing.UiTestHelper.waitButtonNotDisplayed import com.android.safetycenter.testing.UiTestHelper.waitDisplayed import com.android.safetycenter.testing.UiTestHelper.waitNotDisplayed -import org.junit.After -import org.junit.Assume.assumeTrue -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -50,42 +48,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SafetyCenterStatusCardTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() - private val context: Context = getApplicationContext() - private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context) private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetySourceTestData = SafetySourceTestData(context) private val safetyCenterTestData = SafetyCenterTestData(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) - // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - - @Before - fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() - } - - @After - fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() - } + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() @Test fun withUnknownStatus_displaysScanningOnLoad() { diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt index 3aef2097a..dd377cc70 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt @@ -41,7 +41,6 @@ import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafet import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExit import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExitAllowingRetries import com.android.safetycenter.testing.SafetyCenterFlags -import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MULTIPLE_SOURCES_GROUP_ID_1 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID @@ -52,10 +51,12 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5 import com.android.safetycenter.testing.SafetyCenterTestData import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceTestData +import com.android.safetycenter.testing.SupportsSafetyCenterRule import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal import com.android.safetycenter.testing.UiTestHelper.clickDismissIssueCard @@ -78,7 +79,6 @@ import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed import java.util.concurrent.TimeUnit import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -90,52 +90,35 @@ import org.junit.runner.RunWith @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") class SafetyCenterSubpagesTest { - @get:Rule val disableAnimationRule = DisableAnimationRule() - - @get:Rule val freezeRotationRule = FreezeRotationRule() + private val context: Context = getApplicationContext() + private val safetyCenterTestHelper = SafetyCenterTestHelper(context) + private val safetySourceTestData = SafetySourceTestData(context) + private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) + private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context) - @get:Rule val screenRecordRule = ScreenRecordRule() + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() + @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() + @get:Rule(order = 5) val screenRecordRule = ScreenRecordRule() - // It is necessery to couple RetryRule and Timeout to ensure that all the retries together are + // It is necessary to couple RetryRule and Timeout to ensure that all the retries together are // restricted with the test timeout - @get:Rule val retryRule = RetryRule(/* retries= */ 3) - @get:Rule + @get:Rule(order = 6) val retryRule = RetryRule(/* retries = */ 3) + @get:Rule(order = 7) val timeoutRule = Timeout( InstrumentationRegistry.getArguments().getString("timeout_msec", "60000").toLong(), TimeUnit.MILLISECONDS ) - private val context: Context = getApplicationContext() - private val safetyCenterTestHelper = SafetyCenterTestHelper(context) - private val safetySourceTestData = SafetySourceTestData(context) - private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) - private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context) - - // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to - // manually skip the setup and teardown methods. - private val shouldRunTests = context.deviceSupportsSafetyCenter() - - @Before - fun assumeDeviceSupportsSafetyCenterToRunTests() { - assumeTrue(shouldRunTests) - } - @Before fun enableSafetyCenterBeforeTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.setup() SafetyCenterFlags.showSubpages = true } @After fun clearDataAfterTest() { - if (!shouldRunTests) { - return - } - safetyCenterTestHelper.reset() UiAutomatorUtils2.getUiDevice().resetRotation() } diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt index c72166c65..784701b8a 100644 --- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt +++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt @@ -26,8 +26,9 @@ import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAnd import com.android.safetycenter.testing.SafetyCenterFlags import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestHelper -import org.junit.After +import com.android.safetycenter.testing.SafetyCenterTestRule import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -47,17 +48,13 @@ class SafetyCenterInteractionLoggingHelperTests { private val safetyCenterTestHelper = SafetyCenterTestHelper(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) + @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @Before fun setUp() { - safetyCenterTestHelper.setup() SafetyCenterFlags.showSubpages = true } - @After - fun tearDown() { - safetyCenterTestHelper.reset() - } - @Test fun openSafetyCenter() { context.launchSafetyCenterActivity {} diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt index f4677cfed..458516379 100644 --- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt +++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt @@ -25,9 +25,10 @@ import com.android.safetycenter.testing.SafetyCenterFlags import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceTestData -import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -48,20 +49,16 @@ class SafetyCenterNotificationLoggingHelperTests { private val safetySourceTestData = SafetySourceTestData(context) private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) + @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @Before fun setUp() { - safetyCenterTestHelper.setup() SafetyCenterFlags.notificationsEnabled = true SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ID) SafetyCenterFlags.allowStatsdLogging = true safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) } - @After - fun tearDown() { - safetyCenterTestHelper.reset() - } - @Test fun sendNotification() { safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue()) diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt index b7bd60d10..9e3a33018 100644 --- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt +++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt @@ -18,21 +18,30 @@ package android.safetycenter.hostside.device import android.content.Context import android.safetycenter.SafetyCenterManager +import android.safetycenter.SafetyCenterStatus +import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS +import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceErrorDetails import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compatibility.common.util.SystemUtil -import com.android.safetycenter.testing.* +import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission +import com.android.safetycenter.testing.SafetyCenterFlags +import com.android.safetycenter.testing.SafetyCenterTestConfigs import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_3 +import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule import com.android.safetycenter.testing.SafetySourceIntentHandler.Request import com.android.safetycenter.testing.SafetySourceIntentHandler.Response +import com.android.safetycenter.testing.SafetySourceReceiver import com.android.safetycenter.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithReceiverPermissionAndWait -import org.junit.After +import com.android.safetycenter.testing.SafetySourceTestData import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -44,18 +53,14 @@ class SafetySourceStateCollectedLoggingHelperTests { private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! + @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + @Before fun setUp() { - safetyCenterTestHelper.setup() SafetyCenterFlags.allowStatsdLogging = true safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) } - @After - fun tearDown() { - safetyCenterTestHelper.reset() - } - @Test fun triggerStatsPull() { val label = 1 // Arbitrary label in [0, 16) @@ -124,13 +129,11 @@ class SafetySourceStateCollectedLoggingHelperTests { @Test fun refreshAllSources_reasonPageOpen_oneSuccessOneErrorOneTimeout() { - SafetyCenterFlags.setAllRefreshTimeoutsTo(Coroutines.TIMEOUT_SHORT) simulateRefresh(Response.SetData(safetySourceTestData.information), Response.Error, null) } @Test fun refreshAllSources_reasonButtonClick_oneSuccessOneErrorOneTimeout() { - SafetyCenterFlags.setAllRefreshTimeoutsTo(Coroutines.TIMEOUT_SHORT) simulateRefresh( Response.SetData(safetySourceTestData.information), Response.Error, @@ -154,8 +157,23 @@ class SafetySourceStateCollectedLoggingHelperTests { if (source3Response != null) { SafetySourceReceiver.setResponse(Request.Refresh(SOURCE_ID_3), source3Response) } + + val atLeastOneTimeout = + source1Response == null || source2Response == null || source3Response == null + if (atLeastOneTimeout) { + SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) + } + + // Refresh sources and wait until the refresh has fully completed / timed out to ensure that + // things are logged. + val listener = safetyCenterTestHelper.addListener() safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(refreshReason) - // Give time for responses to all sources - Thread.sleep(Coroutines.TIMEOUT_SHORT.toMillis()) + listener.receiveSafetyCenterData { + it.status.refreshStatus == REFRESH_STATUS_DATA_FETCH_IN_PROGRESS || + it.status.refreshStatus == REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS + } + listener.receiveSafetyCenterData { + it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_NONE + } } } diff --git a/tests/utils/safetycenter/AndroidManifest.xml b/tests/utils/safetycenter/AndroidManifest.xml index 9d7c52e62..e6d8460f3 100644 --- a/tests/utils/safetycenter/AndroidManifest.xml +++ b/tests/utils/safetycenter/AndroidManifest.xml @@ -54,5 +54,14 @@ <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity-alias> + + <service android:name=".TestNotificationListener" + android:label="TestNotificationListener" + android:exported="false" + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> + <intent-filter> + <action android:name="android.service.notification.NotificationListenerService" /> + </intent-filter> + </service> </application> </manifest> diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt new file mode 100644 index 000000000..1ed0ecbc3 --- /dev/null +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.safetycenter.testing + +import android.Manifest.permission.MANAGE_SENSOR_PRIVACY +import android.Manifest.permission.OBSERVE_SENSOR_PRIVACY +import android.content.Context +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE +import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity +import org.junit.Assume.assumeTrue +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * A JUnit [TestRule] to ensure a given [sensor] is enabled. + * + * This rule disables sensor privacy before a test and restores the prior state afterwards. + */ +class EnableSensorRule(context: Context, val sensor: Int) : TestRule { + + private val sensorPrivacyManager: SensorPrivacyManager = + context.getSystemService(SensorPrivacyManager::class.java)!! + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + assumeTrue( + "Test device does not support toggling sensor $sensor", + supportsSensorToggle() + ) + val oldSensorPrivacy = isSensorPrivacyEnabled() + setSensorPrivacy(false) + try { + base.evaluate() + } finally { + setSensorPrivacy(oldSensorPrivacy) + } + } + } + } + + private fun supportsSensorToggle(): Boolean = + sensorPrivacyManager.supportsSensorToggle(sensor) && + sensorPrivacyManager.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor) + + private fun isSensorPrivacyEnabled(): Boolean = + callWithShellPermissionIdentity(OBSERVE_SENSOR_PRIVACY) { + sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor) + } + + private fun setSensorPrivacy(enabled: Boolean) { + callWithShellPermissionIdentity(MANAGE_SENSOR_PRIVACY, OBSERVE_SENSOR_PRIVACY) { + sensorPrivacyManager.setSensorPrivacy(sensor, enabled) + } + } +} diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/NotificationCharacteristics.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt index b91c72422..177c2359c 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/NotificationCharacteristics.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.safetycenter.functional.testing +package com.android.safetycenter.testing import android.app.Notification diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt index aad979229..1dee37532 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt @@ -20,9 +20,7 @@ import android.Manifest.permission.READ_DEVICE_CONFIG import android.Manifest.permission.WRITE_DEVICE_CONFIG import android.annotation.TargetApi import android.app.job.JobInfo -import android.content.Context import android.content.pm.PackageManager -import android.content.res.Resources import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE import android.provider.DeviceConfig import android.provider.DeviceConfig.NAMESPACE_PRIVACY @@ -324,12 +322,6 @@ object SafetyCenterFlags { backgroundRefreshRequiresChargingFlag ) - /** Returns whether the device supports Safety Center. */ - fun Context.deviceSupportsSafetyCenter() = - resources.getBoolean( - Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android") - ) - /** A property that allows getting and setting the [isEnabledFlag]. */ var isEnabled: Boolean by isEnabledFlag diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt index d62214843..624c32d5f 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt @@ -46,9 +46,22 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener { runBlockingWithTimeout { errorChannel.send(errorDetails) } } - /** Waits for a [SafetyCenterData] update from SafetyCenter within the given [timeout]. */ - fun receiveSafetyCenterData(timeout: Duration = TIMEOUT_LONG) = - runBlockingWithTimeout(timeout) { dataChannel.receive() } + /** + * Waits for a [SafetyCenterData] update from SafetyCenter within the given [timeout]. + * + * Optionally, a predicate can be used to wait for the [SafetyCenterData] to be [matching]. + */ + fun receiveSafetyCenterData( + timeout: Duration = TIMEOUT_LONG, + matching: (SafetyCenterData) -> Boolean = { true } + ): SafetyCenterData = + runBlockingWithTimeout(timeout) { + var safetyCenterData = dataChannel.receive() + while (!matching(safetyCenterData)) { + safetyCenterData = dataChannel.receive() + } + safetyCenterData + } /** * Waits for a [SafetyCenterErrorDetails] update from SafetyCenter within the given [timeout]. diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt new file mode 100644 index 000000000..005dbe841 --- /dev/null +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.safetycenter.testing + +import android.content.Context +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** A JUnit [TestRule] that performs setup and reset steps before and after Safety Center tests. */ +class SafetyCenterTestRule(private val safetyCenterTestHelper: SafetyCenterTestHelper) : TestRule { + + constructor(context: Context) : this(SafetyCenterTestHelper(context)) + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + safetyCenterTestHelper.setup() + try { + base.evaluate() + } finally { + safetyCenterTestHelper.reset() + } + } + } + } +} diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt index 97e2078e0..e804ff62f 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt @@ -812,13 +812,19 @@ class SafetySourceTestData(private val context: Context) { } /** Returns a [PendingIntent] that redirects to [intent]. */ - fun createRedirectPendingIntent(context: Context, intent: Intent): PendingIntent { + fun createRedirectPendingIntent( + context: Context, + intent: Intent, + inQuietMode: Boolean = false + ): PendingIntent { val explicitIntent = Intent(intent).setPackage(context.packageName) val redirectIntent = if (intentResolves(context, explicitIntent)) { explicitIntent } else if (intentResolves(context, intent)) { intent + } else if (inQuietMode) { + explicitIntent } else { throw IllegalStateException("Intent doesn't resolve") } diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/StatusBarNotificationWithChannel.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt index d9a998c3e..53ea34362 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/StatusBarNotificationWithChannel.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.safetycenter.functional.testing +package com.android.safetycenter.testing import android.app.NotificationChannel import android.service.notification.StatusBarNotification diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt new file mode 100644 index 000000000..bfb5c4bd7 --- /dev/null +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.safetycenter.testing + +import android.content.Context +import android.content.res.Resources + +/** + * Returns whether the device supports Safety Center according to the `config_enableSafetyCenter` + * boolean system resource. + */ +fun Context.deviceSupportsSafetyCenter(): Boolean { + val resId = Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android") + return resources.getBoolean(resId) +} diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt new file mode 100644 index 000000000..7227873ff --- /dev/null +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.safetycenter.testing + +import android.content.Context +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * JUnit [TestRule] for on-device tests that requires Safety Center to be supported. This rule does + * not require Safety Center to be enabled. + * + * For tests which should only run on devices where Safety Center is not supported, instantiate with + * [requireSupportIs] set to `false` to invert the condition. + */ +class SupportsSafetyCenterRule(private val context: Context, requireSupportIs: Boolean = true) : + TestRule { + + private val shouldSupportSafetyCenter: Boolean = requireSupportIs + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + val support = context.deviceSupportsSafetyCenter() + if (shouldSupportSafetyCenter) { + assumeTrue("Test device does not support Safety Center", support) + } else { + assumeFalse("Test device supports Safety Center", support) + } + base.evaluate() + } + } + } +} diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/TestNotificationListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt index 113ad3b23..ebbbc46c8 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/TestNotificationListener.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,11 @@ * limitations under the License. */ -package android.safetycenter.functional.testing +package com.android.safetycenter.testing import android.app.NotificationChannel import android.content.ComponentName +import android.content.Context import android.os.ConditionVariable import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification @@ -86,14 +87,6 @@ class TestNotificationListener : NotificationListenerService() { companion object { private const val TAG = "TestNotificationListene" - private val id: String = - "android.safetycenter.functional/" + TestNotificationListener::class.java.name - private val componentName = - ComponentName( - "android.safetycenter.functional", - TestNotificationListener::class.java.name - ) - private val connected = ConditionVariable(false) private val disconnected = ConditionVariable(true) private var instance: TestNotificationListener? = null @@ -164,7 +157,9 @@ class TestNotificationListener : NotificationListenerService() { return waitForNotificationsToSatisfy( timeout, description = "notification(s) matching characteristics $charsList" - ) { NotificationCharacteristics.areMatching(it, charsList) } + ) { + NotificationCharacteristics.areMatching(it, charsList) + } } /** @@ -283,7 +278,9 @@ class TestNotificationListener : NotificationListenerService() { waitForNotificationsToSatisfy( timeout, description = "no notification with the key $key" - ) { notifications -> notifications.none { it.statusBarNotification.key == key } } + ) { notifications -> + notifications.none { it.statusBarNotification.key == key } + } waitForIssueCacheToContainAnyDismissedNotification() } @@ -313,10 +310,12 @@ class TestNotificationListener : NotificationListenerService() { } /** Runs a shell command to allow or disallow the listener. Use before and after test. */ - private fun toggleListenerAccess(allowed: Boolean) { - // TODO(b/260335646): Try to do this using the AndroidTest.xml instead of in code + private fun toggleListenerAccess(context: Context, allowed: Boolean) { + val componentName = ComponentName(context, TestNotificationListener::class.java) val verb = if (allowed) "allow" else "disallow" - SystemUtil.runShellCommand("cmd notification ${verb}_listener $id") + SystemUtil.runShellCommand( + "cmd notification ${verb}_listener ${componentName.flattenToString()}" + ) if (allowed) { requestRebind(componentName) if (!connected.block(TIMEOUT_LONG.toMillis())) { @@ -332,17 +331,19 @@ class TestNotificationListener : NotificationListenerService() { } /** Prepare the [TestNotificationListener] for a notification test */ - fun setup() { - toggleListenerAccess(true) + fun setup(context: Context) { + toggleListenerAccess(context, true) } /** Clean up the [TestNotificationListener] after executing a notification test. */ - fun reset() { + fun reset(context: Context) { waitForNotificationsToSatisfy( forAtLeast = Duration.ZERO, description = "all Safety Center notifications removed in tear down" - ) { it.isEmpty() } - toggleListenerAccess(false) + ) { + it.isEmpty() + } + toggleListenerAccess(context, false) safetyCenterNotificationEvents.cancel() safetyCenterNotificationEvents = Channel(capacity = Channel.UNLIMITED) } |