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