Merge "Add new Factory reset protection policy APIs"
diff --git a/api/current.txt b/api/current.txt
index 4439fb7..1d50359 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6764,6 +6764,7 @@
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+    method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
     method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
     method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6882,6 +6883,7 @@
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
     method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
     method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
     method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
     method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -7116,6 +7118,21 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
   }
 
+  public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+    method public boolean isFactoryResetProtectionDisabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+  }
+
+  public static class FactoryResetProtectionPolicy.Builder {
+    ctor public FactoryResetProtectionPolicy.Builder();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+  }
+
   public class FreezePeriod {
     ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
     method public java.time.MonthDay getEnd();
diff --git a/api/system-current.txt b/api/system-current.txt
index f8484d79..87af55c 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -816,6 +816,7 @@
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 62b499c..69640b8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1411,6 +1411,16 @@
             = "android.app.action.DEVICE_OWNER_CHANGED";
 
     /**
+     * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+     *
+     * @see #setFactoryResetProtectionPolicy
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+            "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+    /**
      * The ComponentName of the administrator component.
      *
      * @see #ACTION_ADD_DEVICE_ADMIN
@@ -4329,6 +4339,60 @@
     }
 
     /**
+     * Callable by device owner or profile owner of an organization-owned device, to set a
+     * factory reset protection (FRP) policy. When a new policy is set, the system
+     * notifies the FRP management agent of a policy change by broadcasting
+     * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+     *
+     * @param admin  Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param policy the new FRP policy, or {@code null} to clear the current policy.
+     * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+     *                           an organization-owned device.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        throwIfParentInstance("setFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                mService.setFactoryResetProtectionPolicy(admin, policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Callable by device owner or profile owner of an organization-owned device, to retrieve
+     * the current factory reset protection (FRP) policy set previously by
+     * {@link #setFactoryResetProtectionPolicy}.
+     * <p>
+     * This method can also be called by the FRP management agent on device, in which case,
+     * it can pass {@code null} as the ComponentName.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     *              {@code null} if called by the FRP management agent on device.
+     * @return The current FRP policy object or {@code null} if no policy is set.
+     * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+     *                           an organization-owned device or the FRP management agent.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName admin) {
+        throwIfParentInstance("getFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                return mService.getFactoryResetProtectionPolicy(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called by an application that is administering the device to set the
      * global proxy and exclusion list.
      * <p>
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
new file mode 100644
index 0000000..72e639a
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 android.app.admin;
+
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 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 android.app.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+    private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+    private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+            "factory_reset_protection_account";
+    private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+            "factory_reset_protection_disabled";
+    private static final String ATTR_VALUE = "value";
+
+    private final List<String> mFactoryResetProtectionAccounts;
+    private final boolean mFactoryResetProtectionDisabled;
+
+    private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+            boolean factoryResetProtectionDisabled) {
+        mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+        mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+    }
+
+    /**
+     * Get the list of accounts that can provision a device which has been factory reset.
+     */
+    public @NonNull List<String> getFactoryResetProtectionAccounts() {
+        return mFactoryResetProtectionAccounts;
+    }
+
+    /**
+     * Return whether factory reset protection for the device is disabled or not.
+     */
+    public boolean isFactoryResetProtectionDisabled() {
+        return mFactoryResetProtectionDisabled;
+    }
+
+    /**
+     * Builder class for {@link FactoryResetProtectionPolicy} objects.
+     */
+    public static class Builder {
+        private List<String> mFactoryResetProtectionAccounts;
+        private boolean mFactoryResetProtectionDisabled;
+
+        /**
+         * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+         */
+        public Builder() {
+        };
+
+        /**
+         * Sets which accounts can unlock a device that has been factory reset.
+         * <p>
+         * Once set, the consumer unlock flow will be disabled and only accounts in this list
+         * can unlock factory reset protection after untrusted factory reset.
+         * <p>
+         * It's up to the FRP management agent to interpret the {@code String} as account it
+         * supports. Please consult their relevant documentation for details.
+         *
+         * @param factoryResetProtectionAccounts list of accounts.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionAccounts(
+                @NonNull List<String> factoryResetProtectionAccounts) {
+            mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+            return this;
+        }
+
+        /**
+         * Sets whether factory reset protection is disabled or not.
+         * <p>
+         * Once disabled, factory reset protection will not kick in all together when the device
+         * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+         * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+         *
+         * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+            mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+            return this;
+        }
+
+        /**
+         * Combines all of the attributes that have been set on this {@code Builder}
+         *
+         * @return a new {@link FactoryResetProtectionPolicy} object.
+         */
+        @NonNull
+        public FactoryResetProtectionPolicy build() {
+            return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+                    mFactoryResetProtectionDisabled);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FactoryResetProtectionPolicy{"
+                + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+                + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+                + '}';
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+        int accountsCount = mFactoryResetProtectionAccounts.size();
+        dest.writeInt(accountsCount);
+        for (String account: mFactoryResetProtectionAccounts) {
+            dest.writeString(account);
+        }
+        dest.writeBoolean(mFactoryResetProtectionDisabled);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+            new Creator<FactoryResetProtectionPolicy>() {
+
+                @Override
+                public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+                    List<String> factoryResetProtectionAccounts = new ArrayList<>();
+                    int accountsCount = in.readInt();
+                    for (int i = 0; i < accountsCount; i++) {
+                        factoryResetProtectionAccounts.add(in.readString());
+                    }
+                    boolean factoryResetProtectionDisabled = in.readBoolean();
+
+                    return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                            factoryResetProtectionDisabled);
+                }
+
+                @Override
+                public FactoryResetProtectionPolicy[] newArray(int size) {
+                    return new FactoryResetProtectionPolicy[size];
+                }
+    };
+
+    /**
+     * Restore a previously saved FactoryResetProtectionPolicy from XML.
+     * <p>
+     * No validation is required on the reconstructed policy since the XML was previously
+     * created by the system server from a validated policy.
+     * @hide
+     */
+    @Nullable
+    public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+        try {
+            boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+                    parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+            List<String> factoryResetProtectionAccounts = new ArrayList<>();
+            int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != END_DOCUMENT
+                    && (type != END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == END_TAG || type == TEXT) {
+                    continue;
+                }
+                if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+                    continue;
+                }
+                factoryResetProtectionAccounts.add(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            }
+
+            return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                    factoryResetProtectionDisabled);
+        } catch (XmlPullParserException | IOException e) {
+            Log.w(LOG_TAG, "Reading from xml failed", e);
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+        out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+                Boolean.toString(mFactoryResetProtectionDisabled));
+        for (String account : mFactoryResetProtectionAccounts) {
+            out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+            out.attribute(null, ATTR_VALUE, account);
+            out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+        }
+    }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3eec46b..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -104,6 +105,9 @@
 
     void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
 
+    void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+    FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
     void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 9054d54..0fca1d1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -154,4 +154,5 @@
   SET_AUTO_TIME = 127;
   SET_AUTO_TIME_ZONE = 128;
   SET_PACKAGES_PROTECTED = 129;
+  SET_FACTORY_RESET_PROTECTION = 130;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0e4cea1..d4768c0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -445,6 +445,7 @@
 
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
     <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+    <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
     <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
 
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
 
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
+
+    /** Retrieves the UID that can access the persistent data partition. */
+    int getAllowedUid();
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
             writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
         }
 
+        @Override
+        public int getAllowedUid() {
+            return mAllowedUid;
+        }
+
         private void writeInternal(byte[] data, long offset, int dataLength) {
             checkArgument(data == null || data.length > 0, "data must be null or non-empty");
             checkArgument(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3592e5f..6b81fdd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
@@ -268,6 +269,7 @@
 import com.android.internal.widget.PasswordValidationError;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1006,6 +1008,8 @@
         private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
                 "cross-profile-calendar-packages-null";
         private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+        private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+                "factory_reset_protection_policy";
 
         DeviceAdminInfo info;
 
@@ -1016,6 +1020,9 @@
         @NonNull
         PasswordPolicy mPasswordPolicy = new PasswordPolicy();
 
+        @Nullable
+        FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
         static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
         long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
 
@@ -1351,6 +1358,11 @@
                         mCrossProfileCalendarPackages);
             }
             writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+            if (mFactoryResetProtectionPolicy != null) {
+                out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+                mFactoryResetProtectionPolicy.writeToXml(out);
+                out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+            }
         }
 
         void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1584,6 +1596,9 @@
                     mCrossProfileCalendarPackages = null;
                 } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
                     mCrossProfilePackages = readPackageList(parser, tag);
+                } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+                    mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+                                parser);
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -2036,6 +2051,10 @@
             return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
         }
 
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+
         LockSettingsInternal getLockSettingsInternal() {
             return LocalServices.getService(LockSettingsInternal.class);
         }
@@ -6736,6 +6755,67 @@
     }
 
     @Override
+    public void setFactoryResetProtectionPolicy(ComponentName who,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            admin.mFactoryResetProtectionPolicy = policy;
+            saveSettingsLocked(userId);
+        }
+
+        mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+                .setAdmin(who)
+                .write();
+    }
+
+    @Override
+    public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName who) {
+        if (!mHasFeature) {
+            return null;
+        }
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            if (who == null) {
+                if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+                    throw new SecurityException(
+                            "Must be called by the FRP management agent on device");
+                }
+                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                        UserHandle.getUserId(frpManagementAgentUid));
+            } else {
+                admin = getActiveAdminForCallerLocked(
+                        who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            }
+        }
+        return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+    }
+
+    private int getFrpManagementAgentUidOrThrow() {
+        PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+        if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+            throw new UnsupportedOperationException(
+                    "The persistent data block service is not supported on this device");
+        }
+        return pdb.getAllowedUid();
+    }
+
+    @Override
     public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
         if (!mHasFeature) {
             return;
@@ -8131,6 +8211,14 @@
         return null;
     }
 
+    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+        ActiveAdmin admin = getDeviceOwnerAdminLocked();
+        if (admin == null) {
+            admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+        }
+        return admin;
+    }
+
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.File;
@@ -223,6 +224,11 @@
         }
 
         @Override
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return services.persistentDataBlockManagerInternal;
+        }
+
+        @Override
         Looper getMyLooper() {
             return Looper.getMainLooper();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 20cb449..21034d3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,6 +31,8 @@
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
@@ -62,6 +64,7 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -2022,6 +2025,116 @@
         );
     }
 
+    public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+        setupProfileOwner();
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+    }
+
+    public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+            throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setActiveAdmin(admin1, /*replace=*/ false);
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+                expectedPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+    }
+
     public void testGetMacAddress() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+    private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+            "factory_reset_protection_policy";
+
+    @Test
+    public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testSerializationAndDeserialization(policy);
+    }
+
+    @Test
+    public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testInvalidXmlSerializationAndDeserialization(policy);
+    }
+
+    private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        FactoryResetProtectionPolicy actualPolicy =
+                FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+        assertPoliciesAreEqual(policy, actualPolicy);
+        parcel.recycle();
+    }
+
+    private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new InputStreamReader(inStream));
+        assertEquals(XmlPullParser.START_TAG, parser.next());
+
+        assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+    }
+
+    private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = mock(XmlPullParser.class);
+        when(parser.next()).thenThrow(XmlPullParserException.class);
+        parser.setInput(new InputStreamReader(inStream));
+
+        // If deserialization fails, then null is returned.
+        assertNull(policy.readFromXml(parser));
+    }
+
+    private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+            throws IOException {
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        final XmlSerializer outXml = new FastXmlSerializer();
+        outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+        outXml.startDocument(null, true);
+        outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        policy.writeToXml(outXml);
+        outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        outXml.endDocument();
+        outXml.flush();
+        return outStream;
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+                actualPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertEquals(expectedAccounts.size(), actualAccounts.size());
+        for (int i = 0; i < expectedAccounts.size(); i++) {
+            assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..6c2c144 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -68,6 +68,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -117,6 +118,7 @@
     public final TimeDetector timeDetector;
     public final TimeZoneDetector timeZoneDetector;
     public final KeyChain.KeyChainConnection keyChainConnection;
+    public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -160,6 +162,7 @@
         timeDetector = mock(TimeDetector.class);
         timeZoneDetector = mock(TimeZoneDetector.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+        persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());