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