Home | History | Annotate | Download | only in mms
      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