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

Commit 9a3c1f1d authored by Tony Mak's avatar Tony Mak
Browse files

Configurable NAS by using DeviceConfig

The latest plan is only system apps with a certain privilege permission
could become NAS. And DeviceConfig could specify any of these valid
candidate to be the default NAS.

So the logic would be like this:
1. If user has set the NAS manually, NMS will persist the user_set bit
   and never mess with it.
2. If it is not the case, NMS will try the NAS defined in DeviceConfig,
   and then the one defined in config.xml

Also added some new shell commands for easy debugging.

Test: atest NotificationAssistantTest.java
Test: atest NotificationManagerServiceTest.java
Test: Use "device_config put" command to set a valid one. Observe that
      NAS is updated and persisted across reboot.
Test: Repeat the command with an invalid one, observe that NAS is not
      updated.
Test: Go to settings, set a NAS, and repeat the device_config command,
      observe that NAS is not changed.
Test: Go to settings, set NAS to be none. Reboot the device, and "none"
      is persisted.

FIXES: 123566150

Change-Id: Ibf8e498944afd5d1fa8659a856a8abdcce41f092
parent 084e4717
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -81,6 +81,12 @@ public final class SystemUiDeviceConfigFlags {
     */
    public static final String SSIN_MAX_NUM_ACTIONS = "ssin_max_num_actions";

    /**
     * The default component of
     * {@link android.service.notification.NotificationAssistantService}.
     */
    public static final String NAS_DEFAULT_SERVICE = "nas_default_service";

    // Flags related to media notifications

    /**
+2 −2
Original line number Diff line number Diff line
@@ -3698,8 +3698,8 @@

    <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>

    <!-- Package name that should be granted Notification Assistant access -->
    <string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string>
    <!-- Component name that should be granted Notification Assistant access -->
    <string name="config_defaultAssistantAccessComponent" translatable="false">android.ext.services/android.ext.services.notification.Assistant</string>

    <bool name="config_supportBluetoothPersistedState">true</bool>

+1 −1
Original line number Diff line number Diff line
@@ -3532,7 +3532,7 @@
  <java-symbol type="string" name="harmful_app_warning_title" />
  <java-symbol type="layout" name="harmful_app_warning_dialog" />

  <java-symbol type="string" name="config_defaultAssistantAccessPackage" />
  <java-symbol type="string" name="config_defaultAssistantAccessComponent" />

  <java-symbol type="bool" name="config_supportBluetoothPersistedState" />

+13 −2
Original line number Diff line number Diff line
@@ -333,6 +333,7 @@ abstract public class ManagedServices {
                        out.attribute(null, ATT_APPROVED_LIST, allowedItems);
                        out.attribute(null, ATT_USER_ID, Integer.toString(approvedUserId));
                        out.attribute(null, ATT_IS_PRIMARY, Boolean.toString(isPrimary));
                        writeExtraAttributes(out, approvedUserId);
                        out.endTag(null, TAG_MANAGED_SERVICES);

                        if (!forBackup && isPrimary) {
@@ -345,10 +346,14 @@ abstract public class ManagedServices {
                }
            }
        }

        out.endTag(null, getConfig().xmlTag);
    }

    /**
     * Writes extra xml attributes to {@link #TAG_MANAGED_SERVICES} tag.
     */
    protected void writeExtraAttributes(XmlSerializer out, int userId) throws IOException {}

    protected void migrateToXml() {
        loadAllowedComponentsFromSettings();
    }
@@ -377,7 +382,7 @@ abstract public class ManagedServices {
                            ? userId : XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                    final boolean isPrimary =
                            XmlUtils.readBooleanAttribute(parser, ATT_IS_PRIMARY, true);

                    readExtraAttributes(tag, parser, resolvedUserId);
                    if (allowedManagedServicePackages == null ||
                            allowedManagedServicePackages.test(getPackageName(approved))) {
                        if (mUm.getUserInfo(resolvedUserId) != null) {
@@ -391,6 +396,12 @@ abstract public class ManagedServices {
        rebindServices(false, USER_ALL);
    }

    /**
     * Read extra attributes in the {@link #TAG_MANAGED_SERVICES} tag.
     */
    protected void readExtraAttributes(String tag, XmlPullParser parser, int userId)
            throws IOException {}

    private void loadAllowedComponentsFromSettings() {
        for (UserInfo user : mUm.getUsers()) {
            final ContentResolver cr = mContext.getContentResolver();
+138 −52
Original line number Diff line number Diff line
@@ -153,6 +153,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -190,6 +191,7 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -515,26 +517,32 @@ public class NotificationManagerService extends SystemService {
            }
        }

        readDefaultAssistant(userId);
        setDefaultAssistantForUser(userId);
    }

    protected void readDefaultAssistant(int userId) {
        String defaultAssistantAccess = getContext().getResources().getString(
                com.android.internal.R.string.config_defaultAssistantAccessPackage);
        if (defaultAssistantAccess != null) {
            // Gather all notification assistant components for candidate pkg. There should
            // only be one
            Set<ComponentName> approvedAssistants =
                    mAssistants.queryPackageForServices(defaultAssistantAccess,
                            MATCH_DIRECT_BOOT_AWARE
                                    | MATCH_DIRECT_BOOT_UNAWARE, userId);
            for (ComponentName cn : approvedAssistants) {
                try {
                    getBinderService().setNotificationAssistantAccessGrantedForUser(
                            cn, userId, true);
                } catch (RemoteException e) {
                    e.printStackTrace();
    protected void setDefaultAssistantForUser(int userId) {
        List<ComponentName> validAssistants = new ArrayList<>(
                mAssistants.queryPackageForServices(
                        null, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId));

        List<String> candidateStrs = new ArrayList<>();
        candidateStrs.add(DeviceConfig.getProperty(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE));
        candidateStrs.add(getContext().getResources().getString(
                com.android.internal.R.string.config_defaultAssistantAccessComponent));

        for (String candidateStr : candidateStrs) {
            if (TextUtils.isEmpty(candidateStr)) {
                continue;
            }
            ComponentName candidate = ComponentName.unflattenFromString(candidateStr);
            if (candidate != null && validAssistants.contains(candidate)) {
                setNotificationAssistantAccessGrantedForUserInternal(candidate, userId, true);
                Slog.d(TAG, String.format("Set default NAS to be %s in %d", candidateStr, userId));
                return;
            } else {
                Slog.w(TAG, "Invalid default NAS config is found: " + candidateStr);
            }
        }
    }
@@ -594,6 +602,8 @@ public class NotificationManagerService extends SystemService {
            mConditionProviders.migrateToXml();
            handleSavePolicyFile();
        }

        mAssistants.resetDefaultAssistantsIfNecessary();
    }

    private void loadPolicyFile() {
@@ -1740,6 +1750,21 @@ public class NotificationManagerService extends SystemService {
        publishLocalService(NotificationManagerInternal.class, mInternalService);
    }

    private void registerDeviceConfigChange() {
        DeviceConfig.addOnPropertyChangedListener(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                getContext().getMainExecutor(),
                (namespace, name, value) -> {
                    if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(namespace)) {
                        return;
                    }
                    if (SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE.equals(name)) {
                        mAssistants.resetDefaultAssistantsIfNecessary();
                    }
                });
    }


    private GroupHelper getGroupHelper() {
        mAutoGroupAtCount =
                getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
@@ -1803,6 +1828,7 @@ public class NotificationManagerService extends SystemService {
            mListeners.onBootPhaseAppsCanStart();
            mAssistants.onBootPhaseAppsCanStart();
            mConditionProviders.onBootPhaseAppsCanStart();
            registerDeviceConfigChange();
        }
    }

@@ -3733,14 +3759,14 @@ public class NotificationManagerService extends SystemService {

        @Override
        public void setNotificationAssistantAccessGranted(ComponentName assistant,
                boolean granted) throws RemoteException {
                boolean granted) {
            setNotificationAssistantAccessGrantedForUser(
                    assistant, getCallingUserHandle().getIdentifier(), granted);
        }

        @Override
        public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
                boolean granted) throws RemoteException {
                boolean granted) {
            Preconditions.checkNotNull(listener);
            checkCallerIsSystemOrShell();
            final long identity = Binder.clearCallingIdentity();
@@ -3766,32 +3792,12 @@ public class NotificationManagerService extends SystemService {

        @Override
        public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant,
                int userId, boolean granted) throws RemoteException {
                int userId, boolean granted) {
            checkCallerIsSystemOrShell();
            if (assistant == null) {
                ComponentName allowedAssistant = CollectionUtils.firstOrNull(
                        mAssistants.getAllowedComponents(userId));
                if (allowedAssistant != null) {
                    setNotificationAssistantAccessGrantedForUser(allowedAssistant, userId, false);
                }
                return;
            }
            mAssistants.setUserSet(userId, true);
            final long identity = Binder.clearCallingIdentity();
            try {
                if (mAllowedManagedServicePackages.test(assistant.getPackageName())) {
                    mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
                            userId, false, granted);
                    mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
                            userId, true, granted);

                    getContext().sendBroadcastAsUser(new Intent(
                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                    .setPackage(assistant.getPackageName())
                                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
                            UserHandle.of(userId), null);

                    handleSavePolicyFile();
                }
                setNotificationAssistantAccessGrantedForUserInternal(assistant, userId, granted);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
@@ -4004,6 +4010,34 @@ public class NotificationManagerService extends SystemService {
        }
    };

    @VisibleForTesting
    protected void setNotificationAssistantAccessGrantedForUserInternal(
            ComponentName assistant, int userId, boolean granted) {
        if (assistant == null) {
            ComponentName allowedAssistant = CollectionUtils.firstOrNull(
                    mAssistants.getAllowedComponents(userId));
            if (allowedAssistant != null) {
                setNotificationAssistantAccessGrantedForUserInternal(
                        allowedAssistant, userId, false);
            }
            return;
        }
        if (mAllowedManagedServicePackages.test(assistant.getPackageName())) {
            mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
                    userId, false, granted);
            mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
                    userId, true, granted);

            getContext().sendBroadcastAsUser(new Intent(
                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                            .setPackage(assistant.getPackageName())
                            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
                    UserHandle.of(userId), null);

            handleSavePolicyFile();
        }
    }

    private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
        if (r == null) {
            return;
@@ -7091,6 +7125,13 @@ public class NotificationManagerService extends SystemService {
    public class NotificationAssistants extends ManagedServices {
        static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";

        private static final String ATT_USER_SET = "user_set";

        private final Object mLock = new Object();

        @GuardedBy("mLock")
        private ArrayMap<Integer, Boolean> mUserSetMap = new ArrayMap<>();

        public NotificationAssistants(Context context, Object lock, UserProfiles up,
                IPackageManager pm) {
            super(context, lock, up, pm);
@@ -7157,6 +7198,30 @@ public class NotificationManagerService extends SystemService {
            }
        }

        boolean hasUserSet(int userId) {
            synchronized (mLock) {
                return mUserSetMap.getOrDefault(userId, false);
            }
        }

        void setUserSet(int userId, boolean set) {
            synchronized (mLock) {
                mUserSetMap.put(userId, set);
            }
        }

        @Override
        protected void writeExtraAttributes(XmlSerializer out, int userId) throws IOException {
            out.attribute(null, ATT_USER_SET, Boolean.toString(hasUserSet(userId)));
        }

        @Override
        protected void readExtraAttributes(String tag, XmlPullParser parser, int userId)
                throws IOException {
            boolean userSet = XmlUtils.readBooleanAttribute(parser, ATT_USER_SET, false);
            setUserSet(userId, userSet);
        }

        private void notifySeen(final ManagedServiceInfo info,
                final ArrayList<String> keys) {
            final INotificationListener assistant = (INotificationListener) info.service;
@@ -7314,13 +7379,13 @@ public class NotificationManagerService extends SystemService {
            return !getServices().isEmpty();
        }

        protected void ensureAssistant() {
        protected void resetDefaultAssistantsIfNecessary() {
            final List<UserInfo> activeUsers = mUm.getUsers(true);
            for (UserInfo userInfo : activeUsers) {
                int userId = userInfo.getUserHandle().getIdentifier();
                if (getAllowedPackages(userId).isEmpty()) {
                if (!hasUserSet(userId)) {
                    Slog.d(TAG, "Approving default notification assistant for user " + userId);
                    readDefaultAssistant(userId);
                    setDefaultAssistantForUser(userId);
                }
            }
        }
@@ -7334,16 +7399,24 @@ public class NotificationManagerService extends SystemService {
                if (!allowedComponents.isEmpty()) {
                    ComponentName currentComponent = CollectionUtils.firstOrNull(allowedComponents);
                    if (currentComponent.flattenToString().equals(pkgOrComponent)) return;
                    try {
                        getBinderService().setNotificationAssistantAccessGrantedForUser(
                    setNotificationAssistantAccessGrantedForUserInternal(
                            currentComponent, userId, false);
                    } catch (RemoteException e) {
                        e.rethrowFromSystemServer();
                    }
                }
            }
            super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled);
        }

        @Override
        public void dump(PrintWriter pw, DumpFilter filter) {
            super.dump(pw, filter);
            pw.println("    Has user set:");
            synchronized (mLock) {
                Set<Integer> userIds = mUserSetMap.keySet();
                for (int userId : userIds) {
                    pw.println("      userId=" + userId + " value=" + mUserSetMap.get(userId));
                }
            }
        }
    }

    public class NotificationListeners extends ManagedServices {
@@ -7895,6 +7968,19 @@ public class NotificationManagerService extends SystemService {
        }
    }

    @VisibleForTesting
    void resetAssistantUserSet(int userId) {
        mAssistants.setUserSet(userId, false);
        handleSavePolicyFile();
    }

    @VisibleForTesting
    @Nullable
    ComponentName getApprovedAssistant(int userId) {
        List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
        return CollectionUtils.firstOrNull(allowedComponents);
    }

    @VisibleForTesting
    protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) {
        // only use for testing: mimic receive broadcast that package is (un)suspended
Loading