Extend DeviceOwner concept to accommodate ProfileOwners

ProfileOwners, like DeviceOwners, are Device Admins that have
additional priviledges. ProfileOwners however are scoped per
user.

Change-Id: I1e22c85878e0672121e6ebbe97fca38591f992b2
diff --git a/api/current.txt b/api/current.txt
index c0ed81a..740ec38 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4771,6 +4771,7 @@
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isDeviceOwnerApp(java.lang.String);
+    method public boolean isProfileOwnerApp(java.lang.String);
     method public void lockNow();
     method public void removeActiveAdmin(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ab82531..40bdb73 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
+import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -1681,4 +1682,88 @@
         }
         return null;
     }
+
+    /**
+     * @hide
+     * Sets the given package as the profile owner of the given user profile. The package must
+     * already be installed and there shouldn't be an existing profile owner registered for this
+     * user. Also, this method must be called before the user has been used for the first time.
+     * @param packageName the package name of the application to be registered as profile owner.
+     * @param ownerName the human readable name of the organisation associated with this DPM.
+     * @return whether the package was successfully registered as the profile owner.
+     * @throws IllegalArgumentException if packageName is null, the package isn't installed, or
+     *         the user has already been set up.
+     */
+    public boolean setProfileOwner(String packageName, String ownerName)
+            throws IllegalArgumentException {
+        if (mService != null) {
+            try {
+                return mService.setProfileOwner(packageName, ownerName,
+                        Process.myUserHandle().getIdentifier());
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to set profile owner", re);
+                throw new IllegalArgumentException("Couldn't set profile owner.", re);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Used to determine if a particular package is registered as the Profile Owner for the
+     * current user. A profile owner is a special device admin that has additional priviledges
+     * within the managed profile.
+     *
+     * @param packageName The package name of the app to compare with the registered profile owner.
+     * @return Whether or not the package is registered as the profile owner.
+     */
+    public boolean isProfileOwnerApp(String packageName) {
+        if (mService != null) {
+            try {
+                String profileOwnerPackage = mService.getProfileOwner(
+                        Process.myUserHandle().getIdentifier());
+                return profileOwnerPackage != null && profileOwnerPackage.equals(packageName);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to check profile owner");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     * @return the packageName of the owner of the given user profile or null if no profile
+     * owner has been set for that user.
+     * @throws IllegalArgumentException if the userId is invalid.
+     */
+    public String getProfileOwner() throws IllegalArgumentException {
+        if (mService != null) {
+            try {
+                return mService.getProfileOwner(Process.myUserHandle().getIdentifier());
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to get profile owner");
+                throw new IllegalArgumentException(
+                        "Requested profile owner for invalid userId", re);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     * @return the human readable name of the organisation associated with this DPM or null if
+     *         one is not set.
+     * @throws IllegalArgumentException if the userId is invalid.
+     */
+    public String getProfileOwnerName() throws IllegalArgumentException {
+        if (mService != null) {
+            try {
+                return mService.getProfileOwnerName(Process.myUserHandle().getIdentifier());
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to get profile owner");
+                throw new IllegalArgumentException(
+                        "Requested profile owner for invalid userId", re);
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 172c47c..9d189db 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -103,6 +103,10 @@
     String getDeviceOwner();
     String getDeviceOwnerName();
 
+    boolean setProfileOwner(String packageName, String ownerName, int userHandle);
+    String getProfileOwner(int userHandle);
+    String getProfileOwnerName(int userHandle);
+
     boolean installCaCert(in byte[] certBuffer);
     void uninstallCaCert(in byte[] certBuffer);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
new file mode 100644
index 0000000..1b048a1
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2014 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.devicepolicy;
+
+import android.app.AppGlobals;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+
+/**
+ * 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.
+ */
+public class DeviceOwner {
+    private static final String TAG = "DevicePolicyManagerService";
+
+    private static final String DEVICE_OWNER_XML = "device_owner.xml";
+    private static final String TAG_DEVICE_OWNER = "device-owner";
+    private static final String TAG_PROFILE_OWNER = "profile-owner";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_PACKAGE = "package";
+    private static final String ATTR_USERID = "userId";
+
+    private AtomicFile fileForWriting;
+
+    // Input/Output streams for testing.
+    private InputStream mInputStreamForTest;
+    private OutputStream mOutputStreamForTest;
+
+    // Internal state for the device owner package.
+    private String mDeviceOwnerPackageName;
+    private String mDeviceOwnerName;
+
+    // Internal state for the profile owner packages.
+    private final HashMap<Integer, String[]> mProfileOwners = new HashMap<Integer, String[]>();
+
+    // Private default constructor.
+    private DeviceOwner() {
+    }
+
+    @VisibleForTesting
+    DeviceOwner(InputStream in, OutputStream out) {
+        mInputStreamForTest = in;
+        mOutputStreamForTest = out;
+    }
+
+    /**
+     * Loads the device owner state from disk.
+     */
+    static DeviceOwner load() {
+        DeviceOwner owner = new DeviceOwner();
+        if (new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML).exists()) {
+            owner.readOwnerFile();
+            return owner;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 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.mDeviceOwnerPackageName = packageName;
+        owner.mDeviceOwnerName = ownerName;
+        return owner;
+    }
+
+    /**
+     * Creates an instance of the device owner object with the profile owner set.
+     */
+    static DeviceOwner createWithProfileOwner(String packageName, String ownerName, int userId) {
+        DeviceOwner owner = new DeviceOwner();
+        owner.mProfileOwners.put(userId, new String[] { packageName, ownerName });
+        return owner;
+    }
+
+    String getDeviceOwnerPackageName() {
+        return mDeviceOwnerPackageName;
+    }
+
+    String getDeviceOwnerName() {
+        return mDeviceOwnerName;
+    }
+
+    void setDeviceOwner(String packageName, String ownerName) {
+        mDeviceOwnerPackageName = packageName;
+        mDeviceOwnerName = ownerName;
+    }
+
+    void setProfileOwner(String packageName, String ownerName, int userId) {
+        mProfileOwners.put(userId, new String[] { packageName, ownerName });
+    }
+
+    void removeProfileOwner(int userId) {
+        mProfileOwners.remove(userId);
+    }
+
+    String getProfileOwnerPackageName(int userId) {
+        String[] profileOwner = mProfileOwners.get(userId);
+        return profileOwner != null ? profileOwner[0] : null;
+    }
+
+    String getProfileOwnerName(int userId) {
+        String[] profileOwner = mProfileOwners.get(userId);
+        return profileOwner != null ? profileOwner[1] : null;
+    }
+
+    boolean hasDeviceOwner() {
+        return mDeviceOwnerPackageName != null;
+    }
+
+    static boolean isInstalled(String packageName, PackageManager pm) {
+        try {
+            PackageInfo pi;
+            if ((pi = pm.getPackageInfo(packageName, 0)) != null) {
+                if ((pi.applicationInfo.flags) != 0) {
+                    return true;
+                }
+            }
+        } catch (NameNotFoundException nnfe) {
+            Slog.w(TAG, "Device Owner package " + packageName + " not installed.");
+        }
+        return false;
+    }
+
+    static boolean isInstalledForUser(String packageName, int userHandle) {
+        try {
+            PackageInfo pi = (AppGlobals.getPackageManager())
+                    .getPackageInfo(packageName, 0, userHandle);
+            if (pi != null && pi.applicationInfo.flags != 0) {
+                return true;
+            }
+        } catch (RemoteException re) {
+            throw new RuntimeException("Package manager has died", re);
+        }
+
+        return false;
+    }
+
+    void readOwnerFile() {
+        try {
+            InputStream input = openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(input, null);
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type!=XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String tag = parser.getName();
+                if (tag.equals(TAG_DEVICE_OWNER)) {
+                    mDeviceOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+                    mDeviceOwnerName = parser.getAttributeValue(null, ATTR_NAME);
+                } else if (tag.equals(TAG_PROFILE_OWNER)) {
+                    String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+                    String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME);
+                    int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USERID));
+                    mProfileOwners.put(userId,
+                            new String[] { profileOwnerPackageName, profileOwnerName });
+                } else {
+                    throw new XmlPullParserException(
+                            "Unexpected tag in device owner file: " + tag);
+                }
+            }
+            input.close();
+        } catch (XmlPullParserException xppe) {
+            Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
+        } catch (IOException ioe) {
+            Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
+        }
+    }
+
+    void writeOwnerFile() {
+        synchronized (this) {
+            writeOwnerFileLocked();
+        }
+    }
+
+    private void writeOwnerFileLocked() {
+        try {
+            OutputStream outputStream = startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(outputStream, "utf-8");
+            out.startDocument(null, true);
+
+            // Write device owner tag
+            if (mDeviceOwnerPackageName != null) {
+                out.startTag(null, TAG_DEVICE_OWNER);
+                out.attribute(null, ATTR_PACKAGE, mDeviceOwnerPackageName);
+                if (mDeviceOwnerName != null) {
+                    out.attribute(null, ATTR_NAME, mDeviceOwnerName);
+                }
+                out.endTag(null, TAG_DEVICE_OWNER);
+            }
+
+            // Write profile owner tags
+            if (mProfileOwners.size() > 0) {
+                for (HashMap.Entry<Integer, String[]> owner : mProfileOwners.entrySet()) {
+                    out.startTag(null, TAG_PROFILE_OWNER);
+                    out.attribute(null, ATTR_PACKAGE, owner.getValue()[0]);
+                    out.attribute(null, ATTR_NAME, owner.getValue()[1]);
+                    out.attribute(null, ATTR_USERID, Integer.toString(owner.getKey()));
+                    out.endTag(null, TAG_PROFILE_OWNER);
+                }
+            }
+            out.endDocument();
+            out.flush();
+            finishWrite(outputStream);
+        } catch (IOException ioe) {
+            Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
+        }
+    }
+
+    private InputStream openRead() throws IOException {
+        if (mInputStreamForTest != null) {
+            return mInputStreamForTest;
+        }
+
+        return new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+                DEVICE_OWNER_XML)).openRead();
+    }
+
+    private OutputStream startWrite() throws IOException {
+        if (mOutputStreamForTest != null) {
+            return mOutputStreamForTest;
+        }
+
+        fileForWriting = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+                DEVICE_OWNER_XML));
+        return fileForWriting.startWrite();
+    }
+
+    private void finishWrite(OutputStream stream) {
+        if (fileForWriting != null) {
+            fileForWriting.finishWrite((FileOutputStream) stream);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 186fbe1..a8f2df1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -26,10 +26,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.org.conscrypt.TrustedCertificateStore;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 import android.app.Activity;
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
@@ -49,9 +45,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.ProxyProperties;
@@ -75,7 +69,6 @@
 import android.security.IKeyChainService;
 import android.security.KeyChain;
 import android.security.KeyChain.KeyChainConnection;
-import android.util.AtomicFile;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -84,6 +77,10 @@
 import android.util.Xml;
 import android.view.IWindowManager;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -132,6 +129,7 @@
     IWindowManager mIWindowManager;
     NotificationManager mNotificationManager;
 
+    // Stores and loads state on device and profile owners.
     private DeviceOwner mDeviceOwner;
 
     /**
@@ -601,6 +599,10 @@
                 Slog.w(LOG_TAG, "Tried to remove device policy file for user 0! Ignoring.");
                 return;
             }
+
+            mDeviceOwner.removeProfileOwner(userHandle);
+            mDeviceOwner.writeOwnerFile();
+
             DevicePolicyData policy = mUserData.get(userHandle);
             if (policy != null) {
                 mUserData.remove(userHandle);
@@ -614,9 +616,7 @@
 
     void loadDeviceOwner() {
         synchronized (this) {
-            if (DeviceOwner.isRegistered()) {
-                mDeviceOwner = new DeviceOwner();
-            }
+            mDeviceOwner = DeviceOwner.load();
         }
     }
 
@@ -1294,7 +1294,8 @@
             if (admin.getUid() != Binder.getCallingUid()) {
                 // If trying to remove device owner, refuse when the caller is not the owner.
                 if (mDeviceOwner != null
-                        && adminReceiver.getPackageName().equals(mDeviceOwner.getPackageName())) {
+                        && adminReceiver.getPackageName().equals(
+                        mDeviceOwner.getDeviceOwnerPackageName())) {
                     return;
                 }
                 mContext.enforceCallingOrSelfPermission(
@@ -2739,14 +2740,26 @@
                     + " for device owner");
         }
         synchronized (this) {
-            if (mDeviceOwner == null && !isDeviceProvisioned()) {
-                mDeviceOwner = new DeviceOwner(packageName, ownerName);
+            if (isDeviceProvisioned()) {
+                throw new IllegalStateException(
+                        "Trying to set device owner but device is already provisioned.");
+            }
+
+            if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) {
+                throw new IllegalStateException(
+                        "Trying to set device owner but device owner is already set.");
+            }
+
+            if (mDeviceOwner == null) {
+                // Device owner is not set and does not exist, set it.
+                mDeviceOwner = DeviceOwner.createWithDeviceOwner(packageName, ownerName);
                 mDeviceOwner.writeOwnerFile();
                 return true;
             } else {
-                throw new IllegalStateException("Trying to set device owner to " + packageName
-                        + ", owner=" + mDeviceOwner.getPackageName()
-                        + ", device_provisioned=" + isDeviceProvisioned());
+                // Device owner is not set but a profile owner exists, update Device owner state.
+                mDeviceOwner.setDeviceOwner(packageName, ownerName);
+                mDeviceOwner.writeOwnerFile();
+                return true;
             }
         }
     }
@@ -2758,7 +2771,7 @@
         }
         synchronized (this) {
             return mDeviceOwner != null
-                    && mDeviceOwner.getPackageName().equals(packageName);
+                    && mDeviceOwner.getDeviceOwnerPackageName().equals(packageName);
         }
     }
 
@@ -2769,7 +2782,7 @@
         }
         synchronized (this) {
             if (mDeviceOwner != null) {
-                return mDeviceOwner.getPackageName();
+                return mDeviceOwner.getDeviceOwnerPackageName();
             }
         }
         return null;
@@ -2783,7 +2796,67 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         synchronized (this) {
             if (mDeviceOwner != null) {
-                return mDeviceOwner.getName();
+                return mDeviceOwner.getDeviceOwnerName();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean setProfileOwner(String packageName, String ownerName, int userHandle) {
+        if (!mHasFeature) {
+            return false;
+        }
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        if (packageName == null
+                || !DeviceOwner.isInstalledForUser(packageName, userHandle)) {
+            throw new IllegalArgumentException("Package name " + packageName
+                    + " not installed for userId:" + userHandle);
+        }
+        synchronized (this) {
+            if (isUserSetupComplete(userHandle)) {
+                throw new IllegalStateException(
+                        "Trying to set profile owner but user is already set-up.");
+            }
+            if (mDeviceOwner == null) {
+                // Device owner state does not exist, create it.
+                mDeviceOwner = DeviceOwner.createWithProfileOwner(packageName, ownerName,
+                        userHandle);
+                mDeviceOwner.writeOwnerFile();
+                return true;
+            } else {
+                // Device owner already exists, update it.
+                mDeviceOwner.setProfileOwner(packageName, ownerName, userHandle);
+                mDeviceOwner.writeOwnerFile();
+                return true;
+            }
+        }
+    }
+
+    @Override
+    public String getProfileOwner(int userHandle) {
+        if (!mHasFeature) {
+            return null;
+        }
+
+        synchronized (this) {
+            if (mDeviceOwner != null) {
+                return mDeviceOwner.getProfileOwnerPackageName(userHandle);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getProfileOwnerName(int userHandle) {
+        if (!mHasFeature) {
+            return null;
+        }
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+
+        synchronized (this) {
+            if (mDeviceOwner != null) {
+                return mDeviceOwner.getProfileOwnerName(userHandle);
             }
         }
         return null;
@@ -2794,6 +2867,11 @@
                 Settings.Global.DEVICE_PROVISIONED, 0) > 0;
     }
 
+    private boolean isUserSetupComplete(int userId) {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0, userId) > 0;
+    }
+
     private void enforceCrossUserPermission(int userHandle) {
         if (userHandle < 0) {
             throw new IllegalArgumentException("Invalid userId " + userHandle);
@@ -2858,103 +2936,4 @@
             }
         }
     }
-
-    static class DeviceOwner {
-        private static final String DEVICE_OWNER_XML = "device_owner.xml";
-        private static final String TAG_DEVICE_OWNER = "device-owner";
-        private static final String ATTR_NAME = "name";
-        private static final String ATTR_PACKAGE = "package";
-        private String mPackageName;
-        private String mOwnerName;
-
-        DeviceOwner() {
-            readOwnerFile();
-        }
-
-        DeviceOwner(String packageName, String ownerName) {
-            this.mPackageName = packageName;
-            this.mOwnerName = ownerName;
-        }
-
-        static boolean isRegistered() {
-            return new File(Environment.getSystemSecureDirectory(),
-                    DEVICE_OWNER_XML).exists();
-        }
-
-        String getPackageName() {
-            return mPackageName;
-        }
-
-        String getName() {
-            return mOwnerName;
-        }
-
-        static boolean isInstalled(String packageName, PackageManager pm) {
-            try {
-                PackageInfo pi;
-                if ((pi = pm.getPackageInfo(packageName, 0)) != null) {
-                    if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                        return true;
-                    }
-                }
-            } catch (NameNotFoundException nnfe) {
-                Slog.w(LOG_TAG, "Device Owner package " + packageName + " not installed.");
-            }
-            return false;
-        }
-
-        void readOwnerFile() {
-            AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
-                    DEVICE_OWNER_XML));
-            try {
-                FileInputStream input = file.openRead();
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(input, null);
-                int type;
-                while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                        && type != XmlPullParser.START_TAG) {
-                }
-                String tag = parser.getName();
-                if (!TAG_DEVICE_OWNER.equals(tag)) {
-                    throw new XmlPullParserException(
-                            "Device Owner file does not start with device-owner tag: found " + tag);
-                }
-                mPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-                mOwnerName = parser.getAttributeValue(null, ATTR_NAME);
-                input.close();
-            } catch (XmlPullParserException xppe) {
-                Slog.e(LOG_TAG, "Error parsing device-owner file\n" + xppe);
-            } catch (IOException ioe) {
-                Slog.e(LOG_TAG, "IO Exception when reading device-owner file\n" + ioe);
-            }
-        }
-
-        void writeOwnerFile() {
-            synchronized (this) {
-                writeOwnerFileLocked();
-            }
-        }
-
-        private void writeOwnerFileLocked() {
-            AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
-                    DEVICE_OWNER_XML));
-            try {
-                FileOutputStream output = file.startWrite();
-                XmlSerializer out = new FastXmlSerializer();
-                out.setOutput(output, "utf-8");
-                out.startDocument(null, true);
-                out.startTag(null, TAG_DEVICE_OWNER);
-                out.attribute(null, ATTR_PACKAGE, mPackageName);
-                if (mOwnerName != null) {
-                    out.attribute(null, ATTR_NAME, mOwnerName);
-                }
-                out.endTag(null, TAG_DEVICE_OWNER);
-                out.endDocument();
-                out.flush();
-                file.finishWrite(output);
-            } catch (IOException ioe) {
-                Slog.e(LOG_TAG, "IO Exception when writing device-owner file\n" + ioe);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java
new file mode 100644
index 0000000..f913b97
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 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.devicepolicy;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Tests for the DeviceOwner object that saves & loads device and policy owner information.
+ * run this test with:
+ *   make -j FrameworksServicesTests
+ *   runtest --path frameworks/base/services/tests/servicestests/ \
+ *       src/com/android/server/devicepolicy/DeviceOwnerTest.java
+ */
+public class DeviceOwnerTest extends AndroidTestCase {
+
+    private ByteArrayInputStream mInputStreamForTest;
+    private ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream();
+
+    @SmallTest
+    public void testDeviceOwnerOnly() throws Exception {
+        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
+        out.setDeviceOwner("some.device.owner.package", "owner");
+        out.writeOwnerFile();
+
+        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.getProfileOwnerPackageName(1));
+    }
+
+    @SmallTest
+    public void testProfileOwnerOnly() throws Exception {
+        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
+        out.setProfileOwner("some.profile.owner.package", "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("some.profile.owner.package", in.getProfileOwnerPackageName(1));
+        assertEquals("some-company", in.getProfileOwnerName(1));
+    }
+
+    @SmallTest
+    public void testDeviceAndProfileOwners() throws Exception {
+        DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
+        out.setDeviceOwner("some.device.owner.package", "owner");
+        out.setProfileOwner("some.profile.owner.package", "some-company", 1);
+        out.setProfileOwner("some.other.profile.owner", "some-other-company", 2);
+        out.writeOwnerFile();
+
+        mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());
+
+        DeviceOwner in = new DeviceOwner(mInputStreamForTest, null);
+        in.readOwnerFile();
+
+        assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName());
+        assertEquals("owner", in.getDeviceOwnerName());
+        assertEquals("some.profile.owner.package", in.getProfileOwnerPackageName(1));
+        assertEquals("some-company", in.getProfileOwnerName(1));
+        assertEquals("some.other.profile.owner", in.getProfileOwnerPackageName(2));
+        assertEquals("some-other-company", in.getProfileOwnerName(2));
+    }
+}
\ No newline at end of file