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

Commit 763bb16d authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Handle upgrade and B&R compatibilty

If loading xml that's an earlier version (either from B&R
or an OS upgrade), then migrate the app importance setting to
permissionmanager.

When writing XML for B&R, include app level permission values from
permissionmanager rather than local storage. Although permissions
are B&Red separately this information needs to be included in the
notifications backup in case it's restored onto an earlier OS.

Test: PermissionHelperTest/PreferencesHelperTest
Bug: 194833441
Change-Id: Id1271c538f524b90ee8c9b4eb447f872958b322c
parent 8e20aba8
Loading
Loading
Loading
Loading
+70 −8
Original line number Diff line number Diff line
@@ -21,22 +21,27 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.permission.PermissionManager.PERMISSION_GRANTED;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;

import com.android.server.pm.permission.PermissionManagerServiceInternal;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * NotificationManagerService helper for querying/setting the app-level notification permission
@@ -44,7 +49,7 @@ import java.util.Map;
public final class PermissionHelper {
    private static final String TAG = "PermissionHelper";

    private static String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
    private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;

    private final PermissionManagerServiceInternal mPmi;
    private final IPackageManager mPackageManager;
@@ -77,9 +82,9 @@ public final class PermissionHelper {
     * Returns all of the apps that have requested the notification permission in a given user.
     * Must not be called with a lock held. Format: uid, packageName
     */
    public Map<Integer, String> getAppsRequestingPermission(int userId) {
    Set<Pair<Integer, String>> getAppsRequestingPermission(int userId) {
        assertFlag();
        Map<Integer, String> requested = new HashMap<>();
        Set<Pair<Integer, String>> requested = new HashSet<>();
        List<PackageInfo> pkgs = getInstalledPackages(userId);
        for (PackageInfo pi : pkgs) {
            // when data was stored in PreferencesHelper, we only had data for apps that
@@ -90,7 +95,7 @@ public final class PermissionHelper {
            }
            for (String perm : pi.requestedPermissions) {
                if (NOTIFICATION_PERMISSION.equals(perm)) {
                    requested.put(pi.applicationInfo.uid, pi.packageName);
                    requested.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
                    break;
                }
            }
@@ -115,9 +120,9 @@ public final class PermissionHelper {
     * Returns a list of apps that hold the notification permission. Must not be called
     * with a lock held. Format: uid, packageName.
     */
    public Map<Integer, String> getAppsGrantedPermission(int userId) {
    Set<Pair<Integer, String>> getAppsGrantedPermission(int userId) {
        assertFlag();
        Map<Integer, String> granted = new HashMap<>();
        Set<Pair<Integer, String>> granted = new HashSet<>();
        ParceledListSlice<PackageInfo> parceledList = null;
        try {
            parceledList = mPackageManager.getPackagesHoldingPermissions(
@@ -129,11 +134,24 @@ public final class PermissionHelper {
            return granted;
        }
        for (PackageInfo pi : parceledList.getList()) {
            granted.put(pi.applicationInfo.uid, pi.packageName);
            granted.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
        }
        return granted;
    }

    public @NonNull
    ArrayMap<Pair<Integer, String>, Boolean> getNotificationPermissionValues(
            int userId) {
        assertFlag();
        ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
        Set<Pair<Integer, String>> allRequestingUids = getAppsRequestingPermission(userId);
        Set<Pair<Integer, String>> allApprovedUids = getAppsGrantedPermission(userId);
        for (Pair<Integer, String> pair : allRequestingUids) {
            notifPermissions.put(pair, allApprovedUids.contains(pair));
        }
        return notifPermissions;
    }

    /**
     * Grants or revokes the notification permission for a given package/user. UserSet should
     * only be true if this method is being called to migrate existing user choice, because it
@@ -159,6 +177,12 @@ public final class PermissionHelper {
        }
    }

    public void setNotificationPermission(PackagePermission pkgPerm) {
        assertFlag();
        setNotificationPermission(
                pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
    }

    public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
        assertFlag();
        try {
@@ -177,4 +201,42 @@ public final class PermissionHelper {
            throw new IllegalStateException("Method called without checking flag value");
        }
    }

    public static class PackagePermission {
        public final String packageName;
        public final @UserIdInt int userId;
        public final boolean granted;
        public final boolean userSet;

        public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) {
            this.packageName = pkg;
            this.userId = userId;
            this.granted = granted;
            this.userSet = userSet;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PackagePermission that = (PackagePermission) o;
            return userId == that.userId && granted == that.granted && userSet == that.userSet
                    && Objects.equals(packageName, that.packageName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(packageName, userId, granted, userSet);
        }

        @Override
        public String toString() {
            return "PackagePermission{" +
                    "packageName='" + packageName + '\'' +
                    ", userId=" + userId +
                    ", granted=" + granted +
                    ", userSet=" + userSet +
                    '}';
        }
    }
}
+107 −78
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
@@ -68,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.notification.PermissionHelper.PackagePermission;

import org.json.JSONArray;
import org.json.JSONException;
@@ -89,7 +91,7 @@ import java.util.concurrent.ConcurrentHashMap;

public class PreferencesHelper implements RankingConfig {
    private static final String TAG = "NotificationPrefHelper";
    private static final int XML_VERSION = 2;
    private final int XML_VERSION;
    /** What version to check to do the upgrade for bubbles. */
    private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
    @VisibleForTesting
@@ -201,6 +203,12 @@ public class PreferencesHelper implements RankingConfig {
        mAppOps = appOpsManager;
        mStatsEventBuilderFactory = statsEventBuilderFactory;

        if (mPermissionHelper.isMigrationEnabled()) {
            XML_VERSION = 3;
        } else {
            XML_VERSION = 2;
        }

        updateBadgingEnabled();
        updateBubblesEnabled();
        updateMediaNotificationFilteringEnabled();
@@ -217,11 +225,13 @@ public class PreferencesHelper implements RankingConfig {

        final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
        boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
        boolean migrateToPermission = (xmlVersion < XML_VERSION);
        ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
        synchronized (mPackagePreferences) {
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                tag = parser.getName();
                if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
                    return;
                    break;
                }
                if (type == XmlPullParser.START_TAG) {
                    if (TAG_STATUS_ICONS.equals(tag)) {
@@ -252,11 +262,12 @@ public class PreferencesHelper implements RankingConfig {
                                    ? BUBBLE_PREFERENCE_ALL
                                    : parser.getAttributeInt(
                                            null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
                            int appImportance = parser.getAttributeInt(
                                    null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);

                            PackagePreferences r = getOrCreatePackagePreferencesLocked(
                                    name, userId, uid,
                                    parser.getAttributeInt(
                                            null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
                                    appImportance,
                                    parser.getAttributeInt(
                                            null, ATT_PRIORITY, DEFAULT_PRIORITY),
                                    parser.getAttributeInt(
@@ -264,8 +275,6 @@ public class PreferencesHelper implements RankingConfig {
                                    parser.getAttributeBoolean(
                                            null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
                                    bubblePref);
                            r.importance = parser.getAttributeInt(
                                    null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
                            r.priority = parser.getAttributeInt(
                                    null, ATT_PRIORITY, DEFAULT_PRIORITY);
                            r.visibility = parser.getAttributeInt(
@@ -340,6 +349,7 @@ public class PreferencesHelper implements RankingConfig {
                                        }
                                    }
                                }

                                // Delegate
                                if (TAG_DELEGATE.equals(tagName)) {
                                    int delegateId =
@@ -367,12 +377,30 @@ public class PreferencesHelper implements RankingConfig {
                            } catch (PackageManager.NameNotFoundException e) {
                                Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e);
                            }

                            if (migrateToPermission) {
                                boolean hasChangedChannel = false;
                                for (NotificationChannel channel : r.channels.values()) {
                                    if (channel.getUserLockedFields() != 0) {
                                        hasChangedChannel = true;
                                        break;
                                    }
                                }
                                PackagePermission pkgPerm = new PackagePermission(
                                        r.pkg, userId, appImportance != IMPORTANCE_NONE,
                                        hasChangedChannel  || appImportance == IMPORTANCE_NONE);
                                pkgPerms.add(pkgPerm);
                            } else {
                                r.importance = appImportance;
                            }
                        }
                    }
                }
            }
        }
        throw new IllegalStateException("Failed to reach END_DOCUMENT");
        for (PackagePermission p : pkgPerms) {
            mPermissionHelper.setNotificationPermission(p);
        }
    }

    private boolean isShortcutOk(NotificationChannel channel) {
@@ -402,18 +430,12 @@ public class PreferencesHelper implements RankingConfig {

    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
            int uid) {
        // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE
        return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
                DEFAULT_BUBBLE_PREFERENCE);
    }

    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
            @UserIdInt int userId, int uid) {
        return getOrCreatePackagePreferencesLocked(pkg, userId, uid,
                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
                DEFAULT_BUBBLE_PREFERENCE);
    }

    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
            @UserIdInt int userId, int uid, int importance, int priority, int visibility,
            boolean showBadge, int bubblePreference) {
@@ -535,6 +557,10 @@ public class PreferencesHelper implements RankingConfig {
            out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
            out.endTag(null, TAG_STATUS_ICONS);
        }
        ArrayMap<Pair<Integer, String>, Boolean> notifPermissions = new ArrayMap<>();
        if (mPermissionHelper.isMigrationEnabled() && forBackup) {
            notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
        }

        synchronized (mPackagePreferences) {
            final int N = mPackagePreferences.size();
@@ -543,25 +569,19 @@ public class PreferencesHelper implements RankingConfig {
                if (forBackup && UserHandle.getUserId(r.uid) != userId) {
                    continue;
                }
                final boolean hasNonDefaultSettings =
                        r.importance != DEFAULT_IMPORTANCE
                                || r.priority != DEFAULT_PRIORITY
                                || r.visibility != DEFAULT_VISIBILITY
                                || r.showBadge != DEFAULT_SHOW_BADGE
                                || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
                                || r.channels.size() > 0
                                || r.groups.size() > 0
                                || r.delegate != null
                                || r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE
                                || r.hasSentInvalidMessage
                                || r.userDemotedMsgApp
                                || r.hasSentValidMessage;
                if (hasNonDefaultSettings) {
                out.startTag(null, TAG_PACKAGE);
                out.attribute(null, ATT_NAME, r.pkg);
                if (!notifPermissions.isEmpty()) {
                    Pair<Integer, String> app = new Pair(r.uid, r.pkg);
                    out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app)
                            ? IMPORTANCE_DEFAULT
                            : IMPORTANCE_NONE);
                    notifPermissions.remove(app);
                } else {
                    if (r.importance != DEFAULT_IMPORTANCE) {
                        out.attributeInt(null, ATT_IMPORTANCE, r.importance);
                    }
                }
                if (r.priority != DEFAULT_PRIORITY) {
                    out.attributeInt(null, ATT_PRIORITY, r.priority);
                }
@@ -616,6 +636,15 @@ public class PreferencesHelper implements RankingConfig {
                out.endTag(null, TAG_PACKAGE);
            }
        }
        // Some apps have permissions set but don't have expanded notification settings
        if (!notifPermissions.isEmpty()) {
            for (Pair<Integer, String> app : notifPermissions.keySet()) {
                out.startTag(null, TAG_PACKAGE);
                out.attribute(null, ATT_NAME, app.second);
                out.attributeInt(null, ATT_IMPORTANCE,
                        notifPermissions.get(app) ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
                out.endTag(null, TAG_PACKAGE);
            }
        }
        out.endTag(null, TAG_RANKING);
    }
@@ -1904,7 +1933,7 @@ public class PreferencesHelper implements RankingConfig {
    public void dump(PrintWriter pw, String prefix,
            @NonNull NotificationManagerService.DumpFilter filter) {
        pw.print(prefix);
        pw.println("per-package config:");
        pw.println("per-package config version: " + XML_VERSION);

        pw.println("PackagePreferences:");
        synchronized (mPackagePreferences) {
@@ -1924,7 +1953,7 @@ public class PreferencesHelper implements RankingConfig {
                mRestoredWithoutUids);
    }

    private static void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
    private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
            @NonNull NotificationManagerService.DumpFilter filter,
            ArrayMap<String, PackagePreferences> packagePreferences) {
        final int N = packagePreferences.size();
@@ -1937,7 +1966,7 @@ public class PreferencesHelper implements RankingConfig {
                pw.print(" (");
                pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
                pw.print(')');
                if (r.importance != DEFAULT_IMPORTANCE) {
                if (!mPermissionHelper.isMigrationEnabled() && r.importance != DEFAULT_IMPORTANCE) {
                    pw.print(" importance=");
                    pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
                }
+59 −6
Original line number Diff line number Diff line
@@ -38,10 +38,11 @@ import android.Manifest;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.permission.IPermissionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.util.Slog;

import androidx.test.runner.AndroidJUnit4;

@@ -50,6 +51,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

import org.junit.Before;
@@ -66,6 +68,7 @@ import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -107,6 +110,9 @@ public class PermissionHelperTest extends UiServiceTestCase {
                        args.add(false);
                    } else if (type.getTypeName().equals("int")) {
                        args.add(1);
                    } else if (type.getTypeName().equals(
                            "com.android.server.notification.PermissionHelper$PackagePermission")) {
                        args.add(null);
                    }
                }
                try {
@@ -158,15 +164,16 @@ public class PermissionHelperTest extends UiServiceTestCase {
        aiSecond.uid = 2;
        second.applicationInfo = aiSecond;

        Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
        Set<Pair<Integer, String>> expected =
                ImmutableSet.of(new Pair(1, "first"), new Pair(2, "second"));

        ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
                ImmutableList.of(notThis, none, first, second));
        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos);

        Map<Integer, String> actual = mPermissionHelper.getAppsRequestingPermission(0);
        Set<Pair<Integer, String>> actual = mPermissionHelper.getAppsRequestingPermission(0);

        assertThat(actual).containsExactlyEntriesIn(expected);
        assertThat(actual).containsExactlyElementsIn(expected);
    }

    @Test
@@ -202,10 +209,11 @@ public class PermissionHelperTest extends UiServiceTestCase {
                eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
                .thenReturn(infos);

        Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
        Set<Pair<Integer, String>> expected =
                ImmutableSet.of(new Pair(1, "first"), new Pair(2, "second"));

        assertThat(mPermissionHelper.getAppsGrantedPermission(userId))
                .containsExactlyEntriesIn(expected);
                .containsExactlyElementsIn(expected);
    }

    @Test
@@ -268,4 +276,49 @@ public class PermissionHelperTest extends UiServiceTestCase {

        assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue();
    }

    @Test
    public void testGetNotificationPermissionValues() throws Exception {
        int userId = 1;
        PackageInfo first = new PackageInfo();
        first.packageName = "first";
        first.requestedPermissions =
                new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS};
        ApplicationInfo aiFirst = new ApplicationInfo();
        aiFirst.uid = 1;
        first.applicationInfo = aiFirst;

        PackageInfo second = new PackageInfo();
        second.packageName = "second";
        second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
        ApplicationInfo aiSecond = new ApplicationInfo();
        aiSecond.uid = 2;
        second.applicationInfo = aiSecond;

        PackageInfo third = new PackageInfo();
        third.packageName = "third";
        third.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
        ApplicationInfo aiThird = new ApplicationInfo();
        aiThird.uid = 3;
        third.applicationInfo = aiThird;

        ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
                ImmutableList.of(first, second));
        when(mPackageManager.getPackagesHoldingPermissions(
                eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
                .thenReturn(infos);
        ParceledListSlice<PackageInfo> requesting = new ParceledListSlice<>(
                ImmutableList.of(first, second, third));
        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt()))
                .thenReturn(requesting);

        Map<Pair<Integer, String>, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true,
                new Pair(2, "second"), true,
                new Pair(3, "third"), false);

        Map<Pair<Integer, String>, Boolean> actual =
                mPermissionHelper.getNotificationPermissionValues(userId);

        assertThat(actual).containsExactlyEntriesIn(expected);
    }
}
+394 −0

File changed.

Preview size limit exceeded, changes collapsed.