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

Commit 39e784dd authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Split device owner config files

DPMS.mDeviceOwner is now always non-null, so no null checks are needed.

Bug 22802261
Bug 23432442

Change-Id: Ia8e5f114ecfc0add44b0d1be7d043ef6e37019ef
parent ec537527
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
}