All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.notification.PermissionHelper Maven / Gradle / Ivy

/*
 * 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 static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.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.Binder;
import android.os.RemoteException;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;

import com.android.internal.util.ArrayUtils;
import com.android.server.pm.permission.PermissionManagerServiceInternal;

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

/**
 * NotificationManagerService helper for querying/setting the app-level notification permission
 */
public final class PermissionHelper {
    private static final String TAG = "PermissionHelper";

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

    private final PermissionManagerServiceInternal mPmi;
    private final IPackageManager mPackageManager;
    private final IPermissionManager mPermManager;

    public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
            IPermissionManager permManager) {
        mPmi = pmi;
        mPackageManager = packageManager;
        mPermManager = permManager;
    }

    /**
     * Returns whether the given uid holds the notification permission. Must not be called
     * with a lock held.
     */
    public boolean hasPermission(int uid) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

    /**
     * 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
     */
    Set> getAppsRequestingPermission(int userId) {
        Set> requested = new HashSet<>();
        List pkgs = getInstalledPackages(userId);
        for (PackageInfo pi : pkgs) {
            // when data was stored in PreferencesHelper, we only had data for apps that
            // had ever registered an intent to send a notification. To match that behavior,
            // filter the app list to apps that have requested the notification permission.
            if (pi.requestedPermissions == null) {
                continue;
            }
            for (String perm : pi.requestedPermissions) {
                if (NOTIFICATION_PERMISSION.equals(perm)) {
                    requested.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
                    break;
                }
            }
        }
        return requested;
    }

    private List getInstalledPackages(int userId) {
        ParceledListSlice parceledList = null;
        try {
            parceledList = mPackageManager.getInstalledPackages(GET_PERMISSIONS, userId);
        } catch (RemoteException e) {
            Slog.d(TAG, "Could not reach system server", e);
        }
        if (parceledList == null) {
            return Collections.emptyList();
        }
        return parceledList.getList();
    }

    /**
     * Returns a list of apps that hold the notification permission. Must not be called
     * with a lock held. Format: uid, packageName.
     */
    Set> getAppsGrantedPermission(int userId) {
        Set> granted = new HashSet<>();
        ParceledListSlice parceledList = null;
        try {
            parceledList = mPackageManager.getPackagesHoldingPermissions(
                    new String[] {NOTIFICATION_PERMISSION}, 0, userId);
        } catch (RemoteException e) {
            Slog.e(TAG, "Could not reach system server", e);
        }
        if (parceledList == null) {
            return granted;
        }
        for (PackageInfo pi : parceledList.getList()) {
            granted.add(new Pair<>(pi.applicationInfo.uid, pi.packageName));
        }
        return granted;
    }

    // Key: (uid, package name); Value: (granted, user set)
    public @NonNull
            ArrayMap, Pair>
                    getNotificationPermissionValues(int userId) {
        ArrayMap, Pair> notifPermissions = new ArrayMap<>();
        Set> allRequestingUids = getAppsRequestingPermission(userId);
        Set> allApprovedUids = getAppsGrantedPermission(userId);
        for (Pair pair : allRequestingUids) {
            notifPermissions.put(pair, new Pair(allApprovedUids.contains(pair),
                    isPermissionUserSet(pair.second /* package name */, userId)));
        }
        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
     * can prevent the user from seeing the in app permission dialog. Must not be called
     * with a lock held.
     */
    public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
            boolean userSet) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            // Do not change the permission if the package doesn't request it, do not change fixed
            // permissions, and do not change non-user set permissions that are granted by default,
            // or granted by role.
            if (!packageRequestsNotificationPermission(packageName, userId)
                    || isPermissionFixed(packageName, userId)
                    || (isPermissionGrantedByDefaultOrRole(packageName, userId) && !userSet)) {
                return;
            }

            boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION,
                    userId) != PackageManager.PERMISSION_DENIED;
            if (grant && !currentlyGranted) {
                mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
            } else if (!grant && currentlyGranted) {
                mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
                        userId, TAG);
            }
            if (userSet) {
                mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
                        FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, userId);
            } else {
                mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
                        0, FLAG_PERMISSION_USER_SET, true, userId);
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Could not reach system server", e);
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

    /**
     * Set the notification permission state upon phone version upgrade from S- to T+, or upon
     * restoring a pre-T backup on a T+ device
     */
    public void setNotificationPermission(PackagePermission pkgPerm) {
        if (pkgPerm == null || pkgPerm.packageName == null) {
            return;
        }
        if (!isPermissionFixed(pkgPerm.packageName, pkgPerm.userId)) {
            setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
                    true /* userSet always true on upgrade */);
        }
    }

    public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            try {
                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
                        userId);
                return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
                        || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
            } catch (RemoteException e) {
                Slog.e(TAG, "Could not reach system server", e);
            }
            return false;
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

    boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            try {
                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
                        userId);
                return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
                        | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
            } catch (RemoteException e) {
                Slog.e(TAG, "Could not reach system server", e);
            }
            return false;
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

    boolean isPermissionGrantedByDefaultOrRole(String packageName, @UserIdInt int userId) {
        final long callingId = Binder.clearCallingIdentity();
        try {
            try {
                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
                        userId);
                return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
                        | PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
            } catch (RemoteException e) {
                Slog.e(TAG, "Could not reach system server", e);
            }
            return false;
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }

    private boolean packageRequestsNotificationPermission(String packageName,
            @UserIdInt int userId) {
        try {
            String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS,
                    userId).requestedPermissions;
            return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION);
        } catch (RemoteException e) {
            Slog.e(TAG, "Could not reach system server", e);
        }
        return false;
    }

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

        public PackagePermission(String pkg, int userId, boolean granted, boolean userSet) {
            this.packageName = pkg;
            this.userId = userId;
            this.granted = granted;
            this.userModifiedSettings = 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 && userModifiedSettings
                    == that.userModifiedSettings
                    && Objects.equals(packageName, that.packageName);
        }

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

        @Override
        public String toString() {
            return "PackagePermission{" +
                    "packageName='" + packageName + '\'' +
                    ", userId=" + userId +
                    ", granted=" + granted +
                    ", userSet=" + userModifiedSettings +
                    '}';
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy