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; 18 19 import static android.health.connect.Constants.DEFAULT_INT; 20 21 import static com.android.server.healthconnect.HealthConnectDailyJobs.HC_DAILY_JOB; 22 import static com.android.server.healthconnect.exportimport.ExportImportJobs.PERIODIC_EXPORT_JOB_NAME; 23 import static com.android.server.healthconnect.migration.MigrationConstants.MIGRATION_COMPLETE_JOB_NAME; 24 import static com.android.server.healthconnect.migration.MigrationConstants.MIGRATION_PAUSE_JOB_NAME; 25 26 import android.annotation.NonNull; 27 import android.annotation.UserIdInt; 28 import android.app.job.JobInfo; 29 import android.app.job.JobParameters; 30 import android.app.job.JobScheduler; 31 import android.app.job.JobService; 32 import android.health.connect.Constants; 33 import android.util.Slog; 34 35 import com.android.server.healthconnect.exportimport.ExportImportJobs; 36 import com.android.server.healthconnect.exportimport.ExportManager; 37 import com.android.server.healthconnect.migration.MigrationStateChangeJob; 38 39 import java.time.Clock; 40 import java.util.Objects; 41 42 /** 43 * Health Connect wrapper around JobService. 44 * 45 * @hide 46 */ 47 public class HealthConnectDailyService extends JobService { 48 public static final String EXTRA_USER_ID = "user_id"; 49 public static final String EXTRA_JOB_NAME_KEY = "job_name"; 50 private static final String TAG = "HealthConnectDailyService"; 51 @UserIdInt private static volatile int sCurrentUserId; 52 53 /** 54 * Routes the job to the right place based on the job name, after performing common checks., 55 * 56 * <p>Please handle exceptions for each task within the task. Do not crash the job as it might 57 * result in failure of other tasks being triggered from the job. 58 */ 59 @Override onStartJob(@onNull JobParameters params)60 public boolean onStartJob(@NonNull JobParameters params) { 61 int userId = params.getExtras().getInt(EXTRA_USER_ID, /* defaultValue= */ DEFAULT_INT); 62 String jobName = params.getExtras().getString(EXTRA_JOB_NAME_KEY); 63 if (userId == DEFAULT_INT || userId != sCurrentUserId) { 64 // This job is no longer valid, the service for this user should have been stopped. 65 // Just ignore this request in case we still got the request. 66 return false; 67 } 68 69 if (Objects.isNull(jobName)) { 70 return false; 71 } 72 73 // This service executes each incoming job on a Handler running on the application's 74 // main thread. This means that we must offload the execution logic to background executor. 75 switch (jobName) { 76 case HC_DAILY_JOB: 77 HealthConnectThreadScheduler.scheduleInternalTask( 78 () -> { 79 HealthConnectDailyJobs.execute(getApplicationContext(), params); 80 jobFinished(params, false); 81 }); 82 return true; 83 case MIGRATION_COMPLETE_JOB_NAME: 84 HealthConnectThreadScheduler.scheduleInternalTask( 85 () -> { 86 MigrationStateChangeJob.executeMigrationCompletionJob( 87 getApplicationContext()); 88 jobFinished(params, false); 89 }); 90 return true; 91 case MIGRATION_PAUSE_JOB_NAME: 92 HealthConnectThreadScheduler.scheduleInternalTask( 93 () -> { 94 MigrationStateChangeJob.executeMigrationPauseJob( 95 getApplicationContext()); 96 jobFinished(params, false); 97 }); 98 return true; 99 case PERIODIC_EXPORT_JOB_NAME: 100 HealthConnectThreadScheduler.scheduleInternalTask( 101 () -> { 102 boolean isExportSuccessful = 103 ExportImportJobs.executePeriodicExportJob( 104 new ExportManager( 105 getApplicationContext(), Clock.systemUTC())); 106 // If the export is not successful, reschedule the job. 107 jobFinished(params, !isExportSuccessful); 108 }); 109 return true; 110 default: 111 Slog.w(TAG, "Job name " + jobName + " is not supported."); 112 break; 113 } 114 return false; 115 } 116 117 /** Called when job needs to be stopped. Don't do anything here and let the job be killed. */ 118 @Override onStopJob(@onNull JobParameters params)119 public boolean onStopJob(@NonNull JobParameters params) { 120 return false; 121 } 122 123 /** Start periodically scheduling this service for {@code userId}. */ schedule( @onNull JobScheduler jobScheduler, @UserIdInt int userId, @NonNull JobInfo jobInfo)124 public static void schedule( 125 @NonNull JobScheduler jobScheduler, @UserIdInt int userId, @NonNull JobInfo jobInfo) { 126 Objects.requireNonNull(jobScheduler); 127 sCurrentUserId = userId; 128 129 int result = jobScheduler.schedule(jobInfo); 130 if (result != JobScheduler.RESULT_SUCCESS) { 131 Slog.e( 132 TAG, 133 "Failed to schedule the job: " 134 + jobInfo.getExtras().getString(EXTRA_JOB_NAME_KEY)); 135 } else if (Constants.DEBUG) { 136 Slog.d( 137 TAG, 138 "Scheduled a job successfully: " 139 + jobInfo.getExtras().getString(EXTRA_JOB_NAME_KEY)); 140 } 141 } 142 } 143