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

Commit 2e77caeb authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Move permission restore code into permission controller.

This still follows the logic from the code that used to be in system
server.

Test: Built
Change-Id: Ie1f4f7cd17ba59e18b15886d9c3ed038f14032a6
parent affe9859
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -84,4 +84,10 @@ public class Constants {
     * role holder for a role.
     * role holder for a role.
     */
     */
    public static final String IS_NONE_ROLE_HOLDER_SELECTED_KEY = "is_none_role_holder_selected:";
    public static final String IS_NONE_ROLE_HOLDER_SELECTED_KEY = "is_none_role_holder_selected:";

    /**
     * Name of file containing the permissions that should be restored, but have not been restored
     * yet.
     */
    public static final String DELAYED_RESTORE_PERMISSIONS_FILE = "delayed_restore_permissions.xml";
}
}
+26 −21
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;


import android.app.ActivityManager;
import android.app.ActivityManager;
@@ -889,13 +890,13 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
                continue;
                continue;
            }
            }


            if (mAppSupportsRuntimePermissions) {
            // Do not touch permissions fixed by the system.
            // Do not touch permissions fixed by the system.
            if (permission.isSystemFixed()) {
            if (permission.isSystemFixed()) {
                wasAllRevoked = false;
                wasAllRevoked = false;
                break;
                break;
            }
            }


            if (mAppSupportsRuntimePermissions) {
                // Revoke the permission if needed.
                // Revoke the permission if needed.
                if (permission.isGranted()) {
                if (permission.isGranted()) {
                    permission.setGranted(false);
                    permission.setGranted(false);
@@ -1124,6 +1125,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
        for (int i = 0; i < numPermissions; i++) {
        for (int i = 0; i < numPermissions; i++) {
            Permission permission = mPermissions.valueAt(i);
            Permission permission = mPermissions.valueAt(i);


            if (!permission.isSystemFixed()) {
                if (permission.isGranted()) {
                if (permission.isGranted()) {
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                            permission.getName(), mUserHandle);
@@ -1131,6 +1133,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
                    mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
                    mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                            permission.getName(), mUserHandle);
                }
                }
            }


            int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
            int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
                    | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
                    | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
@@ -1150,6 +1153,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
                    flags, mUserHandle);
                    flags, mUserHandle);


            if (permission.affectsAppOp()) {
            if (permission.affectsAppOp()) {
                if (!permission.isSystemFixed()) {
                    if (permission.isAppOpAllowed()) {
                    if (permission.isAppOpAllowed()) {
                        allowAppOp(permission, mPackageInfo.applicationInfo.uid);
                        allowAppOp(permission, mPackageInfo.applicationInfo.uid);
                    } else {
                    } else {
@@ -1157,10 +1161,11 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
                    }
                    }


                    // Enabling/Disabling an app op may put the app in a situation in which it has a
                    // Enabling/Disabling an app op may put the app in a situation in which it has a
                // handle to state it shouldn't have, so we have to kill the app. This matches the
                    // handle to state it shouldn't have, so we have to kill the app. This matches
                // revoke runtime permission behavior.
                    // the revoke runtime permission behavior.
                    shouldKillApp = true;
                    shouldKillApp = true;
                }
                }
            }


            switch (permission.getName()) {
            switch (permission.getName()) {
                case READ_MEDIA_AUDIO:
                case READ_MEDIA_AUDIO:
+332 −1
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.packageinstaller.permission.service;
package com.android.packageinstaller.permission.service;


import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
@@ -23,22 +24,39 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.util.Xml.newSerializer;

import static com.android.packageinstaller.Constants.DELAYED_RESTORE_PERMISSIONS_FILE;

import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;

import static java.nio.charset.StandardCharsets.UTF_8;


import android.content.Context;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.os.UserHandle;
import android.util.Log;
import android.util.Xml;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;


import com.android.packageinstaller.Constants;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.model.Permission;
import com.android.packageinstaller.permission.model.Permission;


import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlSerializer;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;


@@ -46,6 +64,8 @@ import java.util.List;
 * Helper for creating and restoring permission backups.
 * Helper for creating and restoring permission backups.
 */
 */
public class BackupHelper {
public class BackupHelper {
    private static final String LOG_TAG = BackupHelper.class.getSimpleName();

    private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
    private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
    private static final String TAG_ALL_GRANTS = "rt-grants";
    private static final String TAG_ALL_GRANTS = "rt-grants";


@@ -69,6 +89,9 @@ public class BackupHelper {
            | FLAG_PERMISSION_USER_FIXED
            | FLAG_PERMISSION_USER_FIXED
            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;


    /** Make sure only one user can change the delayed permissions at a time */
    private static final Object sLock = new Object();

    private final Context mContext;
    private final Context mContext;


    /**
    /**
@@ -85,6 +108,134 @@ public class BackupHelper {
        }
        }
    }
    }


    /**
     * Forward parser and skip everything up to the end of the current tag.
     *
     * @param parser The parser to forward
     */
    private static void skipToEndOfTag(@NonNull XmlPullParser parser)
            throws IOException, XmlPullParserException {
        int numOpenTags = 1;
        while (numOpenTags > 0) {
            switch (parser.next()) {
                case START_TAG:
                    numOpenTags++;
                    break;
                case END_TAG:
                    numOpenTags--;
                    break;
                default:
                    // ignore
            }
        }
    }

    /**
     * Forward parser to a given direct sub-tag.
     *
     * @param parser The parser to forward
     * @param tag The tag to search for
     */
    private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag)
            throws IOException, XmlPullParserException {
        int type;
        do {
            type = parser.next();

            switch (type) {
                case START_TAG:
                    if (!parser.getName().equals(tag)) {
                        skipToEndOfTag(parser);
                    }

                    return;
            }
        } while (type != END_DOCUMENT);
    }

    /**
     * Read a XML file and return the packages stored in it.
     *
     * @param parser The file to read
     *
     * @return The packages in this file
     */
    private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser)
            throws IOException, XmlPullParserException {
        ArrayList<BackupPackageState> pkgStates = new ArrayList<>();

        skipToTag(parser, TAG_PERMISSION_BACKUP);
        skipToTag(parser, TAG_ALL_GRANTS);

        if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) {
            throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > "
                    + TAG_ALL_GRANTS);
        }

        // Read packages to restore from xml
        int type;
        do {
            type = parser.next();

            switch (type) {
                case START_TAG:
                    switch (parser.getName()) {
                        case TAG_GRANT:
                            try {
                                pkgStates.add(BackupPackageState.parseFromXml(parser));
                            } catch (XmlPullParserException e) {
                                Log.e(LOG_TAG, "Could not parse permissions ", e);
                                skipToEndOfTag(parser);
                            }
                            break;
                        default:
                            // ignore tag
                            Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
                                    + " during restore");
                            skipToEndOfTag(parser);
                    }
            }
        } while (type != END_TAG);

        return pkgStates;
    }

    /**
     * Try to restore the permission state from XML.
     *
     * <p>If some apps could not be restored, the leftover apps are written to
     * {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE}.
     *
     * @param parser The xml to read
     */
    void restoreState(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException {
        ArrayList<BackupPackageState> pkgStates = parseFromXml(parser);

        ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>();
        int numPkgStates = pkgStates.size();
        if (numPkgStates > 0) {
            // Try to restore packages
            for (int i = 0; i < numPkgStates; i++) {
                BackupPackageState pkgState = pkgStates.get(i);

                PackageInfo pkgInfo;
                try {
                    pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName,
                            GET_PERMISSIONS);
                } catch (PackageManager.NameNotFoundException ignored) {
                    packagesToRestoreLater.add(pkgState);
                    continue;
                }

                pkgState.restore(mContext, pkgInfo);
            }
        }

        synchronized (sLock) {
            writeDelayedStorePkgsLocked(packagesToRestoreLater);
        }
    }

    /**
    /**
     * Write a xml file for the given packages.
     * Write a xml file for the given packages.
     *
     *
@@ -113,6 +264,26 @@ public class BackupHelper {
        serializer.endDocument();
        serializer.endDocument();
    }
    }


    /**
     * Update the {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE} to contain the
     * {@code packagesToRestoreLater}.
     *
     * @param packagesToRestoreLater The new pkgs in the delayed restore file
     */
    private void writeDelayedStorePkgsLocked(
            @NonNull ArrayList<BackupPackageState> packagesToRestoreLater) {
        try (OutputStream delayedRestoreData = mContext.openFileOutput(
                DELAYED_RESTORE_PERMISSIONS_FILE, MODE_PRIVATE)) {
            XmlSerializer serializer = newSerializer();
            serializer.setOutput(delayedRestoreData, UTF_8.name());

            writePkgsAsXml(serializer, packagesToRestoreLater);
            serializer.flush();
        } catch (IOException e) {
            Log.e(LOG_TAG, "Could not remember which packages still need to be restored", e);
        }
    }

    /**
    /**
     * Write the state of all packages as XML.
     * Write the state of all packages as XML.
     *
     *
@@ -136,6 +307,55 @@ public class BackupHelper {
        writePkgsAsXml(serializer, backupPkgs);
        writePkgsAsXml(serializer, backupPkgs);
    }
    }


    /**
     * Restore delayed permission state for a package (if delayed during {@link #restoreState}).
     *
     * @param packageName The package to be restored
     *
     * @return {@code true} if there is still delayed backup left
     */
    boolean restoreDelayedState(@NonNull String packageName) {
        synchronized (sLock) {
            ArrayList<BackupPackageState> packagesToRestoreLater;

            try (FileInputStream delayedRestoreData =
                         mContext.openFileInput(DELAYED_RESTORE_PERMISSIONS_FILE)) {
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(delayedRestoreData, UTF_8.name());

                packagesToRestoreLater = parseFromXml(parser);
            } catch (IOException | XmlPullParserException e) {
                Log.e(LOG_TAG, "Could not parse delayed permissions", e);
                return false;
            }

            PackageInfo pkgInfo = null;
            try {
                pkgInfo = mContext.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e);
            }

            if (pkgInfo != null) {
                int numPkgs = packagesToRestoreLater.size();
                for (int i = 0; i < numPkgs; i++) {
                    BackupPackageState pkgState = packagesToRestoreLater.get(i);

                    if (pkgState.mPackageName.equals(packageName)) {
                        pkgState.restore(mContext, pkgInfo);
                        packagesToRestoreLater.remove(i);

                        writeDelayedStorePkgsLocked(packagesToRestoreLater);

                        break;
                    }
                }
            }

            return packagesToRestoreLater.size() > 0;
        }
    }

    /**
    /**
     * State that needs to be backed up for a permission.
     * State that needs to be backed up for a permission.
     */
     */
@@ -155,6 +375,28 @@ public class BackupHelper {
            mShouldRevokeOnUpgrade = isRevokeOnUpgrade;
            mShouldRevokeOnUpgrade = isRevokeOnUpgrade;
        }
        }


        /**
         * Parse a package state from XML.
         *
         * @param parser The data to read
         *
         * @return The state
         */
        static @NonNull BackupPermissionState parseFromXml(@NonNull XmlPullParser parser)
                throws XmlPullParserException {
            String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
            if (permName == null) {
                throw new XmlPullParserException("Found " + TAG_PERMISSION + " without "
                        + ATTR_PERMISSION_NAME);
            }

            return new BackupPermissionState(permName,
                    "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)),
                    "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)),
                    "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)),
                    "true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE)));
        }

        /**
        /**
         * Get the state of a permission to back up.
         * Get the state of a permission to back up.
         *
         *
@@ -227,13 +469,37 @@ public class BackupHelper {


            serializer.endTag(null, TAG_PERMISSION);
            serializer.endTag(null, TAG_PERMISSION);
        }
        }

        /**
         * Restore this permission state.
         *
         * @param appPerms The {@link AppPermissions} to restore the state to
         */
        void restore(@NonNull AppPermissions appPerms) {
            AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName);
            if (group == null) {
                Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in "
                        + appPerms.getPackageInfo().packageName);
                return;
            }

            if (mIsGranted) {
                group.grantRuntimePermissions(/* is overridden below */false,
                        new String[]{mPermissionName});
            }

            Permission perm = group.getPermission(mPermissionName);
            perm.setUserSet(mIsUserSet);
            perm.setUserFixed(mIsUserFixed);
            perm.setRevokeOnUpgrade(mShouldRevokeOnUpgrade);
        }
    }
    }


    /**
    /**
     * State that needs to be backed up for a package.
     * State that needs to be backed up for a package.
     */
     */
    private static class BackupPackageState {
    private static class BackupPackageState {
        private final @NonNull String mPackageName;
        final @NonNull String mPackageName;
        private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;
        private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;


        private BackupPackageState(@NonNull String packageName,
        private BackupPackageState(@NonNull String packageName,
@@ -242,6 +508,54 @@ public class BackupHelper {
            mPermissionsToRestore = permissionsToRestore;
            mPermissionsToRestore = permissionsToRestore;
        }
        }


        /**
         * Parse a package state from XML.
         *
         * @param parser The data to read
         *
         * @return The state
         */
        static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser)
                throws IOException, XmlPullParserException {
            String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
            if (packageName == null) {
                throw new XmlPullParserException("Found " + TAG_GRANT + " without "
                        + ATTR_PACKAGE_NAME);
            }

            ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();

            while (true) {
                switch (parser.next()) {
                    case START_TAG:
                        switch (parser.getName()) {
                            case TAG_PERMISSION:
                                try {
                                    permissionsToRestore.add(
                                            BackupPermissionState.parseFromXml(parser));
                                } catch (XmlPullParserException e) {
                                    Log.e(LOG_TAG, "Could not parse permission for "
                                            + packageName, e);
                                    skipToEndOfTag(parser);
                                }
                                break;
                            default:
                                // ignore tag
                                Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
                                        + " while restoring " + packageName);
                                skipToEndOfTag(parser);
                        }

                        break;
                    case END_TAG:
                        return new BackupPackageState(packageName, permissionsToRestore);
                    case END_DOCUMENT:
                        throw new XmlPullParserException("Could not parse state for "
                                + packageName);
                }
            }
        }

        /**
        /**
         * Get the state of a package to back up.
         * Get the state of a package to back up.
         *
         *
@@ -299,5 +613,22 @@ public class BackupHelper {


            serializer.endTag(null, TAG_GRANT);
            serializer.endTag(null, TAG_GRANT);
        }
        }

        /**
         * Restore this package state.
         *
         * @param context A context to use
         * @param pkgInfo The package to restore.
         */
        void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) {
            AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null);

            int numPerms = mPermissionsToRestore.size();
            for (int i = 0; i < numPerms; i++) {
                mPermissionsToRestore.get(i).restore(appPerms);
            }

            appPerms.persistChanges();
        }
    }
    }
}
}
+17 −3
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ import android.permission.RuntimePermissionUsageInfo;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;
import android.util.Xml;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;
@@ -53,10 +54,12 @@ import com.android.packageinstaller.permission.model.PermissionUsages;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.packageinstaller.role.service.PermissionControllerServiceImplRoleMixin;
import com.android.packageinstaller.role.service.PermissionControllerServiceImplRoleMixin;


import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlSerializer;


import java.io.InputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.List;
import java.util.List;
@@ -306,14 +309,25 @@ public final class PermissionControllerServiceImpl extends PermissionControllerS
    @Override
    @Override
    public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
    public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
            @NonNull InputStream backup) {
            @NonNull InputStream backup) {
        // TODO: Implement
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(backup, StandardCharsets.UTF_8.name());

            new BackupHelper(this, user).restoreState(parser);
        } catch (Exception e) {
            Log.e(LOG_TAG, "Exception restoring permissions: " + e.getMessage());
        }
    }
    }


    @Override
    @Override
    public boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
    public boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
            @NonNull UserHandle user) {
            @NonNull UserHandle user) {
        // TODO: Implement
        try {
        return true;
            return new BackupHelper(this, user).restoreDelayedState(packageName);
        } catch (Exception e) {
            Log.e(LOG_TAG, "Exception restoring delayed permissions: " + e.getMessage());
            return false;
        }
    }
    }


    @Override
    @Override
+8 −0
Original line number Original line Diff line number Diff line
@@ -10,6 +10,14 @@
                    "include-filter": "android.permission.cts.PermissionControllerTest"
                    "include-filter": "android.permission.cts.PermissionControllerTest"
                }
                }
            ]
            ]
        },
        {
            "name": "CtsBackupTestCases",
            "options": [
                {
                    "include-filter": "android.backup.cts.PermissionTest"
                }
            ]
        }
        }
    ]
    ]
}
}