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

Commit 46863385 authored by Lyn Han's avatar Lyn Han Committed by Android (Google) Code Review
Browse files

Merge "Flag Notification object when FSI permission is denied"

parents 668c10cb 78413c7e
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;