• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.download.mdd;
18 
19 import static android.app.job.JobScheduler.RESULT_SUCCESS;
20 
21 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW;
22 import static com.android.adservices.shared.proto.JobPolicy.NetworkType.NETWORK_TYPE_ANY;
23 import static com.android.adservices.shared.proto.JobPolicy.NetworkType.NETWORK_TYPE_NONE;
24 import static com.android.adservices.shared.proto.JobPolicy.NetworkType.NETWORK_TYPE_UNMETERED;
25 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_FAILED;
26 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SKIPPED;
27 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SUCCESSFUL;
28 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID;
29 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MDD_CHARGING_PERIODIC_TASK_JOB_ID;
30 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID;
31 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID;
32 
33 import android.app.job.JobInfo;
34 import android.app.job.JobScheduler;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.SharedPreferences;
38 import android.os.PersistableBundle;
39 
40 import com.android.adservices.shared.proto.JobPolicy;
41 import com.android.adservices.shared.proto.JobPolicy.NetworkType;
42 import com.android.adservices.shared.spe.JobServiceConstants;
43 import com.android.adservices.shared.spe.scheduling.JobSpec;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
46 import com.android.ondevicepersonalization.services.FlagsFactory;
47 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
48 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory;
49 
50 import com.google.android.libraries.mobiledatadownload.TaskScheduler;
51 
52 /**
53  * MddTaskScheduler that uses JobScheduler to schedule MDD background tasks
54  */
55 public class MddTaskScheduler implements TaskScheduler {
56     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
57     private static final String TAG = MddTaskScheduler.class.getSimpleName();
58     private static final String MDD_TASK_SHARED_PREFS = "mdd_worker_task_periods";
59     private final Context mContext;
60     static final String MDD_NETWORK_STATE_KEY = "MDD_NETWORK_STATE_KEY";
61     static final String MDD_PERIOD_SECONDS_KEY = "MDD_PERIOD_SECONDS_KEY";
62     static final String MDD_TASK_TAG_KEY = "MDD_TASK_TAG_KEY";
63 
MddTaskScheduler(Context context)64     public MddTaskScheduler(Context context) {
65         this.mContext = context;
66     }
67 
getMddTaskJobId(String mddTag)68     private static int getMddTaskJobId(String mddTag) {
69         switch (mddTag) {
70             case MAINTENANCE_PERIODIC_TASK:
71                 return MDD_MAINTENANCE_PERIODIC_TASK_JOB_ID;
72             case CHARGING_PERIODIC_TASK:
73                 return MDD_CHARGING_PERIODIC_TASK_JOB_ID;
74             case CELLULAR_CHARGING_PERIODIC_TASK:
75                 return MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB_ID;
76             default:
77                 return MDD_WIFI_CHARGING_PERIODIC_TASK_JOB_ID;
78         }
79     }
80 
81     // Maps from the MDD-supplied NetworkState to the JobInfo equivalent int code.
getNetworkConstraints(NetworkState networkState)82     static int getNetworkConstraints(NetworkState networkState) {
83         switch (networkState) {
84             case NETWORK_STATE_ANY:
85                 // Network not required.
86                 return JobInfo.NETWORK_TYPE_NONE;
87             case NETWORK_STATE_CONNECTED:
88                 // Metered or unmetered network available.
89                 return JobInfo.NETWORK_TYPE_ANY;
90             case NETWORK_STATE_UNMETERED:
91             default:
92                 return JobInfo.NETWORK_TYPE_UNMETERED;
93         }
94     }
95 
96     @Override
schedulePeriodicTask( String mddTaskTag, long periodSeconds, NetworkState networkState)97     public void schedulePeriodicTask(
98             String mddTaskTag, long periodSeconds, NetworkState networkState) {
99         schedule(mContext, mddTaskTag, periodSeconds, networkState);
100     }
101 
102     /** Schedules a unique instance of {@link MddJob}. */
schedule(Context context, PersistableBundle extras)103     public static void schedule(Context context, PersistableBundle extras) {
104         schedule(context,
105                 getMddTaskTag(extras),
106                 getMddPeriodSeconds(extras),
107                 getMddNetworkState(extras));
108     }
109 
110     /** Schedules a unique instance of {@link MddJobService}. */
111     @JobServiceConstants.JobSchedulingResultCode
scheduleWithLegacy( Context context, PersistableBundle extras, boolean forceSchedule)112     public static int scheduleWithLegacy(
113             Context context, PersistableBundle extras, boolean forceSchedule) {
114         return scheduleWithLegacy(context,
115                 getMddTaskTag(extras),
116                 getMddPeriodSeconds(extras),
117                 getMddNetworkState(extras),
118                 forceSchedule);
119     }
120 
schedule(Context context, String mddTaskTag, long periodSeconds, NetworkState networkState)121     private static void schedule(Context context, String mddTaskTag, long periodSeconds,
122             NetworkState networkState) {
123         if (FlagsFactory.getFlags().getSpeOnMddJobEnabled()) {
124             OdpJobScheduler.getInstance(context).schedule(
125                     context,
126                     createJobSpec(mddTaskTag, periodSeconds, networkState));
127             return;
128         }
129 
130         sLogger.d("SPE is not enabled. Schedule the job with MddJobService.");
131         int resultCode = scheduleWithLegacy(
132                 context, mddTaskTag, periodSeconds, networkState, /* forceSchedule */ false);
133 
134         OdpJobServiceFactory.getInstance(context)
135                 .getJobSchedulingLogger()
136                 .recordOnSchedulingLegacy(getMddTaskJobId(mddTaskTag), resultCode);
137     }
138 
139     /** Schedules a unique instance of {@link MddJobService}. */
140     @VisibleForTesting
141     @JobServiceConstants.JobSchedulingResultCode
scheduleWithLegacy(Context context, String mddTaskTag, long periodSeconds, NetworkState networkState, boolean forceSchedule)142     static int scheduleWithLegacy(Context context, String mddTaskTag, long periodSeconds,
143             NetworkState networkState, boolean forceSchedule) {
144         SharedPreferences prefs =
145                 context.getSharedPreferences(MDD_TASK_SHARED_PREFS, Context.MODE_PRIVATE);
146 
147         // When the period change, we will need to update the existing works.
148         boolean updateCurrent = false;
149         if (getCurrentPeriodValue(prefs, mddTaskTag) != periodSeconds) {
150             SharedPreferences.Editor editor = prefs.edit();
151             editor.putLong(mddTaskTag, periodSeconds);
152             editor.apply();
153             updateCurrent = true;
154         }
155 
156         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
157         if (jobScheduler.getPendingJob(getMddTaskJobId(mddTaskTag)) == null) {
158             sLogger.d(TAG + ": MddJob %s is not scheduled, scheduling now", mddTaskTag);
159             return schedulePeriodicTaskWithUpdate(
160                     context , jobScheduler, mddTaskTag, periodSeconds, networkState);
161         } else if (updateCurrent) {
162             sLogger.d(TAG + ": scheduling MddJob %s with frequency update", mddTaskTag);
163             return schedulePeriodicTaskWithUpdate(
164                     context, jobScheduler, mddTaskTag, periodSeconds, networkState);
165         } else if (forceSchedule) {
166             sLogger.d(TAG + ": force scheduling MddJob %s", mddTaskTag);
167             return schedulePeriodicTaskWithUpdate(
168                     context, jobScheduler, mddTaskTag, periodSeconds, networkState);
169         }
170         sLogger.d(TAG + ": MddJob %s already scheduled and frequency unchanged,"
171                 + " not scheduling", mddTaskTag);
172         return SCHEDULING_RESULT_CODE_SKIPPED;
173     }
174 
175     @JobServiceConstants.JobSchedulingResultCode
schedulePeriodicTaskWithUpdate(Context context, JobScheduler jobScheduler, String mddTaskTag, long periodSeconds, NetworkState networkState)176     private static int schedulePeriodicTaskWithUpdate(Context context, JobScheduler jobScheduler,
177             String mddTaskTag, long periodSeconds, NetworkState networkState) {
178         // We use extras to pass MDD config values. They will be used in the mdd jobs.
179         PersistableBundle extras = new PersistableBundle();
180         extras.putString(MDD_TASK_TAG_KEY, mddTaskTag);
181         extras.putLong(MDD_PERIOD_SECONDS_KEY, periodSeconds);
182         extras.putString(MDD_NETWORK_STATE_KEY, networkState.name());
183 
184         final JobInfo job =
185                 new JobInfo.Builder(
186                         getMddTaskJobId(mddTaskTag),
187                         new ComponentName(context, MddJobService.class))
188                         .setRequiresDeviceIdle(true)
189                         .setRequiresCharging(false)
190                         .setRequiresBatteryNotLow(true)
191                         .setPeriodic(1000 * periodSeconds) // JobScheduler uses Milliseconds.
192                         .setRequiresStorageNotLow(requireStorageNotLow(mddTaskTag))
193                         // persist this job across boots
194                         .setPersisted(true)
195                         .setRequiredNetworkType(getNetworkConstraints(networkState))
196                         .setExtras(extras)
197                         .build();
198         int schedulingResult = jobScheduler.schedule(job);
199         return RESULT_SUCCESS == schedulingResult ? SCHEDULING_RESULT_CODE_SUCCESSFUL
200                 : SCHEDULING_RESULT_CODE_FAILED;
201     }
202 
203     @VisibleForTesting
createJobSpec(String mddTaskTag, long periodSeconds, NetworkState networkState)204     static JobSpec createJobSpec(String mddTaskTag, long periodSeconds, NetworkState networkState) {
205         // We use extras to pass MDD config values. They will be used in the mdd jobs.
206         PersistableBundle extras = new PersistableBundle();
207         extras.putString(MDD_TASK_TAG_KEY, mddTaskTag);
208         extras.putLong(MDD_PERIOD_SECONDS_KEY, periodSeconds);
209         extras.putString(MDD_NETWORK_STATE_KEY, networkState.name());
210 
211         JobPolicy jobPolicy =
212                 JobPolicy.newBuilder()
213                         .setJobId(getMddTaskJobId(mddTaskTag))
214                         .setRequireDeviceIdle(true)
215                         .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW)
216                         .setPeriodicJobParams(
217                                 JobPolicy.PeriodicJobParams.newBuilder()
218                                         .setPeriodicIntervalMs(1000 * periodSeconds)
219                                         .build())
220                         .setNetworkType(getNetworkType(networkState))
221                         .setRequireStorageNotLow(requireStorageNotLow(mddTaskTag))
222                         .setIsPersisted(true)
223                         .build();
224 
225         return new JobSpec.Builder(jobPolicy)
226                 .setExtras(extras).build();
227     }
228 
getNetworkType(NetworkState networkState)229     private static NetworkType getNetworkType(NetworkState networkState) {
230         switch (networkState) {
231             case NETWORK_STATE_ANY:
232                 // Network not required.
233                 return NETWORK_TYPE_NONE;
234             case NETWORK_STATE_CONNECTED:
235                 // Metered or unmetered network available.
236                 return NETWORK_TYPE_ANY;
237             case NETWORK_STATE_UNMETERED:
238             default:
239                 return NETWORK_TYPE_UNMETERED;
240         }
241     }
242 
getMddTaskTag(final PersistableBundle extras)243     static String getMddTaskTag(final PersistableBundle extras) {
244         requireNonNullExtras(extras);
245         String mddTaskTag = extras.getString(MDD_TASK_TAG_KEY);
246         if (null == mddTaskTag) {
247             throw new IllegalArgumentException("Mdd task tag not found");
248         }
249         return mddTaskTag;
250     }
251 
getMddPeriodSeconds(final PersistableBundle extras)252     private static long getMddPeriodSeconds(final PersistableBundle extras) {
253         requireNonNullExtras(extras);
254         return extras.getLong(MDD_PERIOD_SECONDS_KEY);
255     }
256 
getMddNetworkState(final PersistableBundle extras)257     private static NetworkState getMddNetworkState(final PersistableBundle extras) {
258         requireNonNullExtras(extras);
259         String networkState = extras.getString(MDD_NETWORK_STATE_KEY);
260         if (networkState == null) {
261             throw new IllegalArgumentException("MDD extra network state not found");
262         }
263         return NetworkState.valueOf(networkState);
264     }
265 
requireNonNullExtras(PersistableBundle extras)266     private static void requireNonNullExtras(PersistableBundle extras) {
267         if (null == extras) {
268             throw new IllegalArgumentException("MDD extras not found");
269         }
270     }
271 
getCurrentPeriodValue(SharedPreferences prefs, String mddTaskTag)272     private static long getCurrentPeriodValue(SharedPreferences prefs, String mddTaskTag) {
273         try {
274             return prefs.getLong(mddTaskTag, 0);
275         } catch (ClassCastException e) {
276             sLogger.w(e, TAG + ": ClassCastException retrieving long value from prefs for tag: %s",
277                     mddTaskTag);
278             return 0;
279         }
280     }
281 
requireStorageNotLow(String mddTaskTag)282     private static boolean requireStorageNotLow(String mddTaskTag) {
283         return WIFI_CHARGING_PERIODIC_TASK.equals(mddTaskTag)
284                 || CELLULAR_CHARGING_PERIODIC_TASK.equals(mddTaskTag);
285     }
286 }
287