/* * Copyright (C) 2024 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.server.deviceconfig; import static com.android.server.deviceconfig.Flags.enableCustomRebootTimeConfigurations; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.server.deviceconfig.resources.R; import java.util.Optional; /** * Contains the timing configuration for unattended reboot. * * @hide */ public class RebootTimingConfiguration { private static final String TAG = "RebootTimingConfiguration"; private static final String RESOURCES_PACKAGE = "com.android.server.deviceconfig.resources"; /** * Special value that can be set for both the window start and end hour configs to disable * reboot time window constraint. */ private static final int ALLOW_ALL_HOURS = -1; private static final Pair DEFAULT_REBOOT_WINDOW_HOURS = Pair.create(3, 5); private static final int DEFAULT_REBOOT_FREQUENCY_DAYS = 2; private final Optional> mRebootWindowStartEndHour; private final int mRebootFrequencyDays; public RebootTimingConfiguration(Context context) { if (enableCustomRebootTimeConfigurations()) { Optional resourcesContext = getResourcesContext(context); if (resourcesContext.isPresent()) { Resources res = resourcesContext.get().getResources(); mRebootWindowStartEndHour = getRebootWindowStartEndHour(res); mRebootFrequencyDays = getRebootFrequencyDays(res); Log.d(TAG, "reboot start/end hour: " + mRebootWindowStartEndHour + "; frequency-days: " + mRebootFrequencyDays); return; } else { Log.d(TAG, "Unable to get resources context"); } } mRebootWindowStartEndHour = Optional.of(DEFAULT_REBOOT_WINDOW_HOURS); mRebootFrequencyDays = DEFAULT_REBOOT_FREQUENCY_DAYS; } @VisibleForTesting RebootTimingConfiguration( int rebootWindowStartHour, int rebootWindowEndHour, int rebootFrequencyDays) { mRebootWindowStartEndHour = getRebootWindowStartEndHour(rebootWindowStartHour, rebootWindowEndHour); assert(isDayValid(rebootFrequencyDays)); mRebootFrequencyDays = rebootFrequencyDays; } /** * Returns a {@link Pair} of integers, where the first element represents the reboot window * start hour (inclusive), and the second element represents the reboot window end hour * (exclusive). If the start hour is bigger than the end hour, it means that the end hour falls * on the next day. * *

Returns an empty {@link Optional} if there is no reboot window constraint (i.e. if all * hours are valid). * *

Use {@link #isHourWithinRebootHourWindow(int)} to validate if an hour falls within the * window defined in this configuration. */ public Optional> getRebootWindowStartEndHour() { return mRebootWindowStartEndHour; } /** Returns the reboot frequency in days. */ public int getRebootFrequencyDays() { return mRebootFrequencyDays; } /** * Returns {@code true} if the provided {@code hour} falls within the reboot window defined in * this configuration. */ public boolean isHourWithinRebootHourWindow(int hour) { if (!isHourValid(hour)) { return false; } if (mRebootWindowStartEndHour.isEmpty()) { return true; } Pair rebootWindowStartEndHour = mRebootWindowStartEndHour.get(); if (rebootWindowStartEndHour.first < rebootWindowStartEndHour.second) { // Window lies in a single day. return hour >= rebootWindowStartEndHour.first && hour < rebootWindowStartEndHour.second; } // Window ends on the next day. return hour >= rebootWindowStartEndHour.first || hour < rebootWindowStartEndHour.second; } private static Optional> getRebootWindowStartEndHour(Resources res) { return getRebootWindowStartEndHour( res.getInteger(R.integer.config_unattendedRebootStartHour), res.getInteger(R.integer.config_unattendedRebootEndHour)); } private static Optional> getRebootWindowStartEndHour( int configStartHour, int configEndHour) { if (configStartHour == ALLOW_ALL_HOURS && configEndHour == ALLOW_ALL_HOURS) { return Optional.empty(); } if (!isHourValid(configStartHour) || !isHourValid(configEndHour) || configStartHour == configEndHour) { return Optional.of(DEFAULT_REBOOT_WINDOW_HOURS); } return Optional.of(Pair.create(configStartHour, configEndHour)); } private static int getRebootFrequencyDays(Resources res) { int frequencyDays = res.getInteger(R.integer.config_unattendedRebootFrequencyDays); if (!isDayValid(frequencyDays)) { frequencyDays = DEFAULT_REBOOT_FREQUENCY_DAYS; } return frequencyDays; } private static boolean isHourValid(int hour) { return hour >= 0 && hour <= 23; } private static boolean isHourWindowValid(int startHour, int endHour) { return isHourValid(startHour) && isHourValid(endHour) && startHour != endHour; } private static boolean isDayValid(int day) { return day > 0; } private static Optional getResourcesContext(Context context) { ServiceResourcesHelper resourcesHelper = ServiceResourcesHelper.get(context); Optional resourcesPackageName = resourcesHelper.getResourcesPackageName(); if (resourcesPackageName.isPresent()) { try { return Optional.ofNullable( context.createPackageContext(resourcesPackageName.get(), 0)); } catch (NameNotFoundException e) { Log.e(TAG, "Error in creating resources package context.", e); } } return Optional.empty(); } }