Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78413c7e authored by Lyn's avatar Lyn
Browse files

Flag Notification object when FSI permission is denied

Bug: 243421660
Test: atest NotificationManagerServiceTest
Test: manual logging to see that Notification flag is set

adb shell setprop [flag] 1

Change-Id: If00c67d1b58696ab53426627e54c5a1843b7db4c
parent fe2a1203
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -712,6 +712,15 @@ public class Notification implements Parcelable
     */
    public static final int FLAG_NO_DISMISS = 0x00002000;
    /**
     * Bit to be bitwise-ORed into the {@link #flags} field that should be
     * set by the system if the app that sent this notification does not have the permission to send
     * full screen intents.
     *
     * This flag is for internal use only; applications cannot set this flag directly.
     * @hide
     */
    public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000;
    private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
            BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
+54 −8
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
@@ -175,6 +176,7 @@ import android.companion.ICompanionDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.LoggingOnly;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -226,6 +228,8 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.notification.Adjustment;
@@ -528,6 +532,7 @@ public class NotificationManagerService extends SystemService {
    private IPackageManager mPackageManager;
    private PackageManager mPackageManagerClient;
    PackageManagerInternal mPackageManagerInternal;
    private PermissionManager mPermissionManager;
    private PermissionPolicyInternal mPermissionPolicyInternal;
    AudioManager mAudioManager;
    AudioManagerInternal mAudioManagerInternal;
@@ -2226,7 +2231,8 @@ public class NotificationManagerService extends SystemService {
            MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
            UsageStatsManagerInternal usageStatsManagerInternal,
            TelecomManager telecomManager, NotificationChannelLogger channelLogger,
            SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
            SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
            PermissionManager permissionManager) {
        mHandler = handler;
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2243,6 +2249,7 @@ public class NotificationManagerService extends SystemService {
        mPackageManager = packageManager;
        mPackageManagerClient = packageManagerClient;
        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
        mPermissionManager = permissionManager;
        mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
        mUmInternal = LocalServices.getService(UserManagerInternal.class);
        mUsageStatsManagerInternal = usageStatsManagerInternal;
@@ -2557,7 +2564,8 @@ public class NotificationManagerService extends SystemService {
                        AppGlobals.getPermissionManager()),
                LocalServices.getService(UsageStatsManagerInternal.class),
                getContext().getSystemService(TelecomManager.class),
                new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver());
                new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
                getContext().getSystemService(PermissionManager.class));
        publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -6666,10 +6674,21 @@ public class NotificationManagerService extends SystemService {
        handleSavePolicyFile();
    }
    private void makeStickyHun(Notification notification) {
        notification.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
        if (notification.contentIntent == null) {
            // On notification click, if contentIntent is null, SystemUI launches the
            // fullScreenIntent instead.
            notification.contentIntent = notification.fullScreenIntent;
        }
        notification.fullScreenIntent = null;
    }
    @VisibleForTesting
    protected void fixNotification(Notification notification, String pkg, String tag, int id,
            @UserIdInt int userId, int notificationUid) throws NameNotFoundException,
            RemoteException {
        final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
@@ -6707,13 +6726,40 @@ public class NotificationManagerService extends SystemService {
            }
        }
        notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
        if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
            final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
                    SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
            final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
                    SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
            if (forceDemoteFsiToStickyHun) {
                makeStickyHun(notification);
            } else if (showStickyHunIfDenied) {
                final AttributionSource source = new AttributionSource.Builder(notificationUid)
                        .setPackageName(pkg)
                        .build();
                final int permissionResult = mPermissionManager.checkPermissionForDataDelivery(
                        Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
                if (permissionResult != PermissionCheckerManager.PERMISSION_GRANTED) {
                    makeStickyHun(notification);
                }
            } else {
                int fullscreenIntentPermission = getContext().checkPermission(
                        android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid);
                if (fullscreenIntentPermission != PERMISSION_GRANTED) {
                    notification.fullScreenIntent = null;
                Slog.w(TAG, "Package " + pkg +
                        ": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission");
                    Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
                            + "USE_FULL_SCREEN_INTENT permission");
                }
            }
        }
+92 −1
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;

import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -73,6 +74,8 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;

import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;

import static com.google.common.truth.Truth.assertThat;
@@ -82,6 +85,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

@@ -111,6 +115,7 @@ import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -170,6 +175,8 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -203,6 +210,7 @@ import androidx.test.InstrumentationRegistry;

import com.android.internal.app.IAppOpsService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
@@ -320,6 +328,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    @Mock
    private NotificationManager mMockNm;
    @Mock
    private PermissionManager mPermissionManager;
    @Mock
    private DevicePolicyManagerInternal mDevicePolicyManager;

    @Mock
@@ -527,7 +537,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
                mock(TelephonyManager.class),
                mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
                mTelecomManager, mLogger, mTestFlagResolver);
                mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager);
        // Return first true for RoleObserver main-thread check
        when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -604,6 +614,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
        clearInvocations(mRankingHandler);
        when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);

        mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
        mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
    }

    @After
@@ -10111,6 +10124,84 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
    }

    private void verifyStickyHun(Flag flag, int permissionState, boolean isSticky)
            throws Exception {

        mTestFlagResolver.setFlagOverride(flag, true);

        when(mPermissionManager.checkPermissionForDataDelivery(
                eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any()))
                .thenReturn(permissionState);

        Notification n = new Notification.Builder(mContext, "test")
                .setFullScreenIntent(mock(PendingIntent.class), true)
                .build();

        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);

        final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED;

        if (isSticky) {
            assertNotSame(0, stickyFlag);
        } else {
            assertSame(0, stickyFlag);
        }
    }

    @Test
    public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun()
            throws Exception {

        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_HARD_DENIED,
                /* isSticky= */ true);
    }

    @Test
    public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun()
            throws Exception {

        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_SOFT_DENIED,
                /* isSticky= */ true);
    }

    @Test
    public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
            throws Exception {

        verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_GRANTED,
                /* isSticky= */ false);
    }

    @Test
    public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun()
            throws Exception {

        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_HARD_DENIED,
                /* isSticky= */ true);
    }

    @Test
    public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun()
            throws Exception {

        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_SOFT_DENIED,
                /* isSticky= */ true);
    }

    @Test
    public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun()
            throws Exception {

        verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
                /* permissionState= */ PermissionCheckerManager.PERMISSION_GRANTED,
                /* isSticky= */ true);
    }

    @Test
    public void fixSystemNotification_withOnGoingFlag_shouldBeNonDismissible()
            throws Exception {
+3 −1
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import android.content.pm.PackageManager;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
@@ -169,7 +170,8 @@ public class RoleObserverTest extends UiServiceTestCase {
                    mock(ActivityManagerInternal.class),
                    mock(MultiRateLimiter.class), mock(PermissionHelper.class),
                    mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
                    mock(NotificationChannelLogger.class), new TestableFlagResolver());
                    mock(NotificationChannelLogger.class), new TestableFlagResolver(),
                    mock(PermissionManager.class));
        } catch (SecurityException e) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                throw e;