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

Commit 1184a696 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Migrate are/setNotificationsEnabled to PM

To test this I'm temporarily created a new NMS level test file that
is identical to NMSTest's configuration except that migration is enabled.
Since the setting has to be set before NotificationManagerService is enabled,
it's hard to set on a test method level whether the migration should be enabled
but it's easy to set on a test class level.

Test: atest
Bug: 194833441
Change-Id: I476ee85c06e430a1c6305e53d17103eb3128ae31
parent f9b28756
Loading
Loading
Loading
Loading
+54 −16
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static android.app.NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
@@ -291,6 +292,7 @@ import com.android.server.notification.toast.CustomToastRecord;
import com.android.server.notification.toast.TextToastRecord;
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -489,6 +491,7 @@ public class NotificationManagerService extends SystemService {
    private UserManager mUm;
    private IPlatformCompat mPlatformCompat;
    private ShortcutHelper mShortcutHelper;
    private PermissionHelper mPermissionHelper;

    final IBinder mForegroundToken = new Binder();
    private WorkerHandler mHandler;
@@ -606,6 +609,7 @@ public class NotificationManagerService extends SystemService {

    private int mWarnRemoteViewsSizeBytes;
    private int mStripRemoteViewsSizeBytes;
    final boolean mEnableAppSettingMigration;

    private MetricsLogger mMetricsLogger;
    private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
@@ -2003,6 +2007,12 @@ public class NotificationManagerService extends SystemService {
        mNotificationRecordLogger = notificationRecordLogger;
        mNotificationInstanceIdSequence = notificationInstanceIdSequence;
        Notification.processAllowlistToken = ALLOWLIST_TOKEN;
        // TODO (b/194833441): remove when OS is ready for migration. This flag is checked once
        // rather than having a settings observer because some of the behaviors (e.g. readXml) only
        // happen on reboot
        mEnableAppSettingMigration = Settings.Secure.getIntForUser(
                getContext().getContentResolver(),
                Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM) == 1;
    }

    // TODO - replace these methods with new fields in the VisibleForTesting constructor
@@ -2169,7 +2179,7 @@ public class NotificationManagerService extends SystemService {
            UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
            NotificationHistoryManager historyManager, StatsManager statsManager,
            TelephonyManager telephonyManager, ActivityManagerInternal ami,
            MultiRateLimiter toastRateLimiter) {
            MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) {
        mHandler = handler;
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2275,6 +2285,7 @@ public class NotificationManagerService extends SystemService {
        mGroupHelper = groupHelper;
        mVibratorHelper = new VibratorHelper(getContext());
        mHistoryManager = historyManager;
        mPermissionHelper = permissionHelper;

        // This is a ManagedServices object that keeps track of the listeners.
        mListeners = notificationListeners;
@@ -2480,7 +2491,9 @@ public class NotificationManagerService extends SystemService {
                        Context.STATS_MANAGER),
                getContext().getSystemService(TelephonyManager.class),
                LocalServices.getService(ActivityManagerInternal.class),
                createToastRateLimiter());
                createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
                        PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
                        AppGlobals.getPermissionManager(), mEnableAppSettingMigration));

        publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -3411,7 +3424,13 @@ public class NotificationManagerService extends SystemService {
        @Override
        public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
            enforceSystemOrSystemUI("setNotificationsEnabledForPackage");

            if (mEnableAppSettingMigration) {
                boolean wasEnabled = mPermissionHelper.hasPermission(uid);
                if (wasEnabled == enabled) {
                    return;
                }
                mPermissionHelper.setNotificationPermission(pkg, uid, enabled, true);
            } else {
                synchronized (mNotificationLock) {
                    boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid)
                            != NotificationManager.IMPORTANCE_NONE;
@@ -3422,6 +3441,7 @@ public class NotificationManagerService extends SystemService {
                }

                mPreferencesHelper.setEnabled(pkg, uid, enabled);
            }
            mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
                    .setType(MetricsEvent.TYPE_ACTION)
                    .setPackageName(pkg)
@@ -3489,8 +3509,12 @@ public class NotificationManagerService extends SystemService {
                        "canNotifyAsPackage for uid " + uid);
            }

            if (mEnableAppSettingMigration) {
                return mPermissionHelper.hasPermission(uid);
            } else {
                return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
            }
        }

        /**
         * @return true if and only if "all" bubbles are allowed from the provided package.
@@ -3581,8 +3605,16 @@ public class NotificationManagerService extends SystemService {
        @Override
        public int getPackageImportance(String pkg) {
            checkCallerIsSystemOrSameApp(pkg);
            if (mEnableAppSettingMigration) {
                if (mPermissionHelper.hasPermission(Binder.getCallingUid())) {
                    return IMPORTANCE_DEFAULT;
                } else {
                    return IMPORTANCE_NONE;
                }
            } else {
                return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
            }
        }

        @Override
        public boolean canShowBadge(String pkg, int uid) {
@@ -6248,11 +6280,17 @@ public class NotificationManagerService extends SystemService {
                    + ", notificationUid=" + notificationUid
                    + ", notification=" + notification;
            Slog.e(TAG, noChannelStr);
            boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
            boolean appNotificationsOff;
            if (mEnableAppSettingMigration) {
                appNotificationsOff = !mPermissionHelper.hasPermission(notificationUid);
            } else {
                appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
                        == NotificationManager.IMPORTANCE_NONE;
            }

            if (!appNotificationsOff) {
                doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                doChannelWarningToast(notificationUid,
                        "Developer warning for package \"" + pkg + "\"\n" +
                        "Failed to post notification on channel \"" + channelId + "\"\n" +
                        "See log for more details");
            }
@@ -6498,7 +6536,7 @@ public class NotificationManagerService extends SystemService {
                }
            };

    private void doChannelWarningToast(CharSequence toastText) {
    protected void doChannelWarningToast(int forUid, CharSequence toastText) {
        Binder.withCleanCallingIdentity(() -> {
            final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
            final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
@@ -10871,7 +10909,7 @@ public class NotificationManagerService extends SystemService {
        }
    }


    // TODO (b/194833441): remove when we've fully migrated to a permission
    class RoleObserver implements OnRoleHoldersChangedListener {
        // Role name : user id : list of approved packages
        private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
+13 −78
Original line number Diff line number Diff line
@@ -246,6 +246,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    private PackageManager mPackageManagerClient;
    @Mock
    private WindowManagerInternal mWindowManagerInternal;
    @Mock
    private PermissionHelper mPermissionHelper;
    private TestableContext mContext = spy(getContext());
    private final String PKG = mContext.getPackageName();
    private TestableLooper mTestableLooper;
@@ -334,83 +336,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {

    private NotificationManagerService.WorkerHandler mWorkerHandler;

    // Use a Testable subclass so we can simulate calls from the system without failing.
    private static class TestableNotificationManagerService extends NotificationManagerService {
        int countSystemChecks = 0;
        boolean isSystemUid = true;
        int countLogSmartSuggestionsVisible = 0;
        @Nullable
        NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;

        TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
                InstanceIdSequence notificationInstanceIdSequence) {
            super(context, logger, notificationInstanceIdSequence);
        }

        RankingHelper getRankingHelper() {
            return mRankingHelper;
        }

        @Override
        protected boolean isCallingUidSystem() {
            countSystemChecks++;
            return isSystemUid;
        }

        @Override
        protected boolean isCallerSystemOrPhone() {
            countSystemChecks++;
            return isSystemUid;
        }

        @Override
        protected ICompanionDeviceManager getCompanionManager() {
            return null;
        }

        @Override
        protected void reportUserInteraction(NotificationRecord r) {
            return;
        }

        @Override
        protected void handleSavePolicyFile() {
            return;
        }

        @Override
        void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
            super.logSmartSuggestionsVisible(r, notificationLocation);
            countLogSmartSuggestionsVisible++;
        }

        @Override
        protected void setNotificationAssistantAccessGrantedForUserInternal(
                ComponentName assistant, int userId, boolean granted, boolean userSet) {
            if (mNotificationAssistantAccessGrantedCallback != null) {
                mNotificationAssistantAccessGrantedCallback.onGranted(assistant, userId, granted,
                        userSet);
                return;
            }
            super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted,
                    userSet);
        }

        @Override
        protected String[] getStringArrayResource(int key) {
            return new String[] {PKG_O};
        }

        private void setNotificationAssistantAccessGrantedCallback(
                @Nullable NotificationAssistantAccessGrantedCallback callback) {
            this.mNotificationAssistantAccessGrantedCallback = callback;
        }

        interface NotificationAssistantAccessGrantedCallback {
            void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet);
        }
    }

    private class TestableToastCallback extends ITransientNotification.Stub {
        @Override
        public void show(IBinder windowToken) {
@@ -516,13 +441,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {

        when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);

        // apps allowed as convos
        mService.setStringArrayResourceValue(PKG_O);

        mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
        mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
                mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
                mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
                mAmi, mToastRateLimiter);
                mAmi, mToastRateLimiter, mPermissionHelper);
        // Return first true for RoleObserver main-thread check
        when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -6685,6 +6613,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        enableInteractAcrossUsers();
        mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
                mUid + UserHandle.PER_USER_RANGE);

        verify(mPermissionHelper, never()).hasPermission(anyInt());
    }

    @Test
@@ -8335,4 +8265,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertTrue(captor.getValue().isPackageAllowed(new VersionedPackage("apples", 1001)));
        assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("test", 1002)));
    }

    @Test
    public void testMigrationDisabledByDefault() {
        assertThat(mService.mEnableAppSettingMigration).isFalse();
    }
}
+574 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
// TODO (b/194833441): remove when notification permission is enabled
public class RoleObserverTest extends UiServiceTestCase {
    private TestableNotificationManagerService mService;
    private NotificationManagerService.RoleObserver mRoleObserver;
@@ -162,7 +163,7 @@ public class RoleObserverTest extends UiServiceTestCase {
                    mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
                    mock(StatsManager.class), mock(TelephonyManager.class),
                    mock(ActivityManagerInternal.class),
                    mock(MultiRateLimiter.class));
                    mock(MultiRateLimiter.class), mock(PermissionHelper.class));
        } catch (SecurityException e) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                throw e;
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.notification;

import android.companion.ICompanionDeviceManager;
import android.content.ComponentName;
import android.content.Context;

import androidx.annotation.Nullable;

import com.android.internal.logging.InstanceIdSequence;

import java.util.HashSet;
import java.util.Set;

public class TestableNotificationManagerService extends NotificationManagerService {
    int countSystemChecks = 0;
    boolean isSystemUid = true;
    int countLogSmartSuggestionsVisible = 0;
    Set<Integer> mChannelToastsSent = new HashSet<>();

    String stringArrayResourceValue;
    @Nullable
    NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;

    TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
            InstanceIdSequence notificationInstanceIdSequence) {
        super(context, logger, notificationInstanceIdSequence);
    }

    RankingHelper getRankingHelper() {
        return mRankingHelper;
    }

    @Override
    protected boolean isCallingUidSystem() {
        countSystemChecks++;
        return isSystemUid;
    }

    @Override
    protected boolean isCallerSystemOrPhone() {
        countSystemChecks++;
        return isSystemUid;
    }

    @Override
    protected ICompanionDeviceManager getCompanionManager() {
        return null;
    }

    @Override
    protected void reportUserInteraction(NotificationRecord r) {
        return;
    }

    @Override
    protected void handleSavePolicyFile() {
        return;
    }

    @Override
    void logSmartSuggestionsVisible(NotificationRecord r, int notificationLocation) {
        super.logSmartSuggestionsVisible(r, notificationLocation);
        countLogSmartSuggestionsVisible++;
    }

    @Override
    protected void setNotificationAssistantAccessGrantedForUserInternal(
            ComponentName assistant, int userId, boolean granted, boolean userSet) {
        if (mNotificationAssistantAccessGrantedCallback != null) {
            mNotificationAssistantAccessGrantedCallback.onGranted(assistant, userId, granted,
                    userSet);
            return;
        }
        super.setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted,
                userSet);
    }

    @Override
    protected String[] getStringArrayResource(int key) {
        return new String[] {stringArrayResourceValue};
    }

    protected void setStringArrayResourceValue(String value) {
        stringArrayResourceValue = value;
    }

    void setNotificationAssistantAccessGrantedCallback(
            @Nullable NotificationAssistantAccessGrantedCallback callback) {
        this.mNotificationAssistantAccessGrantedCallback = callback;
    }

    interface NotificationAssistantAccessGrantedCallback {
        void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet);
    }

    @Override
    protected void doChannelWarningToast(int uid, CharSequence toastText) {
        mChannelToastsSent.add(uid);
    }
}