1 /*
2  * Copyright (C) 2021 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.imsserviceentitlement;
18 
19 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
20 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
21 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
22 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
23 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
24 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__POLLING;
25 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE;
26 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__SMSOIP;
27 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOLTE;
28 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI;
29 import static com.android.imsserviceentitlement.ts43.Ts43Constants.EntitlementVersion.ENTITLEMENT_VERSION_EIGHT;
30 
31 import android.app.job.JobParameters;
32 import android.app.job.JobService;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.os.AsyncTask;
36 import android.os.PersistableBundle;
37 import android.telephony.SubscriptionManager;
38 import android.util.Log;
39 import android.util.SparseArray;
40 
41 import androidx.annotation.Nullable;
42 import androidx.annotation.VisibleForTesting;
43 import androidx.annotation.WorkerThread;
44 
45 import com.android.imsserviceentitlement.entitlement.EntitlementConfiguration;
46 import com.android.imsserviceentitlement.entitlement.EntitlementConfiguration.ClientBehavior;
47 import com.android.imsserviceentitlement.entitlement.EntitlementResult;
48 import com.android.imsserviceentitlement.job.JobManager;
49 import com.android.imsserviceentitlement.utils.ImsUtils;
50 import com.android.imsserviceentitlement.utils.MetricsLogger;
51 import com.android.imsserviceentitlement.utils.TelephonyUtils;
52 
53 import java.time.Duration;
54 
55 /**
56  * The {@link JobService} for querying entitlement status in the background. The jobId is unique for
57  * different subId + job combination, so can run the same job for different subIds w/o cancelling
58  * each others. See {@link JobManager}.
59  */
60 public class ImsEntitlementPollingService extends JobService {
61     private static final String TAG = "IMSSE-ImsEntitlementPollingService";
62 
63     public static final ComponentName COMPONENT_NAME =
64             ComponentName.unflattenFromString(
65                     "com.android.imsserviceentitlement/.ImsEntitlementPollingService");
66 
67     private ImsEntitlementApi mImsEntitlementApi;
68 
69     /**
70      * Cache job id associated {@link EntitlementPollingTask} objects for canceling once job be
71      * canceled.
72      */
73     private final SparseArray<EntitlementPollingTask> mTasks = new SparseArray<>();
74 
75     @VisibleForTesting
76     EntitlementPollingTask mOngoingTask;
77 
78     @Override
79     @VisibleForTesting
attachBaseContext(Context base)80     protected void attachBaseContext(Context base) {
81         super.attachBaseContext(base);
82     }
83 
84     @VisibleForTesting
injectImsEntitlementApi(ImsEntitlementApi imsEntitlementApi)85     void injectImsEntitlementApi(ImsEntitlementApi imsEntitlementApi) {
86         this.mImsEntitlementApi = imsEntitlementApi;
87     }
88 
89     /** Enqueues a job to query entitlement status. */
enqueueJob(Context context, int subId, int retryCount)90     public static void enqueueJob(Context context, int subId, int retryCount) {
91         JobManager.getInstance(
92                 context,
93                 COMPONENT_NAME,
94                 subId)
95                 .queryEntitlementStatusOnceNetworkReady(retryCount);
96     }
97 
98     /** Enqueues a job to query entitlement status with delay. */
enqueueJobWithDelay(Context context, int subId, long delayInSeconds)99     private static void enqueueJobWithDelay(Context context, int subId, long delayInSeconds) {
100         JobManager.getInstance(
101                 context,
102                 COMPONENT_NAME,
103                 subId)
104                 .queryEntitlementStatusOnceNetworkReady(0, Duration.ofSeconds(delayInSeconds));
105     }
106 
107     @Override
onStartJob(final JobParameters params)108     public boolean onStartJob(final JobParameters params) {
109         PersistableBundle bundle = params.getExtras();
110         int subId =
111                 bundle.getInt(
112                         SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
113                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
114 
115         int jobId = params.getJobId();
116         Log.d(TAG, "onStartJob: " + jobId);
117 
118         // Ignore the job if the SIM be removed or swapped
119         if (!JobManager.isValidJob(this, params)) {
120             Log.d(TAG, "Stop for invalid job! " + jobId);
121             return false;
122         }
123 
124         // if the same job ID is scheduled again, the current one will be cancelled by platform and
125         // #onStopJob will be called to removed the job.
126         mOngoingTask = new EntitlementPollingTask(params, subId);
127         mTasks.put(jobId, mOngoingTask);
128         mOngoingTask.execute();
129         return true;
130     }
131 
132     @Override
onStopJob(final JobParameters params)133     public boolean onStopJob(final JobParameters params) {
134         int jobId = params.getJobId();
135         Log.d(TAG, "onStopJob: " + jobId);
136         EntitlementPollingTask task = mTasks.get(jobId);
137         if (task != null) {
138             task.cancel(true);
139             mTasks.remove(jobId);
140         }
141 
142         return true;
143     }
144 
145     @VisibleForTesting
146     class EntitlementPollingTask extends AsyncTask<Void, Void, Void> {
147         private final JobParameters mParams;
148         private final ImsEntitlementApi mImsEntitlementApi;
149         private final ImsUtils mImsUtils;
150         private final TelephonyUtils mTelephonyUtils;
151         private final MetricsLogger mMetricsLogger;
152         private final int mSubid;
153         private final int mEntitlementVersion;
154         private final boolean mNeedsImsProvisioning;
155 
156         // States for metrics
157         private long mStartTime;
158         private long mDurationMillis;
159         private int mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE;
160         private int mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
161         private int mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
162         private int mVonrResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
163         private int mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
164 
EntitlementPollingTask(final JobParameters params, int subId)165         EntitlementPollingTask(final JobParameters params, int subId) {
166             this.mParams = params;
167             this.mImsUtils = ImsUtils.getInstance(ImsEntitlementPollingService.this, subId);
168             this.mTelephonyUtils = new TelephonyUtils(ImsEntitlementPollingService.this, subId);
169             this.mSubid = subId;
170             this.mEntitlementVersion =
171                     TelephonyUtils.getEntitlementVersion(ImsEntitlementPollingService.this, mSubid);
172             this.mNeedsImsProvisioning = TelephonyUtils.isImsProvisioningRequired(
173                     ImsEntitlementPollingService.this, mSubid);
174             this.mImsEntitlementApi = ImsEntitlementPollingService.this.mImsEntitlementApi != null
175                     ? ImsEntitlementPollingService.this.mImsEntitlementApi
176                     : new ImsEntitlementApi(ImsEntitlementPollingService.this, subId);
177             this.mMetricsLogger = new MetricsLogger(mTelephonyUtils);
178         }
179 
180         @Override
doInBackground(Void... unused)181         protected Void doInBackground(Void... unused) {
182             int jobId = JobManager.getPureJobId(mParams.getJobId());
183             switch (jobId) {
184                 case JobManager.QUERY_ENTITLEMENT_STATUS_JOB_ID:
185                     mMetricsLogger.start(IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__POLLING);
186                     doEntitlementCheck();
187                     break;
188                 default:
189                     break;
190             }
191             return null;
192         }
193 
194         @Override
onPostExecute(Void unused)195         protected void onPostExecute(Void unused) {
196             Log.d(TAG, "JobId:" + mParams.getJobId() + "- Task done.");
197             sendStatsLogToMetrics();
198             ImsEntitlementPollingService.this.jobFinished(mParams, false);
199         }
200 
201         @Override
onCancelled(Void unused)202         protected void onCancelled(Void unused) {
203             sendStatsLogToMetrics();
204         }
205 
doEntitlementCheck()206         private void doEntitlementCheck() {
207             if (mNeedsImsProvisioning) {
208                 // TODO(b/190476343): Unify EntitlementResult and EntitlementConfiguration.
209                 doImsEntitlementCheck();
210             } else {
211                 doWfcEntitlementCheck();
212             }
213         }
214 
215         @WorkerThread
doImsEntitlementCheck()216         private void doImsEntitlementCheck() {
217             try {
218                 EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
219                 Log.d(TAG, "Entitlement result: " + result);
220 
221                 if (performRetryIfNeeded(result)) {
222                     return;
223                 }
224 
225                 if (shouldTurnOffWfc(result)) {
226                     mImsUtils.setVowifiProvisioned(false);
227                     mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
228                 } else {
229                     mImsUtils.setVowifiProvisioned(true);
230                     mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
231                 }
232 
233                 if (shouldTurnOffVolte(result)) {
234                     mImsUtils.setVolteProvisioned(false);
235                     mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
236                 } else {
237                     mImsUtils.setVolteProvisioned(true);
238                     mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
239                 }
240 
241                 if (mEntitlementVersion >= ENTITLEMENT_VERSION_EIGHT) {
242                     if (shouldTurnOffVonrHome(result)) {
243                         mImsUtils.setVonrProvisioned(false);
244                         mVonrResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
245                     } else {
246                         mImsUtils.setVonrProvisioned(true);
247                         mVonrResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
248                     }
249                 }
250 
251                 if (shouldTurnOffSMSoIP(result)) {
252                     mImsUtils.setSmsoipProvisioned(false);
253                     mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
254                 } else {
255                     mImsUtils.setSmsoipProvisioned(true);
256                     mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
257                 }
258             } catch (RuntimeException e) {
259                 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
260                 mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
261                 mVonrResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
262                 mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
263                 Log.d(TAG, "checkEntitlementStatus failed.", e);
264             }
265             checkVersValidity();
266         }
267 
268         @WorkerThread
doWfcEntitlementCheck()269         private void doWfcEntitlementCheck() {
270             if (!mImsUtils.isWfcEnabledByUser()) {
271                 Log.d(TAG, "WFC not turned on; checkEntitlementStatus not needed this time.");
272                 return;
273             }
274             try {
275                 EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
276                 Log.d(TAG, "Entitlement result: " + result);
277 
278                 if (performRetryIfNeeded(result)) {
279                     return;
280                 }
281 
282                 if (shouldTurnOffWfc(result)) {
283                     mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
284                     mImsUtils.disableWfc();
285                 } else {
286                     mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED;
287                 }
288             } catch (RuntimeException e) {
289                 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
290                 Log.d(TAG, "checkEntitlementStatus failed.", e);
291             }
292         }
293 
294         /**
295          * Performs retry if needed. Returns true if {@link ImsEntitlementPollingService} has
296          * scheduled.
297          */
performRetryIfNeeded(@ullable EntitlementResult result)298         private boolean performRetryIfNeeded(@Nullable EntitlementResult result) {
299             if (result == null || result.getRetryAfterSeconds() < 0) {
300                 return false;
301             }
302             mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
303             ImsEntitlementPollingService.enqueueJobWithDelay(
304                     ImsEntitlementPollingService.this,
305                     mSubid,
306                     result.getRetryAfterSeconds());
307             return true;
308         }
309 
310         /**
311          * Schedules entitlement status check after a VERS.validity time, if the last valid is
312          * during validity.
313          */
checkVersValidity()314         private void checkVersValidity() {
315             EntitlementConfiguration lastEntitlementConfiguration =
316                     new EntitlementConfiguration(ImsEntitlementPollingService.this, mSubid);
317             if (lastEntitlementConfiguration.entitlementValidation()
318                     == ClientBehavior.VALID_DURING_VALIDITY) {
319                 enqueueJobWithDelay(
320                         ImsEntitlementPollingService.this,
321                         mSubid,
322                         lastEntitlementConfiguration.getVersValidity());
323             }
324         }
325 
326         /**
327          * Returns {@code true} when {@code EntitlementResult} says WFC is not activated; Otherwise
328          * {@code false} if {@code EntitlementResult} is not of any known pattern.
329          */
shouldTurnOffWfc(@ullable EntitlementResult result)330         private boolean shouldTurnOffWfc(@Nullable EntitlementResult result) {
331             if (result == null) {
332                 Log.d(TAG, "Entitlement API failed to return a result; don't turn off WFC.");
333                 return false;
334             }
335 
336             // Only turn off WFC for known patterns indicating WFC not activated.
337             return result.getVowifiStatus().serverDataMissing()
338                     || result.getVowifiStatus().inProgress()
339                     || result.getVowifiStatus().incompatible();
340         }
341 
shouldTurnOffVolte(@ullable EntitlementResult result)342         private boolean shouldTurnOffVolte(@Nullable EntitlementResult result) {
343             if (result == null) {
344                 Log.d(TAG, "Entitlement API failed to return a result; don't turn off VoLTE.");
345                 return false;
346             }
347 
348             // Only turn off VoLTE for known patterns indicating VoLTE not activated.
349             return !result.getVolteStatus().isActive();
350         }
351 
shouldTurnOffVonrHome(@ullable EntitlementResult result)352         private boolean shouldTurnOffVonrHome(@Nullable EntitlementResult result) {
353             if (result == null) {
354                 Log.d(TAG, "Entitlement API failed to return a result; don't turn off VoNR.");
355                 return false;
356             }
357 
358             // Only turn off VoNR in Home for known patterns indicating VoNR not activated.
359             return !result.getVonrStatus().isHomeActive();
360         }
361 
shouldTurnOffSMSoIP(@ullable EntitlementResult result)362         private boolean shouldTurnOffSMSoIP(@Nullable EntitlementResult result) {
363             if (result == null) {
364                 Log.d(TAG, "Entitlement API failed to return a result; don't turn off SMSoIP.");
365                 return false;
366             }
367 
368             // Only turn off SMSoIP for known patterns indicating SMSoIP not activated.
369             return !result.getSmsoveripStatus().isActive();
370         }
371 
sendStatsLogToMetrics()372         private void sendStatsLogToMetrics() {
373             // If no result set, it was cancelled for reasons.
374             if (mVowifiResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) {
375                 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
376             }
377             mMetricsLogger.write(
378                     IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI, mVowifiResult);
379 
380             if (mNeedsImsProvisioning) {
381                 if (mVolteResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) {
382                     mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
383                 }
384                 if (mSmsoipResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) {
385                     mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
386                 }
387                 mMetricsLogger.write(
388                         IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOLTE, mVolteResult);
389                 mMetricsLogger.write(
390                         IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__SMSOIP, mSmsoipResult);
391             }
392         }
393 
394         @VisibleForTesting
getVonrResult()395         int getVonrResult() {
396             return mVonrResult;
397         }
398 
399         @VisibleForTesting
getVowifiResult()400         int getVowifiResult() {
401             return mVowifiResult;
402         }
403     }
404 }
405 
406