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