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());