1 /* 2 * Copyright (C) 2023 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.healthconnect.migration; 18 19 import static android.health.connect.HealthConnectDataState.MIGRATION_STATE_ALLOWED; 20 import static android.health.connect.HealthConnectDataState.MIGRATION_STATE_IN_PROGRESS; 21 22 import static com.android.server.healthconnect.migration.MigrationConstants.COUNT_DEFAULT; 23 import static com.android.server.healthconnect.migration.MigrationConstants.EXTRA_USER_ID; 24 import static com.android.server.healthconnect.migration.MigrationConstants.INTERVAL_DEFAULT; 25 26 import android.app.job.JobInfo; 27 import android.app.job.JobScheduler; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.health.connect.Constants; 31 import android.os.PersistableBundle; 32 import android.util.Slog; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.healthconnect.HealthConnectDeviceConfigManager; 37 38 import java.time.Duration; 39 import java.util.Objects; 40 import java.util.UUID; 41 42 /** 43 * This class schedules the {@link MigrationBroadcastJobService} service. 44 * 45 * @hide 46 */ 47 public final class MigrationBroadcastScheduler { 48 49 private static final String TAG = "MigrationBroadcastScheduler"; 50 51 @VisibleForTesting 52 static final String MIGRATION_BROADCAST_NAMESPACE = "HEALTH_CONNECT_MIGRATION_BROADCAST"; 53 54 private final Object mLock = new Object(); 55 private final HealthConnectDeviceConfigManager mHealthConnectDeviceConfigManager = 56 HealthConnectDeviceConfigManager.getInitialisedInstance(); 57 58 @GuardedBy("mLock") 59 private int mUserId; 60 MigrationBroadcastScheduler(int userId)61 public MigrationBroadcastScheduler(int userId) { 62 mUserId = userId; 63 } 64 65 /** Sets userId. Invoked when the user is switched. */ setUserId(int userId)66 public void setUserId(int userId) { 67 synchronized (mLock) { 68 mUserId = userId; 69 } 70 } 71 72 /*** 73 * Cancels all previously scheduled {@link MigrationBroadcastJobService} service jobs. 74 * Retrieves the requiredCount and requiredInterval corresponding to the given migration 75 * state. 76 * If the requiredInterval is greater than or equal to the minimum interval allowed for 77 * periodic jobs, a periodic job is scheduled, else a set of non-periodic jobs are 78 * pre-scheduled. 79 */ scheduleNewJobs(Context context)80 public void scheduleNewJobs(Context context) { 81 synchronized (mLock) { 82 int migrationState = MigrationStateManager.getInitialisedInstance().getMigrationState(); 83 84 if (Constants.DEBUG) { 85 Slog.d(TAG, "Current migration state: " + migrationState); 86 Slog.d(TAG, "Current user: " + mUserId); 87 } 88 89 Objects.requireNonNull(context.getSystemService(JobScheduler.class)) 90 .forNamespace(MIGRATION_BROADCAST_NAMESPACE) 91 .cancelAll(); 92 93 try { 94 // When migration state is not in progress or allowed the count will be zero and no 95 // job will be scheduled. 96 if (getRequiredCount(migrationState) > 0) { 97 createJobLocked( 98 Math.max( 99 getRequiredInterval(migrationState), 100 JobInfo.getMinPeriodMillis()), 101 context); 102 } 103 } catch (Exception e) { 104 Slog.e(TAG, "Exception while creating job : ", e); 105 } 106 } 107 } 108 109 /*** 110 * Creates a new {@link MigrationBroadcastJobService} job, to which it passes the user id in a 111 * PersistableBundle object. 112 * 113 * @param requiredInterval Time interval between each successive job for that current 114 * migration state 115 * @param context Context 116 * 117 * @throws Exception if migration broadcast job scheduling fails. 118 */ 119 @GuardedBy("mLock") createJobLocked(long requiredInterval, Context context)120 private void createJobLocked(long requiredInterval, Context context) throws Exception { 121 ComponentName schedulerServiceComponent = 122 new ComponentName(context, MigrationBroadcastJobService.class); 123 124 int uuid = UUID.randomUUID().toString().hashCode(); 125 int jobId = String.valueOf(mUserId + uuid).hashCode(); 126 127 final PersistableBundle extras = new PersistableBundle(); 128 extras.putInt(EXTRA_USER_ID, mUserId); 129 130 JobInfo.Builder builder = 131 new JobInfo.Builder(jobId, schedulerServiceComponent).setExtras(extras); 132 133 builder.setPeriodic(requiredInterval); 134 135 JobScheduler jobScheduler = 136 Objects.requireNonNull(context.getSystemService(JobScheduler.class)) 137 .forNamespace(MIGRATION_BROADCAST_NAMESPACE); 138 int result = jobScheduler.schedule(builder.build()); 139 if (result == JobScheduler.RESULT_SUCCESS) { 140 if (Constants.DEBUG) { 141 Slog.d(TAG, "Successfully scheduled migration broadcast job"); 142 } 143 } else { 144 throw new Exception("Failed to schedule migration broadcast job"); 145 } 146 } 147 148 /** 149 * Returns the number of migration broadcast jobs to be scheduled for the given migration state. 150 */ 151 @VisibleForTesting getRequiredCount(int migrationState)152 int getRequiredCount(int migrationState) { 153 switch (migrationState) { 154 case MIGRATION_STATE_IN_PROGRESS: 155 return mHealthConnectDeviceConfigManager.getMigrationStateInProgressCount(); 156 case MIGRATION_STATE_ALLOWED: 157 return mHealthConnectDeviceConfigManager.getMigrationStateAllowedCount(); 158 default: 159 return COUNT_DEFAULT; 160 } 161 } 162 163 /** Returns the interval between each migration broadcast job for the given migration state. */ 164 @VisibleForTesting getRequiredInterval(int migrationState)165 long getRequiredInterval(int migrationState) { 166 switch (migrationState) { 167 case MIGRATION_STATE_IN_PROGRESS: 168 return calculateRequiredInterval( 169 mHealthConnectDeviceConfigManager.getInProgressStateTimeoutPeriod(), 170 getRequiredCount(MIGRATION_STATE_IN_PROGRESS)); 171 case MIGRATION_STATE_ALLOWED: 172 return calculateRequiredInterval( 173 mHealthConnectDeviceConfigManager.getNonIdleStateTimeoutPeriod(), 174 getRequiredCount(MIGRATION_STATE_ALLOWED)); 175 default: 176 return INTERVAL_DEFAULT; 177 } 178 } 179 calculateRequiredInterval(Duration timeoutPeriod, int maxBroadcastCount)180 private static long calculateRequiredInterval(Duration timeoutPeriod, int maxBroadcastCount) { 181 return timeoutPeriod.toMillis() / maxBroadcastCount; 182 } 183 } 184