| /* |
| * 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 com.android.cts.deviceowner; |
| |
| import static android.provider.Settings.Global.AIRPLANE_MODE_ON; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import android.app.admin.DevicePolicyManager; |
| import android.app.admin.FreezePeriod; |
| import android.app.admin.SystemUpdatePolicy; |
| import android.app.admin.SystemUpdatePolicy.ValidationFailedException; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.icu.util.Calendar; |
| import android.os.Parcel; |
| import android.provider.Settings; |
| import android.provider.Settings.Global; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.provider.Settings; |
| import android.provider.Settings.Global; |
| |
| import com.google.common.collect.ImmutableList; |
| import java.time.LocalDate; |
| import java.time.MonthDay; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Test {@link SystemUpdatePolicy}, {@link DevicePolicyManager#setSystemUpdatePolicy} and |
| * {@link DevicePolicyManager#getSystemUpdatePolicy} |
| */ |
| public class SystemUpdatePolicyTest extends BaseDeviceOwnerTest { |
| |
| private static final String TAG = "SystemUpdatePolicyTest"; |
| |
| private static final int TIMEOUT_MS = 20_000; |
| private static final int TIMEOUT_SEC = 5; |
| |
| private final Semaphore mPolicyChangedSemaphore = new Semaphore(0); |
| private final Semaphore mTimeChangedSemaphore = new Semaphore(0); |
| private final BroadcastReceiver policyChangedReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| if (DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED.equals(action)) { |
| mPolicyChangedSemaphore.release(); |
| } else if (Intent.ACTION_TIME_CHANGED.equals(action)) { |
| mTimeChangedSemaphore.release(); |
| } |
| } |
| }; |
| |
| private int mSavedAutoTimeConfig; |
| private LocalDate mSavedSystemDate; |
| private boolean mRestoreDate; |
| private int mSavedAirplaneMode; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED); |
| filter.addAction(Intent.ACTION_TIME_CHANGED); |
| mContext.registerReceiver(policyChangedReceiver, filter); |
| clearFreezeRecord(); |
| mSavedAutoTimeConfig = Settings.Global.getInt(mContext.getContentResolver(), |
| Global.AUTO_TIME, 0); |
| executeShellCommand("settings put global auto_time 0"); |
| mSavedSystemDate = LocalDate.now(); |
| mRestoreDate = false; |
| mSavedAirplaneMode = getAirplaneMode(); |
| Log.i(TAG, "Before testing, AIRPLANE_MODE is set to: " + mSavedAirplaneMode); |
| if (mSavedAirplaneMode == 0) { |
| // No need to set mode if AirplaneMode is 1 or error. |
| setAirplaneModeAndWaitBroadcast(1); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| mDevicePolicyManager.setSystemUpdatePolicy(getWho(), null); |
| clearFreezeRecord(); |
| if (mRestoreDate) { |
| setSystemDate(mSavedSystemDate); |
| } |
| executeShellCommand("settings put global auto_time", |
| Integer.toString(mSavedAutoTimeConfig)); |
| // This needs to happen last since setSystemDate() relies on the receiver for |
| // synchronization. |
| mContext.unregisterReceiver(policyChangedReceiver); |
| if (mSavedAirplaneMode == 0) { |
| // Restore AirplaneMode value. |
| setAirplaneModeAndWaitBroadcast(0); |
| } |
| super.tearDown(); |
| } |
| |
| public void testSetEmptytInstallPolicy() { |
| testPolicy(null); |
| } |
| |
| public void testSetAutomaticInstallPolicy() { |
| testPolicy(SystemUpdatePolicy.createAutomaticInstallPolicy()); |
| } |
| |
| public void testSetWindowedInstallPolicy() { |
| testPolicy(SystemUpdatePolicy.createWindowedInstallPolicy(0, 720)); |
| } |
| |
| public void testSetPostponeInstallPolicy() { |
| testPolicy(SystemUpdatePolicy.createPostponeInstallPolicy()); |
| } |
| |
| public void testShouldFailInvalidWindowPolicy() throws Exception { |
| try { |
| SystemUpdatePolicy.createWindowedInstallPolicy(24 * 60 + 1, 720); |
| fail("Invalid window start should not be accepted."); |
| } catch (IllegalArgumentException expected) { } |
| try { |
| SystemUpdatePolicy.createWindowedInstallPolicy(-1, 720); |
| fail("Invalid window start should not be accepted."); |
| } catch (IllegalArgumentException expected) { } |
| try { |
| SystemUpdatePolicy.createWindowedInstallPolicy(0, 24 * 60 + 1); |
| fail("Invalid window end should not be accepted."); |
| } catch (IllegalArgumentException expected) { } |
| try { |
| SystemUpdatePolicy.createWindowedInstallPolicy(0, -1); |
| fail("Invalid window end should not be accepted."); |
| } catch (IllegalArgumentException expected) { } |
| } |
| |
| public void testFreezePeriodValidation() { |
| // Dates are in MM-DD format |
| validateFreezePeriodsSucceeds("01-01", "01-02"); |
| validateFreezePeriodsSucceeds("01-31", "01-31"); |
| validateFreezePeriodsSucceeds("11-01", "01-15"); |
| validateFreezePeriodsSucceeds("02-01", "02-29"); |
| validateFreezePeriodsSucceeds("03-01", "03-31", "09-01", "09-30"); |
| validateFreezePeriodsSucceeds("10-01", "10-31", "12-31", "01-31"); |
| validateFreezePeriodsSucceeds("01-01", "02-28", "05-01", "06-30", "09-01", "10-31"); |
| validateFreezePeriodsSucceeds("11-02", "01-15", "03-18", "04-30", "08-01", "08-30"); |
| |
| // full overlap |
| validateFreezePeriodsFailsOverlap("12-01", "01-31", "12-25", "01-15"); |
| // partial overlap |
| validateFreezePeriodsFailsOverlap("03-01", "03-31", "03-15", "01-01"); |
| // touching interval |
| validateFreezePeriodsFailsOverlap("01-31", "01-31", "02-01", "02-01"); |
| validateFreezePeriodsFailsOverlap("12-01", "12-31", "04-01", "04-01", "01-01", "01-30"); |
| |
| // entire year |
| validateFreezePeriodsFailsTooLong("01-01", "12-31"); |
| // Regular long period |
| validateFreezePeriodsSucceeds("01-01", "03-31", "06-01", "08-29"); |
| validateFreezePeriodsFailsTooLong("01-01", "03-31", "06-01", "08-30"); |
| // long period spanning across year end |
| validateFreezePeriodsSucceeds("11-01", "01-29"); |
| validateFreezePeriodsFailsTooLong("11-01", "01-30"); |
| // Leap year handling |
| validateFreezePeriodsSucceeds("12-01", "02-28"); |
| validateFreezePeriodsFailsTooLong("12-01", "03-01"); |
| |
| // Regular short separation |
| validateFreezePeriodsFailsTooClose( "01-01", "01-01", "01-03", "01-03"); |
| // Short interval spans across end of year |
| validateFreezePeriodsSucceeds("01-31", "03-01", "11-01", "12-01"); |
| validateFreezePeriodsFailsTooClose("01-30", "03-01", "11-01", "12-01"); |
| // Short separation is after wrapped period |
| validateFreezePeriodsSucceeds("03-03", "03-31", "12-31", "01-01"); |
| validateFreezePeriodsFailsTooClose("03-02", "03-31", "12-31", "01-01"); |
| // Short separation including Feb 29 |
| validateFreezePeriodsSucceeds("12-01", "01-15", "03-17", "04-01"); |
| validateFreezePeriodsFailsTooClose("12-01", "01-15", "03-16", "04-01"); |
| // Short separation including Feb 29 |
| validateFreezePeriodsSucceeds("01-01", "02-28", "04-30", "06-01"); |
| validateFreezePeriodsSucceeds("01-01", "02-29", "04-30", "06-01"); |
| validateFreezePeriodsFailsTooClose("01-01", "03-01", "04-30", "06-01"); |
| } |
| |
| public void testFreezePeriodCanBeSetAndChanged() throws Exception { |
| setPolicyWithFreezePeriod("11-02", "01-15", "03-18", "04-30"); |
| // Set to a different period should work |
| setPolicyWithFreezePeriod("08-01", "08-30"); |
| // Clear freeze period should work |
| setPolicyWithFreezePeriod(); |
| // Set to the original period should work |
| setPolicyWithFreezePeriod("11-02", "01-15", "03-18", "04-30"); |
| } |
| |
| public void testFreezePeriodCannotSetIfTooCloseToPrevious() throws Exception { |
| setSystemDate(LocalDate.of(2018, 2, 28)); |
| setPolicyWithFreezePeriod("01-01", "03-01", "06-01", "06-30"); |
| // Clear policy |
| mDevicePolicyManager.setSystemUpdatePolicy(getWho(), null); |
| // Set to a conflict period (too close with previous period [2-28, 2-28]) should fail, |
| // despite the previous policy was cleared from the system just now. |
| try { |
| setPolicyWithFreezePeriod("04-29", "04-30"); |
| fail("Did no flag invalid period"); |
| } catch (ValidationFailedException e) { |
| assertEquals(e.getMessage(), |
| ValidationFailedException.ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, |
| e.getErrorCode()); |
| } |
| // This should succeed as the new freeze period is exactly 60 days away. |
| setPolicyWithFreezePeriod("04-30", "04-30"); |
| } |
| |
| public void testFreezePeriodCannotSetIfTooLongWhenCombinedWithPrevious() throws Exception { |
| setSystemDate(LocalDate.of(2012, 4, 1)); |
| setPolicyWithFreezePeriod("03-01", "05-01"); |
| setSystemDate(LocalDate.of(2012, 4, 30)); |
| // Despite the wait for broadcast in setSystemDate(), TIME_CHANGED broadcast is asynchronous |
| // so give DevicePolicyManagerService more time to receive TIME_CHANGED and to update the |
| // freeze period record. |
| Thread.sleep(5000); |
| // Set to a conflict period (too long when combined with previous period [04-01, 04-30]) |
| // should fail |
| try { |
| setPolicyWithFreezePeriod("04-30", "06-30"); |
| fail("Did no flag invalid period"); |
| } catch (SystemUpdatePolicy.ValidationFailedException e) { |
| assertEquals(e.getMessage(), |
| ValidationFailedException.ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, |
| e.getErrorCode()); |
| } |
| // This should succeed as the combined length (59 days) is just below threshold (90 days). |
| setPolicyWithFreezePeriod("05-01", "06-29"); |
| } |
| |
| public void testFreezePeriodForOneYear() throws Exception { |
| // Set a normal period every day for 365 days |
| for (int i = 1; i <= 365; i++) { |
| // Add two days so the test date range wraps around year-end |
| setSystemDate(LocalDate.ofYearDay(2019, i).plusDays(2)); |
| testFreezePeriodCanBeSetAndChanged(); |
| } |
| } |
| |
| public void testWriteSystemUpdatePolicyToParcel() { |
| final Parcel parcel1 = Parcel.obtain(); |
| try { |
| final SystemUpdatePolicy policy1 = SystemUpdatePolicy.createAutomaticInstallPolicy(); |
| policy1.writeToParcel(parcel1, 0); |
| parcel1.setDataPosition(0); |
| final SystemUpdatePolicy copy1 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel1); |
| assertThat(copy1).isNotNull(); |
| assertSystemUpdatePoliciesEqual(policy1, copy1); |
| } finally { |
| parcel1.recycle(); |
| } |
| |
| final Parcel parcel2 = Parcel.obtain(); |
| try { |
| final SystemUpdatePolicy policy2 = SystemUpdatePolicy |
| .createWindowedInstallPolicy(0, 720); |
| policy2.writeToParcel(parcel2, 0); |
| parcel2.setDataPosition(0); |
| final SystemUpdatePolicy copy2 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel2); |
| assertThat(copy2).isNotNull(); |
| assertSystemUpdatePoliciesEqual(policy2, copy2); |
| } finally { |
| parcel2.recycle(); |
| } |
| |
| final Parcel parcel3 = Parcel.obtain(); |
| try { |
| final SystemUpdatePolicy policy3 = SystemUpdatePolicy.createPostponeInstallPolicy(); |
| policy3.writeToParcel(parcel3, 0); |
| parcel3.setDataPosition(0); |
| final SystemUpdatePolicy copy3 = SystemUpdatePolicy.CREATOR.createFromParcel(parcel3); |
| assertThat(copy3).isNotNull(); |
| assertSystemUpdatePoliciesEqual(policy3, copy3); |
| } finally { |
| parcel3.recycle(); |
| } |
| } |
| |
| public void testWriteValidationFailedExceptionToParcel() { |
| final List<FreezePeriod> freezePeriods = |
| ImmutableList.of(new FreezePeriod(MonthDay.of(1, 10), MonthDay.of(1, 9))); |
| try { |
| SystemUpdatePolicy.createAutomaticInstallPolicy().setFreezePeriods(freezePeriods); |
| fail("ValidationFailedException not thrown for invalid freeze period."); |
| } catch (ValidationFailedException e) { |
| final Parcel parcel = Parcel.obtain(); |
| e.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| |
| final ValidationFailedException copy = |
| ValidationFailedException.CREATOR.createFromParcel(parcel); |
| |
| assertThat(copy).isNotNull(); |
| assertThat(e.getErrorCode()).isEqualTo(copy.getErrorCode()); |
| assertThat(e.getMessage()).isEqualTo(copy.getMessage()); |
| } |
| } |
| |
| private void assertSystemUpdatePoliciesEqual(SystemUpdatePolicy policy, |
| SystemUpdatePolicy copy) { |
| assertThat(policy.getInstallWindowStart()).isEqualTo(copy.getInstallWindowStart()); |
| assertThat(policy.getInstallWindowEnd()).isEqualTo(copy.getInstallWindowEnd()); |
| assertFreezePeriodListsEqual(policy.getFreezePeriods(), copy.getFreezePeriods()); |
| assertThat(policy.getPolicyType()).isEqualTo(copy.getPolicyType()); |
| } |
| |
| private void assertFreezePeriodListsEqual(List<FreezePeriod> original, |
| List<FreezePeriod> copy) { |
| assertThat(original).isNotNull(); |
| assertThat(copy).isNotNull(); |
| assertThat(original.size()).isEqualTo(copy.size()); |
| for (FreezePeriod period1 : original) { |
| assertThat(period1).isNotNull(); |
| assertFreezePeriodListContains(copy, period1); |
| } |
| for (FreezePeriod period1 : copy) { |
| assertThat(period1).isNotNull(); |
| assertFreezePeriodListContains(original, period1); |
| } |
| } |
| |
| private void assertFreezePeriodListContains(List<FreezePeriod> list, FreezePeriod period) { |
| for (FreezePeriod other : list) { |
| assertThat(other).isNotNull(); |
| if (areFreezePeriodsEqual(period, other)) { |
| return; |
| } |
| } |
| final List<String> printablePeriods = new ArrayList<>(); |
| for (FreezePeriod printablePeriod : list) { |
| printablePeriods.add(printablePeriod.toString()); |
| } |
| fail(String.format("FreezePeriod list [%s] does not contain the specified period %s.", |
| String.join(", ", printablePeriods), period)); |
| } |
| |
| private boolean areFreezePeriodsEqual(FreezePeriod period1, FreezePeriod period2) { |
| return period1 != null && period2 != null |
| && Objects.equals(period1.getStart(), period2.getStart()) |
| && Objects.equals(period1.getEnd(), period2.getEnd()); |
| } |
| |
| private void testPolicy(SystemUpdatePolicy policy) { |
| mDevicePolicyManager.setSystemUpdatePolicy(getWho(), policy); |
| waitForPolicyChangedBroadcast(); |
| SystemUpdatePolicy newPolicy = mDevicePolicyManager.getSystemUpdatePolicy(); |
| if (policy == null) { |
| assertNull(newPolicy); |
| } else { |
| assertNotNull(newPolicy); |
| assertEquals(policy.toString(), newPolicy.toString()); |
| assertEquals(policy.getPolicyType(), newPolicy.getPolicyType()); |
| if (policy.getPolicyType() == SystemUpdatePolicy.TYPE_INSTALL_WINDOWED) { |
| assertEquals(policy.getInstallWindowStart(), newPolicy.getInstallWindowStart()); |
| assertEquals(policy.getInstallWindowEnd(), newPolicy.getInstallWindowEnd()); |
| } |
| } |
| } |
| |
| private void setPolicyWithFreezePeriod(String...dates) { |
| SystemUpdatePolicy policy = SystemUpdatePolicy.createPostponeInstallPolicy(); |
| setFreezePeriods(policy, dates); |
| mDevicePolicyManager.setSystemUpdatePolicy(getWho(), policy); |
| |
| List<FreezePeriod> loadedFreezePeriods = mDevicePolicyManager |
| .getSystemUpdatePolicy().getFreezePeriods(); |
| assertEquals(dates.length / 2, loadedFreezePeriods.size()); |
| for (int i = 0; i < dates.length; i += 2) { |
| assertEquals(parseMonthDay(dates[i]), loadedFreezePeriods.get(i / 2).getStart()); |
| assertEquals(parseMonthDay(dates[i + 1]), loadedFreezePeriods.get(i / 2).getEnd()); |
| } |
| } |
| |
| private void validateFreezePeriodsSucceeds(String...dates) { |
| SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy(); |
| setFreezePeriods(p, dates); |
| } |
| |
| private void validateFreezePeriodsFails(int errorCode, String... dates) { |
| SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy(); |
| try { |
| setFreezePeriods(p, dates); |
| fail("Exception not thrown for dates: " + String.join(" ", dates)); |
| } catch (SystemUpdatePolicy.ValidationFailedException e) { |
| assertEquals("Exception not expected: " + e.getMessage(), |
| errorCode,e.getErrorCode()); |
| } |
| } |
| |
| private void validateFreezePeriodsFailsOverlap(String... dates) { |
| validateFreezePeriodsFails(ValidationFailedException.ERROR_DUPLICATE_OR_OVERLAP, dates); |
| } |
| |
| private void validateFreezePeriodsFailsTooLong(String... dates) { |
| validateFreezePeriodsFails(ValidationFailedException.ERROR_NEW_FREEZE_PERIOD_TOO_LONG, |
| dates); |
| } |
| |
| private void validateFreezePeriodsFailsTooClose(String... dates) { |
| validateFreezePeriodsFails(ValidationFailedException.ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, |
| dates); |
| } |
| |
| //dates are in MM-DD format |
| private void setFreezePeriods(SystemUpdatePolicy policy, String... dates) { |
| List<FreezePeriod> periods = new ArrayList<>(); |
| for (int i = 0; i < dates.length; i+= 2) { |
| periods.add(new FreezePeriod(parseMonthDay(dates[i]), parseMonthDay(dates[i + 1]))); |
| } |
| policy.setFreezePeriods(periods); |
| } |
| |
| private MonthDay parseMonthDay(String date) { |
| return MonthDay.of(Integer.parseInt(date.substring(0, 2)), |
| Integer.parseInt(date.substring(3, 5))); |
| } |
| |
| private void clearFreezeRecord() throws Exception { |
| executeShellCommand("dpm", "clear-freeze-period-record"); |
| } |
| |
| private void setSystemDate(LocalDate date) throws Exception { |
| mRestoreDate = true; |
| Calendar c = Calendar.getInstance(); |
| c.set(Calendar.YEAR, date.getYear()); |
| c.set(Calendar.MONTH, date.getMonthValue() - 1); |
| c.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth()); |
| mDevicePolicyManager.setTime(getWho(), c.getTimeInMillis()); |
| waitForTimeChangedBroadcast(); |
| } |
| |
| private void waitForPolicyChangedBroadcast() { |
| try { |
| assertTrue("Timeout while waiting for broadcast.", |
| mPolicyChangedSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| } catch (InterruptedException e) { |
| fail("Interrupted while waiting for broadcast."); |
| } |
| } |
| |
| private void waitForTimeChangedBroadcast() { |
| try { |
| assertTrue("Timeout while waiting for broadcast.", |
| mTimeChangedSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| } catch (InterruptedException e) { |
| fail("Interrupted while waiting for broadcast."); |
| } |
| } |
| |
| private int getAirplaneMode() throws Settings.SettingNotFoundException { |
| int airplaneMode = 0xFF; |
| try { |
| airplaneMode = Settings.Global.getInt(mContext.getContentResolver(), |
| Settings.Global.AIRPLANE_MODE_ON); |
| } catch (Settings.SettingNotFoundException e) { |
| airplaneMode = 0xFF; |
| // if the mode is not supported, return a non zero value. |
| Log.i(TAG, "Airplane mode is not found in Settings. Skipping AirplaneMode update"); |
| } finally { |
| return airplaneMode; |
| } |
| } |
| |
| private boolean setAirplaneModeAndWaitBroadcast (int state) throws Exception { |
| Log.i(TAG, "setAirplaneModeAndWaitBroadcast setting state(0=disable, 1=enable): " + state); |
| |
| final CountDownLatch latch = new CountDownLatch(1); |
| BroadcastReceiver receiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Log.i(TAG, "Received broadcast for AirplaneModeUpdate"); |
| latch.countDown(); |
| } |
| }; |
| mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); |
| try { |
| Settings.Global.putInt(mContext.getContentResolver(), AIRPLANE_MODE_ON, state); |
| if (!latch.await(TIMEOUT_SEC, TimeUnit.SECONDS)) { |
| Log.d(TAG, "Failed to receive broadcast in " + TIMEOUT_SEC + "sec"); |
| return false; |
| } |
| } finally { |
| mContext.unregisterReceiver(receiver); |
| } |
| return true; |
| } |
| } |