Loading api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -6759,9 +6759,11 @@ package android.app.admin { method public static android.app.admin.SystemUpdatePolicy createPostponeInstallPolicy(); method public static android.app.admin.SystemUpdatePolicy createWindowedInstallPolicy(int, int); method public int describeContents(); method public java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>> getFreezePeriods(); method public int getInstallWindowEnd(); method public int getInstallWindowStart(); method public int getPolicyType(); method public android.app.admin.SystemUpdatePolicy setFreezePeriods(java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>>); 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_INSTALL_AUTOMATIC = 1; // 0x1 Loading @@ -6769,6 +6771,18 @@ package android.app.admin { field public static final int TYPE_POSTPONE = 3; // 0x3 } public static final class SystemUpdatePolicy.ValidationFailedException extends java.lang.IllegalArgumentException implements android.os.Parcelable { method public int describeContents(); method public int getErrorCode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy.ValidationFailedException> CREATOR; field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; // 0x5 field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; // 0x4 field public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; // 0x1 field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; // 0x3 field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; // 0x2 } } package android.app.assist { cmds/dpm/src/com/android/commands/dpm/Dpm.java +14 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public final class Dpm extends BaseCommand { private static final String COMMAND_SET_DEVICE_OWNER = "set-device-owner"; private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner"; private static final String COMMAND_REMOVE_ACTIVE_ADMIN = "remove-active-admin"; private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record"; private IDevicePolicyManager mDevicePolicyManager; private int mUserId = UserHandle.USER_SYSTEM; Loading Loading @@ -75,7 +76,11 @@ public final class Dpm extends BaseCommand { "\n" + "dpm remove-active-admin: Disables an active admin, the admin must have declared" + " android:testOnly in the application in its manifest. This will also remove" + " device and profile owners\n"); " device and profile owners\n" + "\n" + "dpm " + COMMAND_CLEAR_FREEZE_PERIOD_RECORD + ": clears framework-maintained " + "record of past freeze periods that the device went through. For use during " + "feature development to prevent triggering restriction on setting freeze periods"); } @Override Loading @@ -101,6 +106,9 @@ public final class Dpm extends BaseCommand { case COMMAND_REMOVE_ACTIVE_ADMIN: runRemoveActiveAdmin(); break; case COMMAND_CLEAR_FREEZE_PERIOD_RECORD: runClearFreezePeriodRecord(); break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); } Loading Loading @@ -190,6 +198,11 @@ public final class Dpm extends BaseCommand { + mComponent.toShortString() + " for user " + mUserId); } private void runClearFreezePeriodRecord() throws RemoteException { mDevicePolicyManager.clearSystemUpdatePolicyFreezePeriodRecord(); System.out.println("Success"); } private ComponentName parseComponentName(String component) { ComponentName cn = ComponentName.unflattenFromString(component); if (cn == null) { Loading core/java/android/app/admin/DevicePolicyManager.java +31 −0 Original line number Diff line number Diff line Loading @@ -7540,13 +7540,28 @@ public class DevicePolicyManager { /** * Called by device owners to set a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcasted. * <p> * If the supplied system update policy has freeze periods set but the freeze periods do not * meet 90-day maximum length or 60-day minimum separation requirement set out in * {@link SystemUpdatePolicy#setFreezePeriods}, * {@link SystemUpdatePolicy.ValidationFailedException} will the thrown. Note that the system * keeps a record of freeze periods the device experienced previously, and combines them with * the new freeze periods to be set when checking the maximum freeze length and minimum freeze * separation constraints. As a result, freeze periods that passed validation during * {@link SystemUpdatePolicy#setFreezePeriods} might fail the additional checks here due to * the freeze period history. If this is causing issues during development, * {@code adb shell dpm clear-freeze-period-record} can be used to clear the record. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. All * components in the device owner package can set system update policies and the most * recent policy takes effect. * @param policy the new policy, or {@code null} to clear the current policy. * @throws SecurityException if {@code admin} is not a device owner. * @throws IllegalArgumentException if the policy type or maintenance window is not valid. * @throws SystemUpdatePolicy.ValidationFailedException if the policy's freeze period does not * meet the requirement. * @see SystemUpdatePolicy * @see SystemUpdatePolicy#setFreezePeriods(List) */ public void setSystemUpdatePolicy(@NonNull ComponentName admin, SystemUpdatePolicy policy) { throwIfParentInstance("setSystemUpdatePolicy"); Loading Loading @@ -7576,6 +7591,22 @@ public class DevicePolicyManager { return null; } /** * Reset record of previous system update freeze period the device went through. * Only callable by ADB. * @hide */ public void clearSystemUpdatePolicyFreezePeriodRecord() { throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); if (mService == null) { return; } try { mService.clearSystemUpdatePolicyFreezePeriodRecord(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Called by a device owner or profile owner of secondary users that is affiliated with the * device to disable the keyguard altogether. Loading core/java/android/app/admin/FreezeInterval.java 0 → 100644 +299 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.app.admin.SystemUpdatePolicy.ValidationFailedException; import android.util.Log; import android.util.Pair; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; /** * An interval representing one freeze period which repeats annually. We use the number of days * since the start of (non-leap) year to define the start and end dates of an interval, both * inclusive. If the end date is smaller than the start date, the interval is considered wrapped * around the year-end. As far as an interval is concerned, February 29th should be treated as * if it were February 28th: so an interval starting or ending on February 28th are not * distinguishable from an interval on February 29th. When calulating interval length or * distance between two dates, February 29th is also disregarded. * * @see SystemUpdatePolicy#setFreezePeriods * @hide */ public class FreezeInterval { private static final String TAG = "FreezeInterval"; private static final int DUMMY_YEAR = 2001; static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year final int mStartDay; // [1,365] final int mEndDay; // [1,365] FreezeInterval(int startDay, int endDay) { if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) { throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay); } mStartDay = startDay; mEndDay = endDay; } int getLength() { return getEffectiveEndDay() - mStartDay + 1; } boolean isWrapped() { return mEndDay < mStartDay; } /** * Returns the effective end day, taking wrapping around year-end into consideration */ int getEffectiveEndDay() { if (!isWrapped()) { return mEndDay; } else { return mEndDay + DAYS_IN_YEAR; } } boolean contains(LocalDate localDate) { final int daysOfYear = dayOfYearDisregardLeapYear(localDate); if (!isWrapped()) { // ---[start---now---end]--- return (mStartDay <= daysOfYear) && (daysOfYear <= mEndDay); } else { // ---end]---[start---now--- // or ---now---end]---[start--- return (mStartDay <= daysOfYear) || (daysOfYear <= mEndDay); } } /** * 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 * current interval (in real calendar dates) that includes now. If the interval does not * include now, the returned dates represents the next future interval. * The result will always have the same month and dayOfMonth value as the non-instantiated * interval itself. */ Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) { final int nowDays = dayOfYearDisregardLeapYear(now); final int startYearAdjustment, endYearAdjustment; if (contains(now)) { // current interval if (mStartDay <= nowDays) { // ----------[start---now---end]--- // or ---end]---[start---now---------- startYearAdjustment = 0; endYearAdjustment = isWrapped() ? 1 : 0; } else /* nowDays <= mEndDay */ { // or ---now---end]---[start---------- startYearAdjustment = -1; endYearAdjustment = 0; } } else { // next interval if (mStartDay > nowDays) { // ----------now---[start---end]--- // or ---end]---now---[start---------- startYearAdjustment = 0; endYearAdjustment = isWrapped() ? 1 : 0; } else /* mStartDay <= nowDays */ { // or ---[start---end]---now---------- startYearAdjustment = 1; endYearAdjustment = 1; } } final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear( now.getYear() + startYearAdjustment); final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear( now.getYear() + endYearAdjustment); return new Pair<>(startDate, endDate); } @Override public String toString() { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd"); return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - " + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter); } // Treat the supplied date as in a non-leap year and return its day of year. static int dayOfYearDisregardLeapYear(LocalDate date) { return date.withYear(DUMMY_YEAR).getDayOfYear(); } /** * Compute the number of days between first (inclusive) and second (exclusive), * treating all years in between as non-leap. */ public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) { return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second) + DAYS_IN_YEAR * (first.getYear() - second.getYear()); } /** * Sort, de-duplicate and merge an interval list * * Instead of using any fancy logic for merging intervals which has loads of corner cases, * simply flatten the interval onto a list of 365 calendar days and recreate the interval list * from that. * * This method should return a list of intervals with the following post-conditions: * 1. Interval.startDay in strictly ascending order * 2. No two intervals should overlap or touch * 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) { boolean[] taken = new boolean[DAYS_IN_YEAR]; // First convert the intervals into flat array for (FreezeInterval interval : intervals) { for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) { taken[(i - 1) % DAYS_IN_YEAR] = true; } } // Then reconstruct intervals from the array List<FreezeInterval> result = new ArrayList<>(); int i = 0; while (i < DAYS_IN_YEAR) { if (!taken[i]) { i++; continue; } final int intervalStart = i + 1; while (i < DAYS_IN_YEAR && taken[i]) i++; result.add(new FreezeInterval(intervalStart, i)); } // Check if the last entry can be merged to the first entry to become one single // wrapped interval final int lastIndex = result.size() - 1; if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR && result.get(0).mStartDay == 1) { FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay, result.get(0).mEndDay); result.set(lastIndex, wrappedInterval); result.remove(0); } return result; } /** * Verifies if the supplied freeze periods satisfies the constraints set out in * {@link SystemUpdatePolicy#setFreezePeriods(List)}, and in particular, any single freeze * period cannot exceed {@link SystemUpdatePolicy#FREEZE_PERIOD_MAX_LENGTH} days, and two freeze * periods need to be at least {@link SystemUpdatePolicy#FREEZE_PERIOD_MIN_SEPARATION} days * apart. * * @hide */ protected static void validatePeriods(List<FreezeInterval> periods) { List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); if (allPeriods.size() != periods.size()) { throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods(); } for (int i = 0; i < allPeriods.size(); i++) { FreezeInterval current = allPeriods.get(i); if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze " + "period " + current + " is too long: " + current.getLength() + " days"); } FreezeInterval previous = i > 0 ? allPeriods.get(i - 1) : allPeriods.get(allPeriods.size() - 1); if (previous != current) { final int separation; if (i == 0 && !previous.isWrapped()) { // -->[current]---[-previous-]<--- separation = current.mStartDay + (DAYS_IN_YEAR - previous.mEndDay) - 1; } else { // --[previous]<--->[current]--------- // OR ----prev---]<--->[current]---[prev- separation = current.mStartDay - previous.mEndDay - 1; } if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooClose("Freeze" + " periods " + previous + " and " + current + " are too close " + "together: " + separation + " days apart"); } } } } /** * Verifies that the current freeze periods are still legal, considering the previous freeze * periods the device went through. In particular, when combined with the previous freeze * period, the maximum freeze length or the minimum freeze separation should not be violated. * * @hide */ protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods, LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) { return; } if (prevPeriodStart.isAfter(now) || prevPeriodEnd.isAfter(now)) { Log.w(TAG, "Previous period (" + prevPeriodStart + "," + prevPeriodEnd + ") is after" + " current date " + now); // Clock was adjusted backwards. We can continue execution though, the separation // and length validation below still works under this condition. } List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); // Given current time now, find the freeze period that's either current, or the one // that's immediately afterwards. For the later case, it might be after the year-end, // but this can only happen if there is only one freeze period. FreezeInterval curOrNextFreezePeriod = allPeriods.get(0); for (FreezeInterval interval : allPeriods) { if (interval.contains(now) || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) { curOrNextFreezePeriod = interval; break; } } Pair<LocalDate, LocalDate> curOrNextFreezeDates = curOrNextFreezePeriod .toCurrentOrFutureRealDates(now); if (now.isAfter(curOrNextFreezeDates.first)) { curOrNextFreezeDates = new Pair<>(now, curOrNextFreezeDates.second); } if (curOrNextFreezeDates.first.isAfter(curOrNextFreezeDates.second)) { throw new IllegalStateException("Current freeze dates inverted: " + curOrNextFreezeDates.first + "-" + curOrNextFreezeDates.second); } // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second; long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first, prevPeriodEnd) - 1; if (separation > 0) { // Two intervals do not overlap, check separation if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { throw ValidationFailedException.combinedPeriodTooClose("Previous freeze period " + "too close to new period: " + separation + ", " + periodsDescription); } } else { // Two intervals overlap, check combined length long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second, prevPeriodStart) + 1; if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period " + "exceeds maximum days: " + length + ", " + periodsDescription); } } } } core/java/android/app/admin/IDevicePolicyManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -295,6 +295,7 @@ interface IDevicePolicyManager { void setSystemUpdatePolicy(in ComponentName who, in SystemUpdatePolicy policy); SystemUpdatePolicy getSystemUpdatePolicy(); void clearSystemUpdatePolicyFreezePeriodRecord(); boolean setKeyguardDisabled(in ComponentName admin, boolean disabled); boolean setStatusBarDisabled(in ComponentName who, boolean disabled); Loading Loading
api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -6759,9 +6759,11 @@ package android.app.admin { method public static android.app.admin.SystemUpdatePolicy createPostponeInstallPolicy(); method public static android.app.admin.SystemUpdatePolicy createWindowedInstallPolicy(int, int); method public int describeContents(); method public java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>> getFreezePeriods(); method public int getInstallWindowEnd(); method public int getInstallWindowStart(); method public int getPolicyType(); method public android.app.admin.SystemUpdatePolicy setFreezePeriods(java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>>); 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_INSTALL_AUTOMATIC = 1; // 0x1 Loading @@ -6769,6 +6771,18 @@ package android.app.admin { field public static final int TYPE_POSTPONE = 3; // 0x3 } public static final class SystemUpdatePolicy.ValidationFailedException extends java.lang.IllegalArgumentException implements android.os.Parcelable { method public int describeContents(); method public int getErrorCode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy.ValidationFailedException> CREATOR; field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; // 0x5 field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; // 0x4 field public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; // 0x1 field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; // 0x3 field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; // 0x2 } } package android.app.assist {
cmds/dpm/src/com/android/commands/dpm/Dpm.java +14 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public final class Dpm extends BaseCommand { private static final String COMMAND_SET_DEVICE_OWNER = "set-device-owner"; private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner"; private static final String COMMAND_REMOVE_ACTIVE_ADMIN = "remove-active-admin"; private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record"; private IDevicePolicyManager mDevicePolicyManager; private int mUserId = UserHandle.USER_SYSTEM; Loading Loading @@ -75,7 +76,11 @@ public final class Dpm extends BaseCommand { "\n" + "dpm remove-active-admin: Disables an active admin, the admin must have declared" + " android:testOnly in the application in its manifest. This will also remove" + " device and profile owners\n"); " device and profile owners\n" + "\n" + "dpm " + COMMAND_CLEAR_FREEZE_PERIOD_RECORD + ": clears framework-maintained " + "record of past freeze periods that the device went through. For use during " + "feature development to prevent triggering restriction on setting freeze periods"); } @Override Loading @@ -101,6 +106,9 @@ public final class Dpm extends BaseCommand { case COMMAND_REMOVE_ACTIVE_ADMIN: runRemoveActiveAdmin(); break; case COMMAND_CLEAR_FREEZE_PERIOD_RECORD: runClearFreezePeriodRecord(); break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); } Loading Loading @@ -190,6 +198,11 @@ public final class Dpm extends BaseCommand { + mComponent.toShortString() + " for user " + mUserId); } private void runClearFreezePeriodRecord() throws RemoteException { mDevicePolicyManager.clearSystemUpdatePolicyFreezePeriodRecord(); System.out.println("Success"); } private ComponentName parseComponentName(String component) { ComponentName cn = ComponentName.unflattenFromString(component); if (cn == null) { Loading
core/java/android/app/admin/DevicePolicyManager.java +31 −0 Original line number Diff line number Diff line Loading @@ -7540,13 +7540,28 @@ public class DevicePolicyManager { /** * Called by device owners to set a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcasted. * <p> * If the supplied system update policy has freeze periods set but the freeze periods do not * meet 90-day maximum length or 60-day minimum separation requirement set out in * {@link SystemUpdatePolicy#setFreezePeriods}, * {@link SystemUpdatePolicy.ValidationFailedException} will the thrown. Note that the system * keeps a record of freeze periods the device experienced previously, and combines them with * the new freeze periods to be set when checking the maximum freeze length and minimum freeze * separation constraints. As a result, freeze periods that passed validation during * {@link SystemUpdatePolicy#setFreezePeriods} might fail the additional checks here due to * the freeze period history. If this is causing issues during development, * {@code adb shell dpm clear-freeze-period-record} can be used to clear the record. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. All * components in the device owner package can set system update policies and the most * recent policy takes effect. * @param policy the new policy, or {@code null} to clear the current policy. * @throws SecurityException if {@code admin} is not a device owner. * @throws IllegalArgumentException if the policy type or maintenance window is not valid. * @throws SystemUpdatePolicy.ValidationFailedException if the policy's freeze period does not * meet the requirement. * @see SystemUpdatePolicy * @see SystemUpdatePolicy#setFreezePeriods(List) */ public void setSystemUpdatePolicy(@NonNull ComponentName admin, SystemUpdatePolicy policy) { throwIfParentInstance("setSystemUpdatePolicy"); Loading Loading @@ -7576,6 +7591,22 @@ public class DevicePolicyManager { return null; } /** * Reset record of previous system update freeze period the device went through. * Only callable by ADB. * @hide */ public void clearSystemUpdatePolicyFreezePeriodRecord() { throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); if (mService == null) { return; } try { mService.clearSystemUpdatePolicyFreezePeriodRecord(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Called by a device owner or profile owner of secondary users that is affiliated with the * device to disable the keyguard altogether. Loading
core/java/android/app/admin/FreezeInterval.java 0 → 100644 +299 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.app.admin.SystemUpdatePolicy.ValidationFailedException; import android.util.Log; import android.util.Pair; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; /** * An interval representing one freeze period which repeats annually. We use the number of days * since the start of (non-leap) year to define the start and end dates of an interval, both * inclusive. If the end date is smaller than the start date, the interval is considered wrapped * around the year-end. As far as an interval is concerned, February 29th should be treated as * if it were February 28th: so an interval starting or ending on February 28th are not * distinguishable from an interval on February 29th. When calulating interval length or * distance between two dates, February 29th is also disregarded. * * @see SystemUpdatePolicy#setFreezePeriods * @hide */ public class FreezeInterval { private static final String TAG = "FreezeInterval"; private static final int DUMMY_YEAR = 2001; static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year final int mStartDay; // [1,365] final int mEndDay; // [1,365] FreezeInterval(int startDay, int endDay) { if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) { throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay); } mStartDay = startDay; mEndDay = endDay; } int getLength() { return getEffectiveEndDay() - mStartDay + 1; } boolean isWrapped() { return mEndDay < mStartDay; } /** * Returns the effective end day, taking wrapping around year-end into consideration */ int getEffectiveEndDay() { if (!isWrapped()) { return mEndDay; } else { return mEndDay + DAYS_IN_YEAR; } } boolean contains(LocalDate localDate) { final int daysOfYear = dayOfYearDisregardLeapYear(localDate); if (!isWrapped()) { // ---[start---now---end]--- return (mStartDay <= daysOfYear) && (daysOfYear <= mEndDay); } else { // ---end]---[start---now--- // or ---now---end]---[start--- return (mStartDay <= daysOfYear) || (daysOfYear <= mEndDay); } } /** * 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 * current interval (in real calendar dates) that includes now. If the interval does not * include now, the returned dates represents the next future interval. * The result will always have the same month and dayOfMonth value as the non-instantiated * interval itself. */ Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) { final int nowDays = dayOfYearDisregardLeapYear(now); final int startYearAdjustment, endYearAdjustment; if (contains(now)) { // current interval if (mStartDay <= nowDays) { // ----------[start---now---end]--- // or ---end]---[start---now---------- startYearAdjustment = 0; endYearAdjustment = isWrapped() ? 1 : 0; } else /* nowDays <= mEndDay */ { // or ---now---end]---[start---------- startYearAdjustment = -1; endYearAdjustment = 0; } } else { // next interval if (mStartDay > nowDays) { // ----------now---[start---end]--- // or ---end]---now---[start---------- startYearAdjustment = 0; endYearAdjustment = isWrapped() ? 1 : 0; } else /* mStartDay <= nowDays */ { // or ---[start---end]---now---------- startYearAdjustment = 1; endYearAdjustment = 1; } } final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear( now.getYear() + startYearAdjustment); final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear( now.getYear() + endYearAdjustment); return new Pair<>(startDate, endDate); } @Override public String toString() { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd"); return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - " + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter); } // Treat the supplied date as in a non-leap year and return its day of year. static int dayOfYearDisregardLeapYear(LocalDate date) { return date.withYear(DUMMY_YEAR).getDayOfYear(); } /** * Compute the number of days between first (inclusive) and second (exclusive), * treating all years in between as non-leap. */ public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) { return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second) + DAYS_IN_YEAR * (first.getYear() - second.getYear()); } /** * Sort, de-duplicate and merge an interval list * * Instead of using any fancy logic for merging intervals which has loads of corner cases, * simply flatten the interval onto a list of 365 calendar days and recreate the interval list * from that. * * This method should return a list of intervals with the following post-conditions: * 1. Interval.startDay in strictly ascending order * 2. No two intervals should overlap or touch * 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) { boolean[] taken = new boolean[DAYS_IN_YEAR]; // First convert the intervals into flat array for (FreezeInterval interval : intervals) { for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) { taken[(i - 1) % DAYS_IN_YEAR] = true; } } // Then reconstruct intervals from the array List<FreezeInterval> result = new ArrayList<>(); int i = 0; while (i < DAYS_IN_YEAR) { if (!taken[i]) { i++; continue; } final int intervalStart = i + 1; while (i < DAYS_IN_YEAR && taken[i]) i++; result.add(new FreezeInterval(intervalStart, i)); } // Check if the last entry can be merged to the first entry to become one single // wrapped interval final int lastIndex = result.size() - 1; if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR && result.get(0).mStartDay == 1) { FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay, result.get(0).mEndDay); result.set(lastIndex, wrappedInterval); result.remove(0); } return result; } /** * Verifies if the supplied freeze periods satisfies the constraints set out in * {@link SystemUpdatePolicy#setFreezePeriods(List)}, and in particular, any single freeze * period cannot exceed {@link SystemUpdatePolicy#FREEZE_PERIOD_MAX_LENGTH} days, and two freeze * periods need to be at least {@link SystemUpdatePolicy#FREEZE_PERIOD_MIN_SEPARATION} days * apart. * * @hide */ protected static void validatePeriods(List<FreezeInterval> periods) { List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); if (allPeriods.size() != periods.size()) { throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods(); } for (int i = 0; i < allPeriods.size(); i++) { FreezeInterval current = allPeriods.get(i); if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze " + "period " + current + " is too long: " + current.getLength() + " days"); } FreezeInterval previous = i > 0 ? allPeriods.get(i - 1) : allPeriods.get(allPeriods.size() - 1); if (previous != current) { final int separation; if (i == 0 && !previous.isWrapped()) { // -->[current]---[-previous-]<--- separation = current.mStartDay + (DAYS_IN_YEAR - previous.mEndDay) - 1; } else { // --[previous]<--->[current]--------- // OR ----prev---]<--->[current]---[prev- separation = current.mStartDay - previous.mEndDay - 1; } if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooClose("Freeze" + " periods " + previous + " and " + current + " are too close " + "together: " + separation + " days apart"); } } } } /** * Verifies that the current freeze periods are still legal, considering the previous freeze * periods the device went through. In particular, when combined with the previous freeze * period, the maximum freeze length or the minimum freeze separation should not be violated. * * @hide */ protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods, LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) { return; } if (prevPeriodStart.isAfter(now) || prevPeriodEnd.isAfter(now)) { Log.w(TAG, "Previous period (" + prevPeriodStart + "," + prevPeriodEnd + ") is after" + " current date " + now); // Clock was adjusted backwards. We can continue execution though, the separation // and length validation below still works under this condition. } List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); // Given current time now, find the freeze period that's either current, or the one // that's immediately afterwards. For the later case, it might be after the year-end, // but this can only happen if there is only one freeze period. FreezeInterval curOrNextFreezePeriod = allPeriods.get(0); for (FreezeInterval interval : allPeriods) { if (interval.contains(now) || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) { curOrNextFreezePeriod = interval; break; } } Pair<LocalDate, LocalDate> curOrNextFreezeDates = curOrNextFreezePeriod .toCurrentOrFutureRealDates(now); if (now.isAfter(curOrNextFreezeDates.first)) { curOrNextFreezeDates = new Pair<>(now, curOrNextFreezeDates.second); } if (curOrNextFreezeDates.first.isAfter(curOrNextFreezeDates.second)) { throw new IllegalStateException("Current freeze dates inverted: " + curOrNextFreezeDates.first + "-" + curOrNextFreezeDates.second); } // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second; long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first, prevPeriodEnd) - 1; if (separation > 0) { // Two intervals do not overlap, check separation if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) { throw ValidationFailedException.combinedPeriodTooClose("Previous freeze period " + "too close to new period: " + separation + ", " + periodsDescription); } } else { // Two intervals overlap, check combined length long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second, prevPeriodStart) + 1; if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period " + "exceeds maximum days: " + length + ", " + periodsDescription); } } } }
core/java/android/app/admin/IDevicePolicyManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -295,6 +295,7 @@ interface IDevicePolicyManager { void setSystemUpdatePolicy(in ComponentName who, in SystemUpdatePolicy policy); SystemUpdatePolicy getSystemUpdatePolicy(); void clearSystemUpdatePolicyFreezePeriodRecord(); boolean setKeyguardDisabled(in ComponentName admin, boolean disabled); boolean setStatusBarDisabled(in ComponentName who, boolean disabled); Loading