Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 658d7bbf authored by Rubin Xu's avatar Rubin Xu
Browse files

Add System API for system update clients

Introduces the concept of installation option and new system API for system
update clients to convert a complex SystemUpdatePolicy into a simple action
and an effective time, under any given time. This should abstract away the
current complexity in SystemUpdatePolicy and all system update clients only
need to query the current installation option and act on it, without the need
to understand or implement other logic related to the policy.

Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases --test com.google.android.gts.devicepolicy.DeviceOwnerTest#testSystemUpdatePolicy --ignore-business-logic-failure
Test: runtest frameworks-services -c com.android.server.devicepolicy.SystemUpdatePolicyTest
Bug: 72939648
Bug: 64813061
Change-Id: I92537c7b99103a807d8adcbe2dc01b42a9740068
parent db72e3b4
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -30,8 +30,8 @@ package android {
    field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
    field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE";
    field public static final java.lang.String BIND_TELEPHONY_DATA_SERVICE = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
    field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
    field public static final java.lang.String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
    field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
    field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
    field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
    field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
@@ -431,6 +431,19 @@ package android.app.admin {
    field public static final int STATE_USER_UNMANAGED = 0; // 0x0
  }

  public class SystemUpdatePolicy implements android.os.Parcelable {
    method public int describeContents();
    method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy> CREATOR;
    field public static final int TYPE_PAUSE = 4; // 0x4
  }

  public static class SystemUpdatePolicy.InstallationOption {
    method public long getEffectiveTime();
    method public int getType();
  }

}

package android.app.backup {
+5 −1
Original line number Diff line number Diff line
@@ -84,6 +84,10 @@ public class FreezeInterval {
        }
    }

    boolean after(LocalDate localDate) {
        return mStartDay > dayOfYearDisregardLeapYear(localDate);
    }

    /**
     * Instantiate the current interval to real calendar dates, given a calendar date
     * {@code now}. If the interval contains now, the returned calendar dates should be the
@@ -161,7 +165,7 @@ public class FreezeInterval {
     *     3. At most one wrapped Interval remains, and it will be at the end of the list
     * @hide
     */
    private static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) {
    protected static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) {
        boolean[] taken = new boolean[DAYS_IN_YEAR];
        // First convert the intervals into flat array
        for (FreezeInterval interval : intervals) {
+175 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.TEXT;

import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -33,9 +34,15 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
@@ -103,6 +110,19 @@ public class SystemUpdatePolicy implements Parcelable {
     */
    public static final int TYPE_POSTPONE = 3;

    /**
     * Incoming system updates (including security updates) should be blocked. This flag is not
     * exposed to third-party apps (and any attempt to set it will raise exceptions). This is used
     * to represent the current installation option type to the privileged system update clients,
     * for example to indicate OTA freeze is currently in place or when system is outside a daily
     * maintenance window.
     *
     * @see InstallationOption
     * @hide
     */
    @SystemApi
    public static final int TYPE_PAUSE = 4;

    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";
@@ -460,6 +480,30 @@ public class SystemUpdatePolicy implements Parcelable {
        return null;
    }

    /**
     * Returns time (in milliseconds) until the start of the next freeze period, assuming now
     * is not within a freeze period.
     */
    private long timeUntilNextFreezePeriod(long now) {
        List<FreezeInterval> sortedPeriods = FreezeInterval.canonicalizeIntervals(mFreezePeriods);
        LocalDate nowDate = millisToDate(now);
        LocalDate nextFreezeStart = null;
        for (FreezeInterval interval : sortedPeriods) {
            if (interval.after(nowDate)) {
                nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first;
                break;
            } else if (interval.contains(nowDate)) {
                throw new IllegalArgumentException("Given date is inside a freeze period");
            }
        }
        if (nextFreezeStart == null) {
            // If no interval is after now, then it must be the one that starts at the beginning
            // of next year
            nextFreezeStart = sortedPeriods.get(0).toCurrentOrFutureRealDates(nowDate).first;
        }
        return dateToMillis(nextFreezeStart) - now;
    }

    /** @hide */
    public void validateFreezePeriods() {
        FreezeInterval.validatePeriods(mFreezePeriods);
@@ -472,6 +516,134 @@ public class SystemUpdatePolicy implements Parcelable {
                prevPeriodEnd, now);
    }

    /**
     * An installation option represents how system update clients should act on incoming system
     * updates and how long this action is valid for, given the current system update policy. Its
     * action could be one of the following
     * <ul>
     * <li> {@code TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and without
     * user intervention as soon as they become available.
     * <li> {@code TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days
     * <li> {@code TYPE_PAUSE} system updates should be postponed indefinitely until further notice
     * </ul>
     *
     * The effective time measures how long this installation option is valid for from the queried
     * time, in milliseconds.
     *
     * This is an internal API for system update clients.
     * @hide
     */
    @SystemApi
    public static class InstallationOption {
        private final int mType;
        private long mEffectiveTime;

        InstallationOption(int type, long effectiveTime) {
            this.mType = type;
            this.mEffectiveTime = effectiveTime;
        }

        public int getType() {
            return mType;
        }

        public long getEffectiveTime() {
            return mEffectiveTime;
        }

        /** @hide */
        protected void limitEffectiveTime(long otherTime) {
            mEffectiveTime = Long.min(mEffectiveTime, otherTime);
        }
    }

    /**
     * Returns the installation option at the specified time, under the current
     * {@code SystemUpdatePolicy} object. This is a convenience method for system update clients
     * so they can instantiate this policy at any given time and find out what to do with incoming
     * system updates, without the need of examining the overall policy structure.
     *
     * Normally the system update clients will query the current installation option by calling this
     * method with the current timestamp, and act on the returned option until its effective time
     * lapses. It can then query the latest option using a new timestamp. It should also listen
     * for {@code DevicePolicyManager#ACTION_SYSTEM_UPDATE_POLICY_CHANGED} broadcast, in case the
     * whole policy is updated.
     *
     * @param when At what time the intallation option is being queried, specified in number of
           milliseonds since the epoch.
     * @see InstallationOption
     * @hide
     */
    @SystemApi
    public InstallationOption getInstallationOptionAt(long when) {
        LocalDate whenDate = millisToDate(when);
        Pair<LocalDate, LocalDate> current = getCurrentFreezePeriod(whenDate);
        if (current != null) {
            return new InstallationOption(TYPE_PAUSE,
                    dateToMillis(roundUpLeapDay(current.second).plusDays(1)) - when);
        }
        // We are not within a freeze period, query the underlying policy.
        // But also consider the start of the next freeze period, which might
        // reduce the effective time of the current installation option
        InstallationOption option = getInstallationOptionRegardlessFreezeAt(when);
        if (mFreezePeriods.size() > 0) {
            option.limitEffectiveTime(timeUntilNextFreezePeriod(when));
        }
        return option;
    }

    private InstallationOption getInstallationOptionRegardlessFreezeAt(long when) {
        if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
            return new InstallationOption(mPolicyType, Long.MAX_VALUE);
        } else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
            Calendar query = Calendar.getInstance();
            query.setTimeInMillis(when);
            // Calculate the number of milliseconds since midnight of the time specified by when
            long whenMillis = TimeUnit.HOURS.toMillis(query.get(Calendar.HOUR_OF_DAY))
                    + TimeUnit.MINUTES.toMillis(query.get(Calendar.MINUTE))
                    + TimeUnit.SECONDS.toMillis(query.get(Calendar.SECOND))
                    + query.get(Calendar.MILLISECOND);
            long windowStartMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowStart);
            long windowEndMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowEnd);
            final long dayInMillis = TimeUnit.DAYS.toMillis(1);

            if ((windowStartMillis <= whenMillis && whenMillis <= windowEndMillis)
                    || ((windowStartMillis > windowEndMillis)
                    && (windowStartMillis <= whenMillis || whenMillis <= windowEndMillis))) {
                return new InstallationOption(TYPE_INSTALL_AUTOMATIC,
                        (windowEndMillis - whenMillis + dayInMillis) % dayInMillis);
            } else {
                return new InstallationOption(TYPE_PAUSE,
                        (windowStartMillis - whenMillis + dayInMillis) % dayInMillis);
            }
        } else {
            throw new RuntimeException("Unknown policy type");
        }
    }

    private static LocalDate roundUpLeapDay(LocalDate date) {
        if (date.isLeapYear() && date.getMonthValue() == 2 && date.getDayOfMonth() == 28) {
            return date.plusDays(1);
        } else {
            return date;
        }
    }

    /** Convert a timestamp since epoch to a LocalDate using default timezone, truncating
     * the hour/min/seconds part.
     */
    private static LocalDate millisToDate(long when) {
        return Instant.ofEpochMilli(when).atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * Returns the timestamp since epoch of a LocalDate, assuming the time is 00:00:00.
     */
    private static long dateToMillis(LocalDate when) {
        return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant()
                .toEpochMilli();
    }

    @Override
    public String toString() {
        return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, "
@@ -480,11 +652,13 @@ public class SystemUpdatePolicy implements Parcelable {
                mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(",")));
    }

    @SystemApi
    @Override
    public int describeContents() {
        return 0;
    }

    @SystemApi
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mPolicyType);
@@ -499,6 +673,7 @@ public class SystemUpdatePolicy implements Parcelable {
        }
    }

    @SystemApi
    public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR =
            new Parcelable.Creator<SystemUpdatePolicy>() {

+142 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Unit tests for {@link android.app.admin.SystemUpdatePolicy}.
@@ -253,6 +254,147 @@ public final class SystemUpdatePolicyTest {

    }

    @Test
    public void testInstallationOptionWithoutFreeze() {
        // Also duplicated at com.google.android.gts.deviceowner.SystemUpdatePolicyTest
        final long millis_2018_01_01 = TimeUnit.SECONDS.toMillis(1514764800);

        SystemUpdatePolicy p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        assertInstallationOption(SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, Long.MAX_VALUE,
                millis_2018_01_01, p);

        p = SystemUpdatePolicy.createPostponeInstallPolicy();
        assertInstallationOption(SystemUpdatePolicy.TYPE_POSTPONE, Long.MAX_VALUE,
                millis_2018_01_01, p);

        p = SystemUpdatePolicy.createWindowedInstallPolicy(120, 180); // 2:00 - 3:00
        // 00:00 is two hours before the next window
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(2),
                millis_2018_01_01, p);
        // 02:00 is within the current maintenance window, and one hour until the window ends
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.HOURS.toMillis(1),
                millis_2018_01_01 + TimeUnit.HOURS.toMillis(2), p);
        // 04:00 is 22 hours from the window next day
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(22),
                millis_2018_01_01 + TimeUnit.HOURS.toMillis(4), p);

        p = SystemUpdatePolicy.createWindowedInstallPolicy(22 * 60, 2 * 60); // 22:00 - 2:00
        // 21:00 is one hour from the next window
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(1),
                millis_2018_01_01 + TimeUnit.HOURS.toMillis(21), p);
        // 00:00 is two hours from the end of current window
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.HOURS.toMillis(2),
                millis_2018_01_01, p);
        // 03:00 is 22 hours from the window today
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(19),
                millis_2018_01_01 + TimeUnit.HOURS.toMillis(3), p);
    }

    @Test
    public void testInstallationOptionWithFreeze() throws Exception {
        final long millis_2016_02_29 = TimeUnit.SECONDS.toMillis(1456704000);
        final long millis_2017_01_31 = TimeUnit.SECONDS.toMillis(1485820800);
        final long millis_2017_02_28 = TimeUnit.SECONDS.toMillis(1488240000);
        final long millis_2018_01_01 = TimeUnit.SECONDS.toMillis(1514764800);
        final long millis_2018_08_01 = TimeUnit.SECONDS.toMillis(1533081600);

        SystemUpdatePolicy p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "01-01", "01-31");
        // Inside a freeze period
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(31),
                millis_2018_01_01, p);
        // Device is outside freeze between 2/28 to 12/31 inclusive
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(307),
                millis_2017_02_28, p);

        // Freeze period contains leap day Feb 29
        p = SystemUpdatePolicy.createPostponeInstallPolicy();
        setFreezePeriods(p, "02-01", "03-15");
        // Freezed until 3/31, note 2016 is a leap year
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(16),
                millis_2016_02_29, p);
        // Freezed until 3/31, note 2017 is not a leap year
        assertInstallationOption(SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(16),
                millis_2017_02_28, p);
        // Next freeze is 2018/2/1
        assertInstallationOption(SystemUpdatePolicy.TYPE_POSTPONE, TimeUnit.DAYS.toMillis(31),
                millis_2018_01_01, p);

        // Freeze period start on or right after leap day
        p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "03-01", "03-31");
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(1),
                millis_2016_02_29, p);
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(1),
                millis_2017_02_28, p);
        setFreezePeriods(p, "02-28", "03-15");
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(16),
                millis_2016_02_29, p);
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(16),
                millis_2017_02_28, p);

        // Freeze period end on or right after leap day
        p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "02-01", "02-28");
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(1),
                millis_2016_02_29, p);
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(1),
                millis_2017_02_28, p);
        p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "02-01", "03-01");
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(2),
                millis_2016_02_29, p);
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.DAYS.toMillis(2),
                millis_2017_02_28, p);

        // Freeze period with maintenance window
        p = SystemUpdatePolicy.createWindowedInstallPolicy(23 * 60, 1 * 60); // 23:00 - 1:00
        setFreezePeriods(p, "02-01", "02-28");
        // 00:00 is within the current window, outside freeze period
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.HOURS.toMillis(1),
                millis_2018_01_01, p);
        // Last day of feeze period, which ends in 22 hours
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(22),
                millis_2017_02_28 + TimeUnit.HOURS.toMillis(2), p);
        // Last day before the next freeze, and within window
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.HOURS.toMillis(1),
                millis_2017_01_31, p);
        // Last day before the next freeze, and there is still a partial maintenance window before
        // the freeze.
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_PAUSE, TimeUnit.HOURS.toMillis(19),
                millis_2017_01_31 + TimeUnit.HOURS.toMillis(4), p);

        // Two freeze periods
        p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "05-01", "06-01", "12-01", "01-31");
        // automatic policy for August, September, November and December
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(122),
                millis_2018_08_01, p);
    }

    private void assertInstallationOption(int expectedType, long expectedTime, long now,
            SystemUpdatePolicy p) {
        assertEquals(expectedType, p.getInstallationOptionAt(now).getType());
        assertEquals(expectedTime, p.getInstallationOptionAt(now).getEffectiveTime());
    }

    private void testFreezePeriodsSucceeds(String...dates) throws Exception {
        SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy();
        setFreezePeriods(p, dates);