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.exportimport;
18 
19 import static com.android.healthfitness.flags.Flags.exportImport;
20 
21 import android.app.job.JobInfo;
22 import android.app.job.JobScheduler;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.os.PersistableBundle;
26 
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.server.healthconnect.HealthConnectDailyService;
30 import com.android.server.healthconnect.storage.ExportImportSettingsStorage;
31 
32 import java.time.Duration;
33 import java.util.Objects;
34 
35 /**
36  * Defines jobs related to Health Connect export and imports.
37  *
38  * @hide
39  */
40 public class ExportImportJobs {
41     private static final int MIN_JOB_ID = ExportImportJobs.class.hashCode();
42 
43     @VisibleForTesting static final String NAMESPACE = "HEALTH_CONNECT_IMPORT_EXPORT_JOBS";
44 
45     public static final String PERIODIC_EXPORT_JOB_NAME = "periodic_export_job";
46 
47     /** Schedule the periodic export job. */
schedulePeriodicExportJob(Context context, int userId)48     public static void schedulePeriodicExportJob(Context context, int userId) {
49         int periodInDays = ExportImportSettingsStorage.getScheduledExportPeriodInDays();
50         if (periodInDays <= 0) {
51             return;
52         }
53 
54         ComponentName componentName = new ComponentName(context, HealthConnectDailyService.class);
55         final PersistableBundle extras = new PersistableBundle();
56         extras.putInt(HealthConnectDailyService.EXTRA_USER_ID, userId);
57         extras.putString(HealthConnectDailyService.EXTRA_JOB_NAME_KEY, PERIODIC_EXPORT_JOB_NAME);
58         JobInfo.Builder builder =
59                 new JobInfo.Builder(MIN_JOB_ID + userId, componentName)
60                         .setRequiresCharging(true)
61                         .setRequiresDeviceIdle(true)
62                         .setPeriodic(
63                                 Duration.ofDays(periodInDays).toMillis(),
64                                 // Flex interval. The flex period begins after periodInDays - 4
65                                 // hours or 5% of periodInDays, whatever is bigger.
66                                 Duration.ofHours(4).toMillis())
67                         .setExtras(extras);
68 
69         HealthConnectDailyService.schedule(
70                 Objects.requireNonNull(context.getSystemService(JobScheduler.class))
71                         .forNamespace(NAMESPACE),
72                 userId,
73                 builder.build());
74     }
75 
76     /**
77      * Execute the periodic export job. It returns true if the export was successful (no need to
78      * reschedule the job). False otherwise.
79      */
80     // TODO(b/318484778): Use dependency injection instead of passing an instance to the method.
executePeriodicExportJob(ExportManager exportManager)81     public static boolean executePeriodicExportJob(ExportManager exportManager) {
82         if (!exportImport() || ExportImportSettingsStorage.getScheduledExportPeriodInDays() <= 0) {
83             // If there is no need to run the export, it counts like a success regarding job
84             // reschedule.
85             return true;
86         }
87         return exportManager.runExport();
88 
89         // TODO(b/325599089): Cancel job if flag is off.
90 
91         // TODO(b/325599089): Do we need an additional periodic / one-off task to make sure a single
92         //  export completes? We need to test if JobScheduler will call the job again if jobFinished
93         //  is never called.
94 
95         // TODO(b/325599089): Consider if we need to do any checkpointing here in case the job
96         //  doesn't complete and we need to pick it up again.
97     }
98 }
99