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; 20 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 21 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 22 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED; 23 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 24 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 25 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__POLLING; 26 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE; 27 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__SMSOIP; 28 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOLTE; 29 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI; 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.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 int mSubid; 151 private final boolean mNeedsImsProvisioning; 152 153 // States for metrics 154 private long mStartTime; 155 private long mDurationMillis; 156 private int mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UNKNOWN_PURPOSE; 157 private int mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 158 private int mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 159 private int mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 160 EntitlementPollingTask(final JobParameters params, int subId)161 EntitlementPollingTask(final JobParameters params, int subId) { 162 this.mParams = params; 163 this.mImsUtils = ImsUtils.getInstance(ImsEntitlementPollingService.this, subId); 164 this.mTelephonyUtils = new TelephonyUtils(ImsEntitlementPollingService.this, subId); 165 this.mSubid = subId; 166 this.mNeedsImsProvisioning = TelephonyUtils.isImsProvisioningRequired( 167 ImsEntitlementPollingService.this, mSubid); 168 this.mImsEntitlementApi = ImsEntitlementPollingService.this.mImsEntitlementApi != null 169 ? ImsEntitlementPollingService.this.mImsEntitlementApi 170 : new ImsEntitlementApi(ImsEntitlementPollingService.this, subId); 171 } 172 173 @Override doInBackground(Void... unused)174 protected Void doInBackground(Void... unused) { 175 mStartTime = mTelephonyUtils.getUptimeMillis(); 176 int jobId = JobManager.getPureJobId(mParams.getJobId()); 177 switch (jobId) { 178 case JobManager.QUERY_ENTITLEMENT_STATUS_JOB_ID: 179 mPurpose = IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__POLLING; 180 doEntitlementCheck(); 181 break; 182 default: 183 break; 184 } 185 return null; 186 } 187 188 @Override onPostExecute(Void unused)189 protected void onPostExecute(Void unused) { 190 Log.d(TAG, "JobId:" + mParams.getJobId() + "- Task done."); 191 sendStatsLogToMetrics(); 192 ImsEntitlementPollingService.this.jobFinished(mParams, false); 193 } 194 195 @Override onCancelled(Void unused)196 protected void onCancelled(Void unused) { 197 sendStatsLogToMetrics(); 198 } 199 doEntitlementCheck()200 private void doEntitlementCheck() { 201 if (mNeedsImsProvisioning) { 202 // TODO(b/190476343): Unify EntitlementResult and EntitlementConfiguration. 203 doImsEntitlementCheck(); 204 } else { 205 doWfcEntitlementCheck(); 206 } 207 } 208 209 @WorkerThread doImsEntitlementCheck()210 private void doImsEntitlementCheck() { 211 try { 212 EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus(); 213 Log.d(TAG, "Entitlement result: " + result); 214 215 if (performRetryIfNeeded(result)) { 216 return; 217 } 218 219 if (shouldTurnOffWfc(result)) { 220 mImsUtils.setVowifiProvisioned(false); 221 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 222 } else { 223 mImsUtils.setVowifiProvisioned(true); 224 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED; 225 } 226 227 if (shouldTurnOffVolte(result)) { 228 mImsUtils.setVolteProvisioned(false); 229 mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 230 } else { 231 mImsUtils.setVolteProvisioned(true); 232 mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED; 233 } 234 235 if (shouldTurnOffSMSoIP(result)) { 236 mImsUtils.setSmsoipProvisioned(false); 237 mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 238 } else { 239 mImsUtils.setSmsoipProvisioned(true); 240 mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED; 241 } 242 } catch (RuntimeException e) { 243 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 244 mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 245 mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 246 Log.d(TAG, "checkEntitlementStatus failed.", e); 247 } 248 checkVersValidity(); 249 } 250 251 @WorkerThread doWfcEntitlementCheck()252 private void doWfcEntitlementCheck() { 253 if (!mImsUtils.isWfcEnabledByUser()) { 254 Log.d(TAG, "WFC not turned on; checkEntitlementStatus not needed this time."); 255 return; 256 } 257 try { 258 EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus(); 259 Log.d(TAG, "Entitlement result: " + result); 260 261 if (performRetryIfNeeded(result)) { 262 return; 263 } 264 265 if (shouldTurnOffWfc(result)) { 266 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 267 mImsUtils.disableWfc(); 268 } else { 269 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__ENABLED; 270 } 271 } catch (RuntimeException e) { 272 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 273 Log.d(TAG, "checkEntitlementStatus failed.", e); 274 } 275 } 276 277 /** 278 * Performs retry if needed. Returns true if {@link ImsEntitlementPollingService} has 279 * scheduled. 280 */ performRetryIfNeeded(@ullable EntitlementResult result)281 private boolean performRetryIfNeeded(@Nullable EntitlementResult result) { 282 if (result == null || result.getRetryAfterSeconds() < 0) { 283 return false; 284 } 285 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 286 ImsEntitlementPollingService.enqueueJobWithDelay( 287 ImsEntitlementPollingService.this, 288 mSubid, 289 result.getRetryAfterSeconds()); 290 return true; 291 } 292 293 /** 294 * Schedules entitlement status check after a VERS.validity time, if the last valid is 295 * during validity. 296 */ checkVersValidity()297 private void checkVersValidity() { 298 EntitlementConfiguration lastEntitlementConfiguration = 299 new EntitlementConfiguration(ImsEntitlementPollingService.this, mSubid); 300 if (lastEntitlementConfiguration.entitlementValidation() 301 == ClientBehavior.VALID_DURING_VALIDITY) { 302 enqueueJobWithDelay( 303 ImsEntitlementPollingService.this, 304 mSubid, 305 lastEntitlementConfiguration.getVersValidity()); 306 } 307 } 308 309 /** 310 * Returns {@code true} when {@code EntitlementResult} says WFC is not activated; Otherwise 311 * {@code false} if {@code EntitlementResult} is not of any known pattern. 312 */ shouldTurnOffWfc(@ullable EntitlementResult result)313 private boolean shouldTurnOffWfc(@Nullable EntitlementResult result) { 314 if (result == null) { 315 Log.d(TAG, "Entitlement API failed to return a result; don't turn off WFC."); 316 return false; 317 } 318 319 // Only turn off WFC for known patterns indicating WFC not activated. 320 return result.getVowifiStatus().serverDataMissing() 321 || result.getVowifiStatus().inProgress() 322 || result.getVowifiStatus().incompatible(); 323 } 324 shouldTurnOffVolte(@ullable EntitlementResult result)325 private boolean shouldTurnOffVolte(@Nullable EntitlementResult result) { 326 if (result == null) { 327 Log.d(TAG, "Entitlement API failed to return a result; don't turn off VoLTE."); 328 return false; 329 } 330 331 // Only turn off VoLTE for known patterns indicating VoLTE not activated. 332 return !result.getVolteStatus().isActive(); 333 } 334 shouldTurnOffSMSoIP(@ullable EntitlementResult result)335 private boolean shouldTurnOffSMSoIP(@Nullable EntitlementResult result) { 336 if (result == null) { 337 Log.d(TAG, "Entitlement API failed to return a result; don't turn off SMSoIP."); 338 return false; 339 } 340 341 // Only turn off SMSoIP for known patterns indicating SMSoIP not activated. 342 return !result.getSmsoveripStatus().isActive(); 343 } 344 sendStatsLogToMetrics()345 private void sendStatsLogToMetrics() { 346 mDurationMillis = mTelephonyUtils.getUptimeMillis() - mStartTime; 347 348 // If no result set, it was cancelled for reasons. 349 if (mVowifiResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) { 350 mVowifiResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 351 } 352 writeStateLogByApps( 353 IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI, mVowifiResult); 354 355 if (mNeedsImsProvisioning) { 356 if (mVolteResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) { 357 mVolteResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 358 } 359 if (mSmsoipResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) { 360 mSmsoipResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 361 } 362 writeStateLogByApps( 363 IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOLTE, mVolteResult); 364 writeStateLogByApps( 365 IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__SMSOIP, mSmsoipResult); 366 } 367 } 368 writeStateLogByApps(int appId, int appResult)369 private void writeStateLogByApps(int appId, int appResult) { 370 ImsServiceEntitlementStatsLog.write( 371 IMS_SERVICE_ENTITLEMENT_UPDATED, 372 mTelephonyUtils.getCarrierId(), 373 mTelephonyUtils.getSpecificCarrierId(), 374 mPurpose, 375 appId, 376 appResult, 377 mDurationMillis); 378 } 379 } 380 } 381