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

Commit bc0ad10c authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Split device owner config files"

parents 8522bf81 39e784dd
Loading
Loading
Loading
Loading
+274 −114
Original line number Original line Diff line number Diff line
@@ -19,12 +19,14 @@ package com.android.server.devicepolicy;
import android.app.AppGlobals;
import android.app.AppGlobals;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.SystemUpdatePolicy;
import android.content.ComponentName;
import android.content.ComponentName;
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.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.Environment;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserManager;
import android.util.AtomicFile;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Slog;
import android.util.Xml;
import android.util.Xml;
@@ -40,13 +42,15 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import java.util.Set;


import libcore.io.IoUtils;

/**
/**
 * Stores and restores state for the Device and Profile owners. By definition there can be
 * Stores and restores state for the Device and Profile owners. By definition there can be
 * only one device owner, but there may be a profile owner for each user.
 * only one device owner, but there may be a profile owner for each user.
@@ -54,21 +58,27 @@ import java.util.Set;
class DeviceOwner {
class DeviceOwner {
    private static final String TAG = "DevicePolicyManagerService";
    private static final String TAG = "DevicePolicyManagerService";


    private static final String DEVICE_OWNER_XML = "device_owner.xml";
    private static final String DEVICE_OWNER_XML_LEGACY = "device_owner.xml";

    private static final String DEVICE_OWNER_XML = "device_owner_2.xml";

    private static final String PROFILE_OWNER_XML = "profile_owner.xml";

    private static final String TAG_ROOT = "root";

    private static final String TAG_DEVICE_OWNER = "device-owner";
    private static final String TAG_DEVICE_OWNER = "device-owner";
    private static final String TAG_DEVICE_INITIALIZER = "device-initializer";
    private static final String TAG_DEVICE_INITIALIZER = "device-initializer";
    private static final String TAG_PROFILE_OWNER = "profile-owner";
    private static final String TAG_PROFILE_OWNER = "profile-owner";

    private static final String ATTR_NAME = "name";
    private static final String ATTR_NAME = "name";
    private static final String ATTR_PACKAGE = "package";
    private static final String ATTR_PACKAGE = "package";
    private static final String ATTR_COMPONENT_NAME = "component";
    private static final String ATTR_COMPONENT_NAME = "component";
    private static final String ATTR_USERID = "userId";
    private static final String ATTR_USERID = "userId";
    private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";


    private AtomicFile fileForWriting;
    private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";


    // Input/Output streams for testing.
    private final Context mContext;
    private InputStream mInputStreamForTest;
    private final UserManager mUserManager;
    private OutputStream mOutputStreamForTest;


    // Internal state for the device owner package.
    // Internal state for the device owner package.
    private OwnerInfo mDeviceOwner;
    private OwnerInfo mDeviceOwner;
@@ -82,54 +92,39 @@ class DeviceOwner {
    // Local system update policy controllable by device owner.
    // Local system update policy controllable by device owner.
    private SystemUpdatePolicy mSystemUpdatePolicy;
    private SystemUpdatePolicy mSystemUpdatePolicy;


    // Private default constructor.
    public DeviceOwner(Context context) {
    private DeviceOwner() {
        mContext = context;
    }
        mUserManager = UserManager.get(mContext);

    @VisibleForTesting
    DeviceOwner(InputStream in, OutputStream out) {
        mInputStreamForTest = in;
        mOutputStreamForTest = out;
    }
    }


    /**
    /**
     * Loads the device owner state from disk.
     * Load configuration from the disk.
     */
     */
    static DeviceOwner load() {
    void load() {
        DeviceOwner owner = new DeviceOwner();
        synchronized (this) {
        if (new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML).exists()) {
            // First, try to read from the legacy file.
            owner.readOwnerFile();
            final File legacy = getLegacyConfigFileWithTestOverride();
            return owner;

        } else {
            if (readLegacyOwnerFile(legacy)) {
            return null;
                // Legacy file exists, write to new files and remove the legacy one.
                writeDeviceOwner();
                for (int userId : getProfileOwnerKeys()) {
                    writeProfileOwner(userId);
                }
                }
                if (!legacy.delete()) {
                    Slog.e(TAG, "Failed to remove the legacy setting file");
                }
                }

                return;
    /**
     * Creates an instance of the device owner object with the device owner set.
     */
    static DeviceOwner createWithDeviceOwner(String packageName, String ownerName) {
        DeviceOwner owner = new DeviceOwner();
        owner.mDeviceOwner = new OwnerInfo(ownerName, packageName);
        return owner;
            }
            }


    /**
            // No legacy file, read from the new format files.
     * Creates an instance of the device owner object with the device initializer set.
            new DeviceOwnerReadWriter().readFromFileLocked();
     */
    static DeviceOwner createWithDeviceInitializer(ComponentName admin) {
        DeviceOwner owner = new DeviceOwner();
        owner.mDeviceInitializer = new OwnerInfo(null, admin);
        return owner;
    }


    /**
            final List<UserInfo> users = mUserManager.getUsers();        // XXX double check this is the correct profile set.
     * Creates an instance of the device owner object with the profile owner set.
            for (UserInfo ui : users) {
     */
                new ProfileOwnerReadWriter(ui.id).readFromFileLocked();  // XXX double check ID is the right one.
    static DeviceOwner createWithProfileOwner(ComponentName admin, String ownerName, int userId) {
            }
        DeviceOwner owner = new DeviceOwner();
        }
        owner.mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
        return owner;
    }
    }


    String getDeviceOwnerPackageName() {
    String getDeviceOwnerPackageName() {
@@ -234,10 +229,13 @@ class DeviceOwner {
        return false;
        return false;
    }
    }


    @VisibleForTesting
    private boolean readLegacyOwnerFile(File file) {
    void readOwnerFile() {
        if (!file.exists()) {
            // Already migrated or the device has no owners.
            return false;
        }
        try {
        try {
            InputStream input = openRead();
            InputStream input = new AtomicFile(file).openRead();
            XmlPullParser parser = Xml.newPullParser();
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(input, StandardCharsets.UTF_8.name());
            parser.setInput(input, StandardCharsets.UTF_8.name());
            int type;
            int type;
@@ -295,99 +293,212 @@ class DeviceOwner {
                }
                }
            }
            }
            input.close();
            input.close();
        } catch (XmlPullParserException xppe) {
        } catch (XmlPullParserException|IOException e) {
            Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
            Slog.e(TAG, "Error parsing device-owner file", e);
        } catch (IOException ioe) {
        }
            Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
        return true;
    }

    void writeDeviceOwner() {
        synchronized (this) {
            new DeviceOwnerReadWriter().writeToFileLocked();
        }
        }
    }
    }


    @VisibleForTesting
    void writeProfileOwner(int userId) {
    void writeOwnerFile() {
        synchronized (this) {
        synchronized (this) {
            writeOwnerFileLocked();
            new ProfileOwnerReadWriter(userId).writeToFileLocked();
        }
    }

    private abstract static class FileReadWriter {
        private final File mFile;

        protected FileReadWriter(File file) {
            mFile = file;
        }
        }

        abstract boolean shouldWrite();

        void writeToFileLocked() {
            if (!shouldWrite()) {
                // No contents, remove the file.
                if (mFile.exists()) {
                    if (!mFile.delete()) {
                        Slog.e(TAG, "Failed to remove " + mFile.getPath());
                    }
                }
                return;
            }
            }


    private void writeOwnerFileLocked() {
            final AtomicFile f = new AtomicFile(mFile);
            FileOutputStream outputStream = null;
            try {
            try {
            OutputStream outputStream = startWrite();
                outputStream = f.startWrite();
            XmlSerializer out = new FastXmlSerializer();
                final XmlSerializer out = new FastXmlSerializer();
                out.setOutput(outputStream, StandardCharsets.UTF_8.name());
                out.setOutput(outputStream, StandardCharsets.UTF_8.name());

                // Root tag
                out.startDocument(null, true);
                out.startDocument(null, true);
                out.startTag(null, TAG_ROOT);


            // Write device owner tag
                // Actual content
            if (mDeviceOwner != null) {
                writeInner(out);
                out.startTag(null, TAG_DEVICE_OWNER);

                out.attribute(null, ATTR_PACKAGE, mDeviceOwner.packageName);
                // Close root
                if (mDeviceOwner.name != null) {
                out.endTag(null, TAG_ROOT);
                    out.attribute(null, ATTR_NAME, mDeviceOwner.name);
                out.endDocument();
                out.flush();

                // Commit the content.
                f.finishWrite(outputStream);
                outputStream = null;

            } catch (IOException e) {
                Slog.e(TAG, "Exception when writing", e);
                if (outputStream != null) {
                    f.failWrite(outputStream);
                }
            }
            }
                out.endTag(null, TAG_DEVICE_OWNER);
        }
        }


            // Write device initializer tag
        void readFromFileLocked() {
            if (mDeviceInitializer != null) {
            if (!mFile.exists()) {
                out.startTag(null, TAG_DEVICE_INITIALIZER);
                return;
                out.attribute(null, ATTR_PACKAGE, mDeviceInitializer.packageName);
            }
                if (mDeviceInitializer.admin != null) {
            final AtomicFile f = new AtomicFile(mFile);
                    out.attribute(
            InputStream input = null;
                            null, ATTR_COMPONENT_NAME, mDeviceInitializer.admin.flattenToString());
            try {
                input = f.openRead();
                final XmlPullParser parser = Xml.newPullParser();
                parser.setInput(input, StandardCharsets.UTF_8.name());

                int type;
                int depth = 0;
                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                    switch (type) {
                        case XmlPullParser.START_TAG:
                            depth++;
                            break;
                        case XmlPullParser.END_TAG:
                            depth--;
                            // fallthrough
                        default:
                            continue;
                    }
                    }
                out.endTag(null, TAG_DEVICE_INITIALIZER);
                    // Check the root tag
                    final String tag = parser.getName();
                    if (depth == 1) {
                        if (!TAG_ROOT.equals(tag)) {
                            Slog.e(TAG, "Invalid root tag: " + tag);
                            return;
                        }
                        }
                    }
                    // readInner() will only see START_TAG at depth >= 2.
                    if (!readInner(parser, depth, tag)) {
                        return; // Error
                    }
                }
            } catch (XmlPullParserException | IOException e) {
                Slog.e(TAG, "Error parsing device-owner file", e);
            } finally {
                IoUtils.closeQuietly(input);
            }
        }

        abstract void writeInner(XmlSerializer out) throws IOException;

        abstract boolean readInner(XmlPullParser parser, int depth, String tag);


            // Write profile owner tags
            if (mProfileOwners.size() > 0) {
                for (HashMap.Entry<Integer, OwnerInfo> owner : mProfileOwners.entrySet()) {
                    out.startTag(null, TAG_PROFILE_OWNER);
                    OwnerInfo ownerInfo = owner.getValue();
                    out.attribute(null, ATTR_PACKAGE, ownerInfo.packageName);
                    out.attribute(null, ATTR_NAME, ownerInfo.name);
                    out.attribute(null, ATTR_USERID, Integer.toString(owner.getKey()));
                    if (ownerInfo.admin != null) {
                        out.attribute(null, ATTR_COMPONENT_NAME, ownerInfo.admin.flattenToString());
    }
    }
                    out.endTag(null, TAG_PROFILE_OWNER);

    private class DeviceOwnerReadWriter extends FileReadWriter {

        protected DeviceOwnerReadWriter() {
            super(getDeviceOwnerFileWithTestOverride());
        }
        }

        @Override
        boolean shouldWrite() {
            return (mDeviceOwner != null) || (mDeviceInitializer != null)
                    || (mSystemUpdatePolicy != null);
        }
        }


            // Write system update policy tag
        @Override
        void writeInner(XmlSerializer out) throws IOException {
            if (mDeviceOwner != null) {
                mDeviceOwner.writeToXml(out, TAG_DEVICE_OWNER);
            }
            if (mDeviceInitializer != null) {
                mDeviceInitializer.writeToXml(out, TAG_DEVICE_INITIALIZER);
            }
            if (mSystemUpdatePolicy != null) {
            if (mSystemUpdatePolicy != null) {
                out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
                out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
                mSystemUpdatePolicy.saveToXml(out);
                mSystemUpdatePolicy.saveToXml(out);
                out.endTag(null, TAG_SYSTEM_UPDATE_POLICY);
                out.endTag(null, TAG_SYSTEM_UPDATE_POLICY);
            }
            }
            out.endDocument();
            out.flush();
            finishWrite(outputStream);
        } catch (IOException ioe) {
            Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
        }
        }

        @Override
        boolean readInner(XmlPullParser parser, int depth, String tag) {
            if (depth > 2) {
                return true; // Ignore
            }
            }
            switch (tag) {
                case TAG_DEVICE_OWNER:
                    mDeviceOwner = OwnerInfo.readFromXml(parser);
                    break;
                case TAG_DEVICE_INITIALIZER:
                    mDeviceInitializer = OwnerInfo.readFromXml(parser);
                    break;
                case TAG_SYSTEM_UPDATE_POLICY:
                    mSystemUpdatePolicy = SystemUpdatePolicy.restoreFromXml(parser);
                    break;
                default:
                    Slog.e(TAG, "Unexpected tag: " + tag);
                    return false;


    private InputStream openRead() throws IOException {
        if (mInputStreamForTest != null) {
            return mInputStreamForTest;
            }
            }
            return true;
        }
    }

    private class ProfileOwnerReadWriter extends FileReadWriter {
        private final int mUserId;


        return new AtomicFile(new File(Environment.getSystemSecureDirectory(),
        ProfileOwnerReadWriter(int userId) {
                DEVICE_OWNER_XML)).openRead();
            super(getProfileOwnerFileWithTestOverride(userId));
            mUserId = userId;
        }
        }


    private OutputStream startWrite() throws IOException {
        @Override
        if (mOutputStreamForTest != null) {
        boolean shouldWrite() {
            return mOutputStreamForTest;
            return mProfileOwners.get(mUserId) != null;
        }
        }


        fileForWriting = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
        @Override
                DEVICE_OWNER_XML));
        void writeInner(XmlSerializer out) throws IOException {
        return fileForWriting.startWrite();
            final OwnerInfo profileOwner = mProfileOwners.get(mUserId);
            if (profileOwner != null) {
                profileOwner.writeToXml(out, TAG_PROFILE_OWNER);
            }
        }
        }


    private void finishWrite(OutputStream stream) {
        @Override
        if (fileForWriting != null) {
        boolean readInner(XmlPullParser parser, int depth, String tag) {
            fileForWriting.finishWrite((FileOutputStream) stream);
            if (depth > 2) {
                return true; // Ignore
            }
            switch (tag) {
                case TAG_PROFILE_OWNER:
                    mProfileOwners.put(mUserId, OwnerInfo.readFromXml(parser));
                    break;
                default:
                    Slog.e(TAG, "Unexpected tag: " + tag);
                    return false;

            }
            return true;
        }
        }
    }
    }


@@ -407,9 +518,46 @@ class DeviceOwner {
            this.admin = admin;
            this.admin = admin;
            this.packageName = admin.getPackageName();
            this.packageName = admin.getPackageName();
        }
        }

        public void writeToXml(XmlSerializer out, String tag) throws IOException {
            out.startTag(null, tag);
            out.attribute(null, ATTR_PACKAGE, packageName);
            if (name != null) {
                out.attribute(null, ATTR_NAME, name);
            }
            if (admin != null) {
                out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
            }
            out.endTag(null, tag);
        }

        public static OwnerInfo readFromXml(XmlPullParser parser) {
            final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
            final String name = parser.getAttributeValue(null, ATTR_NAME);
            final String componentName =
                    parser.getAttributeValue(null, ATTR_COMPONENT_NAME);

            // Has component name?  If so, return [name, component]
            if (componentName != null) {
                final ComponentName admin = ComponentName.unflattenFromString(componentName);
                if (admin != null) {
                    return new OwnerInfo(name, admin);
                } else {
                    // This shouldn't happen but switch from package name -> component name
                    // might have written bad device owner files. b/17652534
                    Slog.e(TAG, "Error parsing device-owner file. Bad component name " +
                            componentName);
                }
            }

            // Else, build with [name, package]
            return new OwnerInfo(name, packageName);
        }

        public void dump(String prefix, PrintWriter pw) {
        public void dump(String prefix, PrintWriter pw) {
            pw.println(prefix + "admin=" + admin);
            pw.println(prefix + "admin=" + admin);
            pw.println(prefix + "name=" + name);
            pw.println(prefix + "name=" + name);
            pw.println(prefix + "package=" + packageName);
            pw.println();
            pw.println();
        }
        }
    }
    }
@@ -426,4 +574,16 @@ class DeviceOwner {
            }
            }
        }
        }
    }
    }

    File getLegacyConfigFileWithTestOverride() {
        return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML_LEGACY);
    }

    File getDeviceOwnerFileWithTestOverride() {
        return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML);
    }

    File getProfileOwnerFileWithTestOverride(int userId) {
        return new File(Environment.getUserSystemDirectory(userId), PROFILE_OWNER_XML);
    }
}
}
+42 −78

File changed.

Preview size limit exceeded, changes collapsed.

+26 −59
Original line number Original line Diff line number Diff line
@@ -17,11 +17,13 @@
package com.android.server.devicepolicy;
package com.android.server.devicepolicy;


import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;


/**
/**
 * Tests for the DeviceOwner object that saves & loads device and policy owner information.
 * Tests for the DeviceOwner object that saves & loads device and policy owner information.
@@ -32,69 +34,34 @@ import java.io.ByteArrayOutputStream;
 */
 */
public class DeviceOwnerTest extends AndroidTestCase {
public class DeviceOwnerTest extends AndroidTestCase {


    private ByteArrayInputStream mInputStreamForTest;
    private static class DeviceOwnerSub extends DeviceOwner{
    private final ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream();
        private final File mLegacyFile;
        private final File mDeviceOwnerFile;
        private final File mProfileOwnerBase;


    @SmallTest
        public DeviceOwnerSub(Context context, File legacyFile, File deviceOwnerFile,
    public void testDeviceOwnerOnly() throws Exception {
                File profileOwnerBase) {
        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
            super(context);
        out.setDeviceOwner("some.device.owner.package", "owner");
            mLegacyFile = legacyFile;
        out.writeOwnerFile();
            mDeviceOwnerFile = deviceOwnerFile;

            mProfileOwnerBase = profileOwnerBase;
        mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());
        DeviceOwner in = new DeviceOwner(mInputStreamForTest, null);
        in.readOwnerFile();

        assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName());
        assertEquals("owner", in.getDeviceOwnerName());
        assertNull(in.getProfileOwnerComponent(1));
        }
        }


    @SmallTest
        @Override
    public void testProfileOwnerOnly() throws Exception {
        File getLegacyConfigFileWithTestOverride() {
        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
            return mLegacyFile;
        ComponentName admin = new ComponentName(
            "some.profile.owner.package", "some.profile.owner.package.Class");
        out.setProfileOwner(admin, "some-company", 1);
        out.writeOwnerFile();

        mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());
        DeviceOwner in = new DeviceOwner(mInputStreamForTest, null);
        in.readOwnerFile();

        assertNull(in.getDeviceOwnerPackageName());
        assertNull(in.getDeviceOwnerName());
        assertEquals(admin, in.getProfileOwnerComponent(1));
        assertEquals("some-company", in.getProfileOwnerName(1));
        }
        }


    @SmallTest
        @Override
    public void testDeviceAndProfileOwners() throws Exception {
        File getDeviceOwnerFileWithTestOverride() {
        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
            return mDeviceOwnerFile;
        ComponentName profileAdmin = new ComponentName(
        }
            "some.profile.owner.package", "some.profile.owner.package.Class");
        ComponentName otherProfileAdmin = new ComponentName(
            "some.other.profile.owner", "some.other.profile.owner.OtherClass");
        // Old code used package name rather than component name, so the class
        // bit could be empty.
        ComponentName legacyComponentName = new ComponentName("legacy.profile.owner.package", "");
        out.setDeviceOwner("some.device.owner.package", "owner");
        out.setProfileOwner(profileAdmin, "some-company", 1);
        out.setProfileOwner(otherProfileAdmin, "some-other-company", 2);
        out.setProfileOwner(legacyComponentName, "legacy-company", 3);
        out.writeOwnerFile();

        mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());

        DeviceOwner in = new DeviceOwner(mInputStreamForTest, null);
        in.readOwnerFile();


        assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName());
        @Override
        assertEquals("owner", in.getDeviceOwnerName());
        File getProfileOwnerFileWithTestOverride(int userId) {
        assertEquals(profileAdmin, in.getProfileOwnerComponent(1));
            return new File(mDeviceOwnerFile.getAbsoluteFile() + "-" + userId);
        assertEquals("some-company", in.getProfileOwnerName(1));
        assertEquals(otherProfileAdmin, in.getProfileOwnerComponent(2));
        assertEquals("some-other-company", in.getProfileOwnerName(2));
        assertEquals(legacyComponentName, in.getProfileOwnerComponent(3));
        }
        }
    }
    }

    // TODO Write tests
}