1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.deviceconfig; 18 19 import static com.android.server.deviceconfig.Flags.enableCustomRebootTimeConfigurations; 20 21 import android.content.Context; 22 import android.content.pm.PackageManager.NameNotFoundException; 23 import android.content.res.Resources; 24 import android.util.Log; 25 import android.util.Pair; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.server.deviceconfig.resources.R; 29 30 import java.util.Optional; 31 32 /** 33 * Contains the timing configuration for unattended reboot. 34 * 35 * @hide 36 */ 37 public class RebootTimingConfiguration { 38 private static final String TAG = "RebootTimingConfiguration"; 39 40 private static final String RESOURCES_PACKAGE = 41 "com.android.server.deviceconfig.resources"; 42 43 /** 44 * Special value that can be set for both the window start and end hour configs to disable 45 * reboot time window constraint. 46 */ 47 private static final int ALLOW_ALL_HOURS = -1; 48 49 private static final Pair<Integer, Integer> DEFAULT_REBOOT_WINDOW_HOURS = Pair.create(3, 5); 50 51 private static final int DEFAULT_REBOOT_FREQUENCY_DAYS = 2; 52 53 private final Optional<Pair<Integer, Integer>> mRebootWindowStartEndHour; 54 private final int mRebootFrequencyDays; 55 RebootTimingConfiguration(Context context)56 public RebootTimingConfiguration(Context context) { 57 if (enableCustomRebootTimeConfigurations()) { 58 Optional<Context> resourcesContext = getResourcesContext(context); 59 if (resourcesContext.isPresent()) { 60 Resources res = resourcesContext.get().getResources(); 61 mRebootWindowStartEndHour = getRebootWindowStartEndHour(res); 62 mRebootFrequencyDays = getRebootFrequencyDays(res); 63 Log.d(TAG, 64 "reboot start/end hour: " + mRebootWindowStartEndHour 65 + "; frequency-days: " + mRebootFrequencyDays); 66 return; 67 } else { 68 Log.d(TAG, "Unable to get resources context"); 69 } 70 } 71 72 mRebootWindowStartEndHour = Optional.of(DEFAULT_REBOOT_WINDOW_HOURS); 73 mRebootFrequencyDays = DEFAULT_REBOOT_FREQUENCY_DAYS; 74 } 75 76 @VisibleForTesting RebootTimingConfiguration( int rebootWindowStartHour, int rebootWindowEndHour, int rebootFrequencyDays)77 RebootTimingConfiguration( 78 int rebootWindowStartHour, int rebootWindowEndHour, int rebootFrequencyDays) { 79 mRebootWindowStartEndHour = 80 getRebootWindowStartEndHour(rebootWindowStartHour, rebootWindowEndHour); 81 assert(isDayValid(rebootFrequencyDays)); 82 mRebootFrequencyDays = rebootFrequencyDays; 83 } 84 85 /** 86 * Returns a {@link Pair} of integers, where the first element represents the reboot window 87 * start hour (inclusive), and the second element represents the reboot window end hour 88 * (exclusive). If the start hour is bigger than the end hour, it means that the end hour falls 89 * on the next day. 90 * 91 * <p>Returns an empty {@link Optional} if there is no reboot window constraint (i.e. if all 92 * hours are valid). 93 * 94 * <p>Use {@link #isHourWithinRebootHourWindow(int)} to validate if an hour falls within the 95 * window defined in this configuration. 96 */ getRebootWindowStartEndHour()97 public Optional<Pair<Integer, Integer>> getRebootWindowStartEndHour() { 98 return mRebootWindowStartEndHour; 99 } 100 101 /** Returns the reboot frequency in days. */ getRebootFrequencyDays()102 public int getRebootFrequencyDays() { 103 return mRebootFrequencyDays; 104 } 105 106 /** 107 * Returns {@code true} if the provided {@code hour} falls within the reboot window defined in 108 * this configuration. 109 */ isHourWithinRebootHourWindow(int hour)110 public boolean isHourWithinRebootHourWindow(int hour) { 111 if (!isHourValid(hour)) { 112 return false; 113 } 114 if (mRebootWindowStartEndHour.isEmpty()) { 115 return true; 116 } 117 Pair<Integer, Integer> rebootWindowStartEndHour = mRebootWindowStartEndHour.get(); 118 if (rebootWindowStartEndHour.first < rebootWindowStartEndHour.second) { 119 // Window lies in a single day. 120 return hour >= rebootWindowStartEndHour.first && hour < rebootWindowStartEndHour.second; 121 } 122 // Window ends on the next day. 123 return hour >= rebootWindowStartEndHour.first || hour < rebootWindowStartEndHour.second; 124 } 125 getRebootWindowStartEndHour(Resources res)126 private static Optional<Pair<Integer, Integer>> getRebootWindowStartEndHour(Resources res) { 127 return getRebootWindowStartEndHour( 128 res.getInteger(R.integer.config_unattendedRebootStartHour), 129 res.getInteger(R.integer.config_unattendedRebootEndHour)); 130 } 131 getRebootWindowStartEndHour( int configStartHour, int configEndHour)132 private static Optional<Pair<Integer, Integer>> getRebootWindowStartEndHour( 133 int configStartHour, int configEndHour) { 134 if (configStartHour == ALLOW_ALL_HOURS && configEndHour == ALLOW_ALL_HOURS) { 135 return Optional.empty(); 136 } 137 if (!isHourValid(configStartHour) 138 || !isHourValid(configEndHour) 139 || configStartHour == configEndHour) { 140 return Optional.of(DEFAULT_REBOOT_WINDOW_HOURS); 141 } 142 return Optional.of(Pair.create(configStartHour, configEndHour)); 143 } 144 getRebootFrequencyDays(Resources res)145 private static int getRebootFrequencyDays(Resources res) { 146 int frequencyDays = res.getInteger(R.integer.config_unattendedRebootFrequencyDays); 147 if (!isDayValid(frequencyDays)) { 148 frequencyDays = DEFAULT_REBOOT_FREQUENCY_DAYS; 149 } 150 return frequencyDays; 151 } 152 isHourValid(int hour)153 private static boolean isHourValid(int hour) { 154 return hour >= 0 && hour <= 23; 155 } 156 isHourWindowValid(int startHour, int endHour)157 private static boolean isHourWindowValid(int startHour, int endHour) { 158 return isHourValid(startHour) && isHourValid(endHour) && startHour != endHour; 159 } 160 isDayValid(int day)161 private static boolean isDayValid(int day) { 162 return day > 0; 163 } 164 getResourcesContext(Context context)165 private static Optional<Context> getResourcesContext(Context context) { 166 ServiceResourcesHelper resourcesHelper = ServiceResourcesHelper.get(context); 167 Optional<String> resourcesPackageName = resourcesHelper.getResourcesPackageName(); 168 if (resourcesPackageName.isPresent()) { 169 try { 170 return Optional.ofNullable( 171 context.createPackageContext(resourcesPackageName.get(), 0)); 172 } catch (NameNotFoundException e) { 173 Log.e(TAG, "Error in creating resources package context.", e); 174 } 175 } 176 return Optional.empty(); 177 } 178 }