1 /* 2 * Copyright (C) 2015 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 android.support.v7.mms; 18 19 import android.app.Service; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Handler; 23 import android.os.IBinder; 24 import android.os.PowerManager; 25 import android.os.Process; 26 import android.telephony.SmsManager; 27 import android.util.Log; 28 29 import java.util.concurrent.ExecutorService; 30 import java.util.concurrent.Executors; 31 import java.util.concurrent.RejectedExecutionException; 32 33 /** 34 * Service to execute MMS requests using deprecated legacy APIs on older platform (prior to L) 35 */ 36 public class MmsService extends Service { 37 static final String TAG = "MmsLib"; 38 39 //The default number of threads allowed to run MMS requests 40 private static final int DEFAULT_THREAD_POOL_SIZE = 4; 41 // Delay before stopping the service 42 private static final int SERVICE_STOP_DELAY_MILLIS = 2000; 43 44 private static final String EXTRA_REQUEST = "request"; 45 private static final String EXTRA_MYPID = "mypid"; 46 47 private static final String WAKELOCK_ID = "mmslib_wakelock"; 48 49 /** 50 * Thread pool size for each request queue 51 */ 52 private static volatile int sThreadPoolSize = DEFAULT_THREAD_POOL_SIZE; 53 54 /** 55 * Optional wake lock to use 56 */ 57 private static volatile boolean sUseWakeLock = true; 58 private static volatile PowerManager.WakeLock sWakeLock = null; 59 private static final Object sWakeLockLock = new Object(); 60 61 /** 62 * Carrier configuration values loader 63 */ 64 private static volatile CarrierConfigValuesLoader sCarrierConfigValuesLoader = null; 65 66 /** 67 * APN loader 68 */ 69 private static volatile ApnSettingsLoader sApnSettingsLoader = null; 70 71 /** 72 * UserAgent and UA Prof URL loader 73 */ 74 private static volatile UserAgentInfoLoader sUserAgentInfoLoader = null; 75 76 /** 77 * Set the size of thread pool for request execution. 78 * Default is DEFAULT_THREAD_POOL_SIZE 79 * 80 * @param size thread pool size 81 */ 82 static void setThreadPoolSize(final int size) { 83 sThreadPoolSize = size; 84 } 85 86 /** 87 * Set whether to use wake lock 88 * 89 * @param useWakeLock true to use wake lock, false otherwise 90 */ 91 static void setUseWakeLock(final boolean useWakeLock) { 92 sUseWakeLock = useWakeLock; 93 } 94 95 /** 96 * Set the optional carrier config values 97 * 98 * @param loader the carrier config values loader 99 */ 100 static void setCarrierConfigValuesLoader(final CarrierConfigValuesLoader loader) { 101 sCarrierConfigValuesLoader = loader; 102 } 103 104 /** 105 * Get the current carrier config values loader 106 * 107 * @return the carrier config values loader currently set 108 */ 109 static CarrierConfigValuesLoader getCarrierConfigValuesLoader() { 110 return sCarrierConfigValuesLoader; 111 } 112 113 /** 114 * Set APN settings loader 115 * 116 * @param loader the APN settings loader 117 */ 118 static void setApnSettingsLoader(final ApnSettingsLoader loader) { 119 sApnSettingsLoader = loader; 120 } 121 122 /** 123 * Get the current APN settings loader 124 * 125 * @return the APN settings loader currently set 126 */ 127 static ApnSettingsLoader getApnSettingsLoader() { 128 return sApnSettingsLoader; 129 } 130 131 /** 132 * Set user agent info loader 133 * 134 * @param loader the user agent info loader 135 */ 136 static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) { 137 sUserAgentInfoLoader = loader; 138 } 139 140 /** 141 * Get the current user agent info loader 142 * 143 * @return the user agent info loader currently set 144 */ 145 static UserAgentInfoLoader getUserAgentInfoLoader() { 146 return sUserAgentInfoLoader; 147 } 148 149 /** 150 * Make sure loaders are not null. Set to default if that's the case 151 * 152 * @param context the Context to use 153 */ 154 private static void ensureLoaders(final Context context) { 155 if (sUserAgentInfoLoader == null) { 156 sUserAgentInfoLoader = new DefaultUserAgentInfoLoader(context); 157 } 158 if (sCarrierConfigValuesLoader == null) { 159 sCarrierConfigValuesLoader = new DefaultCarrierConfigValuesLoader(context); 160 } 161 if (sApnSettingsLoader == null) { 162 sApnSettingsLoader = new DefaultApnSettingsLoader(context); 163 } 164 } 165 166 /** 167 * Acquire the wake lock 168 * 169 * @param context the context to use 170 */ 171 private static void acquireWakeLock(final Context context) { 172 synchronized (sWakeLockLock) { 173 if (sWakeLock == null) { 174 final PowerManager pm = 175 (PowerManager) context.getSystemService(Context.POWER_SERVICE); 176 sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_ID); 177 } 178 sWakeLock.acquire(); 179 } 180 } 181 182 /** 183 * Release the wake lock 184 */ 185 private static void releaseWakeLock() { 186 boolean releasedEmptyWakeLock = false; 187 synchronized (sWakeLockLock) { 188 if (sWakeLock != null) { 189 sWakeLock.release(); 190 } else { 191 releasedEmptyWakeLock = true; 192 } 193 } 194 if (releasedEmptyWakeLock) { 195 Log.w(TAG, "Releasing empty wake lock"); 196 } 197 } 198 199 /** 200 * Check if wake lock is not held (e.g. when service stops) 201 */ 202 private static void verifyWakeLockNotHeld() { 203 boolean wakeLockHeld = false; 204 synchronized (sWakeLockLock) { 205 wakeLockHeld = sWakeLock != null && sWakeLock.isHeld(); 206 } 207 if (wakeLockHeld) { 208 Log.e(TAG, "Wake lock still held!"); 209 } 210 } 211 212 // Remember my PID to discard restarted intent 213 private static volatile int sMyPid = -1; 214 215 /** 216 * Get the current PID 217 * 218 * @return the current PID 219 */ 220 private static int getMyPid() { 221 if (sMyPid < 0) { 222 sMyPid = Process.myPid(); 223 } 224 return sMyPid; 225 } 226 227 /** 228 * Check if the intent is coming from this process 229 * 230 * @param intent the incoming intent for the service 231 * @return true if the intent is from the current process 232 */ 233 private static boolean fromThisProcess(final Intent intent) { 234 final int pid = intent.getIntExtra(EXTRA_MYPID, -1); 235 return pid == getMyPid(); 236 } 237 238 // Request execution thread pools. One thread pool for sending and one for downloading. 239 // The size of the thread pool controls the parallelism of request execution. 240 // See {@link setThreadPoolSize} 241 private ExecutorService[] mExecutors = new ExecutorService[2]; 242 243 // Active request count 244 private int mActiveRequestCount; 245 // The latest intent startId, used for safely stopping service 246 private int mLastStartId; 247 248 private MmsNetworkManager mNetworkManager; 249 250 // Handler for scheduling service stop 251 private final Handler mHandler = new Handler(); 252 // Service stop task 253 private final Runnable mServiceStopRunnable = new Runnable() { 254 @Override 255 public void run() { 256 tryStopService(); 257 } 258 }; 259 260 /** 261 * Start the service with a request 262 * 263 * @param context the Context to use 264 * @param request the request to start 265 */ 266 public static void startRequest(final Context context, final MmsRequest request) { 267 final boolean useWakeLock = sUseWakeLock; 268 request.setUseWakeLock(useWakeLock); 269 final Intent intent = new Intent(context, MmsService.class); 270 intent.putExtra(EXTRA_REQUEST, request); 271 intent.putExtra(EXTRA_MYPID, getMyPid()); 272 if (useWakeLock) { 273 acquireWakeLock(context); 274 } 275 if (context.startService(intent) == null) { 276 if (useWakeLock) { 277 releaseWakeLock(); 278 } 279 } 280 } 281 282 @Override 283 public void onCreate() { 284 super.onCreate(); 285 286 ensureLoaders(this); 287 288 for (int i = 0; i < mExecutors.length; i++) { 289 mExecutors[i] = Executors.newFixedThreadPool(sThreadPoolSize); 290 } 291 292 mNetworkManager = new MmsNetworkManager(this); 293 294 synchronized (this) { 295 mActiveRequestCount = 0; 296 mLastStartId = -1; 297 } 298 } 299 300 @Override 301 public void onDestroy() { 302 super.onDestroy(); 303 304 for (ExecutorService executor : mExecutors) { 305 executor.shutdown(); 306 } 307 } 308 309 @Override 310 public int onStartCommand(Intent intent, int flags, int startId) { 311 // Always remember the latest startId for use when we try releasing the service 312 synchronized (this) { 313 mLastStartId = startId; 314 } 315 boolean scheduled = false; 316 if (intent != null) { 317 // There is a rare situation that right after a intent is started, 318 // the service gets killed. Then the service will restart with 319 // the old intent which we don't want it to run since it will 320 // break our assumption for wake lock. Check the process ID 321 // embedded in the intent to make sure it is indeed from the 322 // the current life of this service. 323 if (fromThisProcess(intent)) { 324 final MmsRequest request = intent.getParcelableExtra(EXTRA_REQUEST); 325 if (request != null) { 326 try { 327 retainService(request, new Runnable() { 328 @Override 329 public void run() { 330 try { 331 request.execute( 332 MmsService.this, 333 mNetworkManager, 334 getApnSettingsLoader(), 335 getCarrierConfigValuesLoader(), 336 getUserAgentInfoLoader()); 337 } catch (Exception e) { 338 Log.w(TAG, "Unexpected execution failure", e); 339 } finally { 340 if (request.getUseWakeLock()) { 341 releaseWakeLock(); 342 } 343 releaseService(); 344 } 345 } 346 }); 347 scheduled = true; 348 } catch (RejectedExecutionException e) { 349 // Rare thing happened. Send back failure using the pending intent 350 // and also release the wake lock. 351 Log.w(TAG, "Executing request failed " + e); 352 request.returnResult(this, SmsManager.MMS_ERROR_UNSPECIFIED, 353 null/*response*/, 0/*httpStatusCode*/); 354 if (request.getUseWakeLock()) { 355 releaseWakeLock(); 356 } 357 } 358 } else { 359 Log.w(TAG, "Empty request"); 360 } 361 } else { 362 Log.w(TAG, "Got a restarted intent from previous incarnation"); 363 } 364 } else { 365 Log.w(TAG, "Empty intent"); 366 } 367 if (!scheduled) { 368 // If the request is not started successfully, we need to try shutdown the service 369 // if nobody is using it. 370 tryScheduleStop(); 371 } 372 return START_NOT_STICKY; 373 } 374 375 /** 376 * Retain the service for executing the request in service thread pool 377 * 378 * @param request The request to execute 379 * @param runnable The runnable to run the request in thread pool 380 */ 381 private void retainService(final MmsRequest request, final Runnable runnable) { 382 final ExecutorService executor = getRequestExecutor(request); 383 synchronized (this) { 384 executor.execute(runnable); 385 mActiveRequestCount++; 386 } 387 } 388 389 /** 390 * Release the service from the request. If nobody is using it, schedule service stop. 391 */ 392 private void releaseService() { 393 synchronized (this) { 394 mActiveRequestCount--; 395 if (mActiveRequestCount <= 0) { 396 mActiveRequestCount = 0; 397 rescheduleServiceStop(); 398 } 399 } 400 } 401 402 /** 403 * Schedule the service stop if there is no active request 404 */ 405 private void tryScheduleStop() { 406 synchronized (this) { 407 if (mActiveRequestCount == 0) { 408 rescheduleServiceStop(); 409 } 410 } 411 } 412 413 /** 414 * Reschedule service stop task 415 */ 416 private void rescheduleServiceStop() { 417 mHandler.removeCallbacks(mServiceStopRunnable); 418 mHandler.postDelayed(mServiceStopRunnable, SERVICE_STOP_DELAY_MILLIS); 419 } 420 421 /** 422 * Really try to stop the service if there is not active request 423 */ 424 private void tryStopService() { 425 Boolean stopped = null; 426 synchronized (this) { 427 if (mActiveRequestCount == 0) { 428 stopped = stopSelfResult(mLastStartId); 429 } 430 } 431 logServiceStop(stopped); 432 } 433 434 /** 435 * Log the result of service stopping. Also check wake lock status when service stops. 436 * 437 * @param stopped Not empty if service stop is performed: true if really stopped, false 438 * if cancelled. 439 */ 440 private void logServiceStop(final Boolean stopped) { 441 if (stopped != null) { 442 if (stopped) { 443 Log.i(TAG, "Service successfully stopped"); 444 verifyWakeLockNotHeld(); 445 } else { 446 Log.i(TAG, "Service stopping cancelled"); 447 } 448 } 449 } 450 451 private ExecutorService getRequestExecutor(final MmsRequest request) { 452 if (request instanceof SendRequest) { 453 // Send 454 return mExecutors[0]; 455 } else { 456 // Download 457 return mExecutors[1]; 458 } 459 } 460 461 @Override 462 public IBinder onBind(Intent intent) { 463 return null; 464 } 465 } 466