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