1 /* 2 * Copyright (C) 2022 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.ondevicepersonalization.services.data.user; 18 19 import static android.app.job.JobScheduler.RESULT_FAILURE; 20 21 import android.app.job.JobInfo; 22 import android.app.job.JobParameters; 23 import android.app.job.JobScheduler; 24 import android.app.job.JobService; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.util.Log; 28 29 import com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig; 30 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 31 32 import com.google.common.util.concurrent.FutureCallback; 33 import com.google.common.util.concurrent.Futures; 34 import com.google.common.util.concurrent.ListenableFuture; 35 36 /** 37 * JobService to collect user data in the background thread. 38 */ 39 public class UserDataCollectionJobService extends JobService { 40 public static final String TAG = "UserDataCollectionJobService"; 41 // 4-hour interval. 42 private static final long PERIOD_SECONDS = 14400; 43 private ListenableFuture<Void> mFuture; 44 private UserDataCollector mUserDataCollector; 45 private RawUserData mUserData; 46 47 /** 48 * Schedules a unique instance of UserDataCollectionJobService to be run. 49 */ schedule(Context context)50 public static int schedule(Context context) { 51 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 52 if (jobScheduler.getPendingJob( 53 OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID) != null) { 54 Log.d(TAG, "Job is already scheduled. Doing nothing,"); 55 return RESULT_FAILURE; 56 } 57 ComponentName serviceComponent = new ComponentName(context, 58 UserDataCollectionJobService.class); 59 JobInfo.Builder builder = new JobInfo.Builder( 60 OnDevicePersonalizationConfig.USER_DATA_COLLECTION_ID, serviceComponent); 61 62 // Constraints 63 builder.setRequiresDeviceIdle(true); 64 builder.setRequiresBatteryNotLow(true); 65 builder.setRequiresStorageNotLow(true); 66 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE); 67 builder.setPeriodic(1000 * PERIOD_SECONDS); // JobScheduler uses Milliseconds. 68 // persist this job across boots 69 builder.setPersisted(true); 70 71 return jobScheduler.schedule(builder.build()); 72 } 73 74 @Override onStartJob(JobParameters params)75 public boolean onStartJob(JobParameters params) { 76 // TODO(b/265856477): return false to disable data collection if kid status is enabled. 77 Log.d(TAG, "onStartJob()"); 78 mUserDataCollector = UserDataCollector.getInstance(this); 79 mUserData = RawUserData.getInstance(); 80 mFuture = Futures.submit(new Runnable() { 81 @Override 82 public void run() { 83 Log.d(TAG, "Running user data collection job"); 84 try { 85 // TODO(b/262749958): add multi-threading support if necessary. 86 mUserDataCollector.updateUserData(mUserData); 87 } catch (Exception e) { 88 Log.e(TAG, "Failed to collect user data", e); 89 } 90 } 91 }, OnDevicePersonalizationExecutors.getBackgroundExecutor()); 92 93 Futures.addCallback( 94 mFuture, 95 new FutureCallback<Void>() { 96 @Override 97 public void onSuccess(Void result) { 98 Log.d(TAG, "User data collection job completed."); 99 jobFinished(params, /* wantsReschedule = */ false); 100 } 101 102 @Override 103 public void onFailure(Throwable t) { 104 Log.e(TAG, "Failed to handle JobService: " + params.getJobId(), t); 105 // When failure, also tell the JobScheduler that the job has completed and 106 // does not need to be rescheduled. 107 jobFinished(params, /* wantsReschedule = */ false); 108 } 109 }, 110 OnDevicePersonalizationExecutors.getBackgroundExecutor()); 111 112 return true; 113 } 114 115 @Override onStopJob(JobParameters params)116 public boolean onStopJob(JobParameters params) { 117 if (mFuture != null) { 118 mFuture.cancel(true); 119 } 120 // Reschedule the job since it ended before finishing 121 return true; 122 } 123 } 124