Add setOtaPolicy/getOtaPolicy API in DPMS
Allow device owners to set OTA policy for automatically accept/postpone
incoming OTA system updates. This class only provides the setting
and getting of OTA policy, the actual OTA subsystem should handle
and respect the policy stored here.
Bug: 19650524
Change-Id: I9b64949fab42097429b7da649039c13f42c10fd1
diff --git a/api/current.txt b/api/current.txt
index dc83f6e..c26a332 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5663,6 +5663,7 @@
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public android.app.admin.OtaPolicy getOtaPolicy();
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -5714,6 +5715,7 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public void setOtaPolicy(android.content.ComponentName, android.app.admin.OtaPolicy);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
method public void setPasswordHistoryLength(android.content.ComponentName, int);
method public void setPasswordMinimumLength(android.content.ComponentName, int);
@@ -5743,6 +5745,7 @@
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+ field public static final java.lang.String ACTION_OTA_POLICY_CHANGED = "android.app.action.OTA_POLICY_CHANGED";
field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
@@ -5810,6 +5813,23 @@
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
}
+ public class OtaPolicy {
+ ctor public OtaPolicy();
+ method public int getInstallWindowEnd();
+ method public int getInstallWindowStart();
+ method public int getPolicyType();
+ method public void setAutomaticInstallPolicy();
+ method public void setPostponeInstallPolicy();
+ method public void setWindowedInstallPolicy(int, int) throws android.app.admin.OtaPolicy.InvalidWindowException;
+ field public static final int TYPE_INSTALL_AUTOMATIC = 1; // 0x1
+ field public static final int TYPE_INSTALL_WINDOWED = 2; // 0x2
+ field public static final int TYPE_POSTPONE = 3; // 0x3
+ }
+
+ public static class OtaPolicy.InvalidWindowException extends java.lang.Exception {
+ ctor public OtaPolicy.InvalidWindowException(java.lang.String);
+ }
+
}
package android.app.backup {
diff --git a/api/system-current.txt b/api/system-current.txt
index ac80474..ef9e05e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5762,6 +5762,7 @@
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public android.app.admin.OtaPolicy getOtaPolicy();
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -5818,6 +5819,7 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public void setOtaPolicy(android.content.ComponentName, android.app.admin.OtaPolicy);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
method public void setPasswordHistoryLength(android.content.ComponentName, int);
method public void setPasswordMinimumLength(android.content.ComponentName, int);
@@ -5847,6 +5849,7 @@
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+ field public static final java.lang.String ACTION_OTA_POLICY_CHANGED = "android.app.action.OTA_POLICY_CHANGED";
field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
field public static final java.lang.String ACTION_SEND_DEVICE_INITIALIZER_STATUS = "android.app.action.SEND_DEVICE_INITIALIZER_STATUS";
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
@@ -5919,6 +5922,23 @@
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
}
+ public class OtaPolicy {
+ ctor public OtaPolicy();
+ method public int getInstallWindowEnd();
+ method public int getInstallWindowStart();
+ method public int getPolicyType();
+ method public void setAutomaticInstallPolicy();
+ method public void setPostponeInstallPolicy();
+ method public void setWindowedInstallPolicy(int, int) throws android.app.admin.OtaPolicy.InvalidWindowException;
+ field public static final int TYPE_INSTALL_AUTOMATIC = 1; // 0x1
+ field public static final int TYPE_INSTALL_WINDOWED = 2; // 0x2
+ field public static final int TYPE_POSTPONE = 3; // 0x3
+ }
+
+ public static class OtaPolicy.InvalidWindowException extends java.lang.Exception {
+ ctor public OtaPolicy.InvalidWindowException(java.lang.String);
+ }
+
}
package android.app.backup {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 88b1f2d..5453e65 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -754,6 +754,14 @@
public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
/**
+ * Broadcast action: notify that a new local OTA policy has been set by the device owner.
+ * The new policy can be retrieved by {@link #getOtaPolicy()}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_OTA_POLICY_CHANGED = "android.app.action.OTA_POLICY_CHANGED";
+
+
+ /**
* Return true if the given administrator component is currently
* active (enabled) in the system.
*/
@@ -4067,4 +4075,49 @@
Log.w(TAG, "Could not send device initializer status", re);
}
}
+
+ /*
+ * Called by device owners to set a local OTA update policy. When a new OTA policy is set,
+ * {@link #ACTION_OTA_POLICY_CHANGED} is broadcasted.
+ *
+ * @param who Which {@link DeviceAdminReceiver} this request is associated with. All components
+ * in the device owner package can set OTA policies and the most recent policy takes effect.
+ * @param policy the new OTA policy, or null to clear the current policy.
+ * @see OtaPolicy
+ */
+ public void setOtaPolicy(ComponentName who, OtaPolicy policy) {
+ if (mService != null) {
+ try {
+ if (policy != null) {
+ mService.setOtaPolicy(who, policy.getPolicyBundle());
+ } else {
+ mService.setOtaPolicy(who, null);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Error calling setOtaPolicy", re);
+ }
+ }
+ }
+
+ /**
+ * Retrieve a local OTA update policy set previously by {@link #setOtaPolicy}.
+ *
+ * @return The current OTA policy object, or null if no policy is set or the system does not
+ * support managed OTA.
+ */
+ public OtaPolicy getOtaPolicy() {
+ if (mService != null) {
+ try {
+ PersistableBundle bundle = mService.getOtaPolicy();
+ if (bundle != null) {
+ return new OtaPolicy(bundle);
+ } else {
+ return null;
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Error calling getOtaPolicy", re);
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 75b97a8..26bdea8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -215,4 +215,6 @@
void setUserIcon(in ComponentName admin, in Bitmap icon);
void sendDeviceInitializerStatus(int statusCode, String description);
+ void setOtaPolicy(in ComponentName who, in PersistableBundle policy);
+ PersistableBundle getOtaPolicy();
}
diff --git a/core/java/android/app/admin/OtaPolicy.java b/core/java/android/app/admin/OtaPolicy.java
new file mode 100644
index 0000000..98581a7
--- /dev/null
+++ b/core/java/android/app/admin/OtaPolicy.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2015 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 android.annotation.IntDef;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class that represents a local OTA policy set by the device owner.
+ *
+ * @see DevicePolicyManager#setOtaPolicy
+ * @see DevicePolicyManager#getOtaPolicy
+ */
+public class OtaPolicy {
+
+ /** @hide */
+ @IntDef({
+ TYPE_INSTALL_AUTOMATIC,
+ TYPE_INSTALL_WINDOWED,
+ TYPE_POSTPONE})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OtaPolicyType {}
+
+ /**
+ * Install OTA update automatically as soon as one is available.
+ */
+ public static final int TYPE_INSTALL_AUTOMATIC = 1;
+
+ /**
+ * Install OTA update automatically within a daily maintenance window, for a maximum of two-week
+ * period. After that period the OTA will be installed automatically.
+ */
+ public static final int TYPE_INSTALL_WINDOWED = 2;
+
+ /**
+ * Incoming OTA will be blocked for a maximum of two weeks, after which it will be installed
+ * automatically.
+ */
+ public static final int TYPE_POSTPONE = 3;
+
+ private static final String KEY_POLICY_TYPE = "policy_type";
+ private static final String KEY_INSTALL_WINDOW_START = "install_window_start";
+ private static final String KEY_INSTALL_WINDOW_END = "install_window_end";
+
+ private PersistableBundle mPolicy;
+
+ public OtaPolicy() {
+ mPolicy = new PersistableBundle();
+ }
+
+ /**
+ * Construct an OtaPolicy object from a bundle.
+ * @hide
+ */
+ public OtaPolicy(PersistableBundle in) {
+ mPolicy = new PersistableBundle(in);
+ }
+
+ /**
+ * Retrieve the underlying bundle where the policy is stored.
+ * @hide
+ */
+ public PersistableBundle getPolicyBundle() {
+ return new PersistableBundle(mPolicy);
+ }
+
+ /**
+ * Set the OTA policy to: install OTA update automatically as soon as one is available.
+ */
+ public void setAutomaticInstallPolicy() {
+ mPolicy.clear();
+ mPolicy.putInt(KEY_POLICY_TYPE, TYPE_INSTALL_AUTOMATIC);
+ }
+
+ /**
+ * Set the OTA policy to: new OTA update will only be installed automatically when the system
+ * clock is inside a daily maintenance window. If the start and end times are the same, the
+ * window is considered to include the WHOLE 24 hours, that is, OTAs can install at any time. If
+ * the given window in invalid, a {@link OtaPolicy.InvalidWindowException} will be thrown. If
+ * start time is later than end time, the window is considered spanning midnight, i.e. end time
+ * donates a time on the next day. The maintenance window will last for two weeks, after which
+ * the OTA will be installed automatically.
+ *
+ * @param startTime the start of the maintenance window, measured as the number of minutes from
+ * midnight in the device's local time. Must be in the range of [0, 1440).
+ * @param endTime the end of the maintenance window, measured as the number of minutes from
+ * midnight in the device's local time. Must be in the range of [0, 1440).
+ */
+ public void setWindowedInstallPolicy(int startTime, int endTime) throws InvalidWindowException{
+ if (startTime < 0 || startTime >= 1440 || endTime < 0 || endTime >= 1440) {
+ throw new InvalidWindowException("startTime and endTime must be inside [0, 1440)");
+ }
+ mPolicy.clear();
+ mPolicy.putInt(KEY_POLICY_TYPE, TYPE_INSTALL_WINDOWED);
+ mPolicy.putInt(KEY_INSTALL_WINDOW_START, startTime);
+ mPolicy.putInt(KEY_INSTALL_WINDOW_END, endTime);
+ }
+
+ /**
+ * Set the OTA policy to: block installation for a maximum period of two weeks. After the
+ * block expires the OTA will be installed automatically.
+ */
+ public void setPostponeInstallPolicy() {
+ mPolicy.clear();
+ mPolicy.putInt(KEY_POLICY_TYPE, TYPE_POSTPONE);
+ }
+
+ /**
+ * Returns the type of OTA policy.
+ *
+ * @return an integer, either one of {@link #TYPE_INSTALL_AUTOMATIC},
+ * {@link #TYPE_INSTALL_WINDOWED} and {@link #TYPE_POSTPONE}, or -1 if no policy has been set.
+ */
+ @OtaPolicyType
+ public int getPolicyType() {
+ return mPolicy.getInt(KEY_POLICY_TYPE, -1);
+ }
+
+ /**
+ * Get the start of the maintenance window.
+ *
+ * @return the start of the maintenance window measured as the number of minutes from midnight,
+ * or -1 if the policy does not have a maintenance window.
+ */
+ public int getInstallWindowStart() {
+ if (getPolicyType() == TYPE_INSTALL_WINDOWED) {
+ return mPolicy.getInt(KEY_INSTALL_WINDOW_START, -1);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Get the end of the maintenance window.
+ *
+ * @return the end of the maintenance window measured as the number of minutes from midnight,
+ * or -1 if the policy does not have a maintenance window.
+ */
+ public int getInstallWindowEnd() {
+ if (getPolicyType() == TYPE_INSTALL_WINDOWED) {
+ return mPolicy.getInt(KEY_INSTALL_WINDOW_END, -1);
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mPolicy.toString();
+ }
+
+ /**
+ * Exception thrown by {@link OtaPolicy#setWindowedInstallPolicy(int, int)} in case the
+ * specified window is invalid.
+ */
+ public static class InvalidWindowException extends Exception {
+ public InvalidWindowException(String reason) {
+ super(reason);
+ }
+ }
+}
+
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a1be2d..3d22e52 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -311,6 +311,7 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
<protected-broadcast android:name="android.service.persistentdata.action.WIPE_IF_ALLOWED" />
+ <protected-broadcast android:name="android.app.action.OTA_POLICY_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
index c766183..2661643 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.AtomicFile;
import android.util.Slog;
@@ -59,6 +60,7 @@
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_COMPONENT_NAME = "component";
private static final String ATTR_USERID = "userId";
+ private static final String TAG_OTA_POLICY = "ota-policy";
private AtomicFile fileForWriting;
@@ -75,6 +77,9 @@
// Internal state for the profile owner packages.
private final HashMap<Integer, OwnerInfo> mProfileOwners = new HashMap<Integer, OwnerInfo>();
+ // Local OTA policy controllable by device owner.
+ private PersistableBundle mOtaPolicy;
+
// Private default constructor.
private DeviceOwner() {
}
@@ -187,6 +192,18 @@
return mProfileOwners.keySet();
}
+ PersistableBundle getOtaPolicy() {
+ return mOtaPolicy;
+ }
+
+ void setOtaPolicy(PersistableBundle otaPolicy) {
+ mOtaPolicy = otaPolicy;
+ }
+
+ void clearOtaPolicy() {
+ mOtaPolicy = null;
+ }
+
boolean hasDeviceOwner() {
return mDeviceOwner != null;
}
@@ -273,6 +290,8 @@
profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName);
}
mProfileOwners.put(userId, profileOwnerInfo);
+ } else if (TAG_OTA_POLICY.equals(tag)) {
+ mOtaPolicy = PersistableBundle.restoreFromXml(parser);
} else {
throw new XmlPullParserException(
"Unexpected tag in device owner file: " + tag);
@@ -338,6 +357,17 @@
out.endTag(null, TAG_PROFILE_OWNER);
}
}
+
+ // Write OTA policy tag
+ if (mOtaPolicy != null) {
+ out.startTag(null, TAG_OTA_POLICY);
+ try {
+ mOtaPolicy.saveToXml(out);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Failed to save OTA policy", e);
+ }
+ out.endTag(null, TAG_OTA_POLICY);
+ }
out.endDocument();
out.flush();
finishWrite(outputStream);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3677132..3ba72ac7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5872,4 +5872,26 @@
}
return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
+
+ @Override
+ public void setOtaPolicy(ComponentName who, PersistableBundle policy) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (policy == null) {
+ mDeviceOwner.clearOtaPolicy();
+ } else {
+ mDeviceOwner.setOtaPolicy(policy);
+ }
+ mDeviceOwner.writeOwnerFile();
+ }
+ mContext.sendBroadcastAsUser(new Intent(DevicePolicyManager.ACTION_OTA_POLICY_CHANGED),
+ UserHandle.OWNER);
+ }
+
+ @Override
+ public PersistableBundle getOtaPolicy() {
+ synchronized (this) {
+ return mDeviceOwner.getOtaPolicy();
+ }
+ }
}