Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2014 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.server;
     18 
     19 import com.android.internal.telephony.IMms;
     20 
     21 import android.Manifest;
     22 import android.app.AppOpsManager;
     23 import android.app.PendingIntent;
     24 import android.content.ComponentName;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.ServiceConnection;
     29 import android.content.pm.PackageManager;
     30 import android.net.Uri;
     31 import android.os.Binder;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.IBinder;
     35 import android.os.Message;
     36 import android.os.RemoteException;
     37 import android.os.SystemClock;
     38 import android.telephony.TelephonyManager;
     39 import android.util.Slog;
     40 
     41 /**
     42  * This class is a proxy for MmsService APIs. We need this because MmsService runs
     43  * in phone process and may crash anytime. This manages a connection to the actual
     44  * MmsService and bridges the public SMS/MMS APIs with MmsService implementation.
     45  */
     46 public class MmsServiceBroker extends SystemService {
     47     private static final String TAG = "MmsServiceBroker";
     48 
     49     private static final ComponentName MMS_SERVICE_COMPONENT =
     50             new ComponentName("com.android.mms.service", "com.android.mms.service.MmsService");
     51 
     52     private static final int MSG_TRY_CONNECTING = 1;
     53 
     54     private static final Uri FAKE_SMS_SENT_URI = Uri.parse("content://sms/sent/0");
     55     private static final Uri FAKE_MMS_SENT_URI = Uri.parse("content://mms/sent/0");
     56     private static final Uri FAKE_SMS_DRAFT_URI = Uri.parse("content://sms/draft/0");
     57     private static final Uri FAKE_MMS_DRAFT_URI = Uri.parse("content://mms/draft/0");
     58 
     59     private static final long SERVICE_CONNECTION_WAIT_TIME_MS = 4 * 1000L; // 4 seconds
     60     private static final long RETRY_DELAY_ON_DISCONNECTION_MS = 3 * 1000L; // 3 seconds
     61 
     62     private Context mContext;
     63     // The actual MMS service instance to invoke
     64     private volatile IMms mService;
     65 
     66     // Cached system service instances
     67     private volatile AppOpsManager mAppOpsManager = null;
     68     private volatile PackageManager mPackageManager = null;
     69     private volatile TelephonyManager mTelephonyManager = null;
     70 
     71     private final Handler mConnectionHandler = new Handler() {
     72         @Override
     73         public void handleMessage(Message msg) {
     74             switch (msg.what) {
     75                 case MSG_TRY_CONNECTING:
     76                     tryConnecting();
     77                     break;
     78                 default:
     79                     Slog.e(TAG, "Unknown message");
     80             }
     81         }
     82     };
     83 
     84     private ServiceConnection mConnection = new ServiceConnection() {
     85         @Override
     86         public void onServiceConnected(ComponentName name, IBinder service) {
     87             Slog.i(TAG, "MmsService connected");
     88             synchronized (MmsServiceBroker.this) {
     89                 mService = IMms.Stub.asInterface(service);
     90                 MmsServiceBroker.this.notifyAll();
     91             }
     92         }
     93 
     94         @Override
     95         public void onServiceDisconnected(ComponentName name) {
     96             Slog.i(TAG, "MmsService unexpectedly disconnected");
     97             synchronized (MmsServiceBroker.this) {
     98                 mService = null;
     99                 MmsServiceBroker.this.notifyAll();
    100             }
    101             // Retry connecting, but not too eager (with a delay)
    102             // since it may come back by itself.
    103             mConnectionHandler.sendMessageDelayed(
    104                     mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING),
    105                     RETRY_DELAY_ON_DISCONNECTION_MS);
    106         }
    107     };
    108 
    109     public MmsServiceBroker(Context context) {
    110         super(context);
    111         mContext = context;
    112         mService = null;
    113     }
    114 
    115     @Override
    116     public void onStart() {
    117         publishBinderService("imms", new BinderService());
    118     }
    119 
    120     public void systemRunning() {
    121         tryConnecting();
    122     }
    123 
    124     private void tryConnecting() {
    125         Slog.i(TAG, "Connecting to MmsService");
    126         synchronized (this) {
    127             if (mService != null) {
    128                 Slog.d(TAG, "Already connected");
    129                 return;
    130             }
    131             final Intent intent = new Intent();
    132             intent.setComponent(MMS_SERVICE_COMPONENT);
    133             try {
    134                 if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
    135                     Slog.e(TAG, "Failed to bind to MmsService");
    136                 }
    137             } catch (SecurityException e) {
    138                 Slog.e(TAG, "Forbidden to bind to MmsService", e);
    139             }
    140         }
    141     }
    142 
    143     private void ensureService() {
    144         synchronized (this) {
    145             if (mService == null) {
    146                 // Service is not connected. Try blocking connecting.
    147                 Slog.w(TAG, "MmsService not connected. Try connecting...");
    148                 mConnectionHandler.sendMessage(
    149                         mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
    150                 final long shouldEnd =
    151                         SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
    152                 long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
    153                 while (waitTime > 0) {
    154                     try {
    155                         // TODO: consider using Java concurrent construct instead of raw object wait
    156                         this.wait(waitTime);
    157                     } catch (InterruptedException e) {
    158                         Slog.w(TAG, "Connection wait interrupted", e);
    159                     }
    160                     if (mService != null) {
    161                         // Success
    162                         return;
    163                     }
    164                     // Calculate remaining waiting time to make sure we wait the full timeout period
    165                     waitTime = shouldEnd - SystemClock.elapsedRealtime();
    166                 }
    167                 // Timed out. Something's really wrong.
    168                 Slog.e(TAG, "Can not connect to MmsService (timed out)");
    169                 throw new RuntimeException("Timed out in connecting to MmsService");
    170             }
    171         }
    172     }
    173 
    174     /**
    175      * Making sure when we obtain the mService instance it is always valid.
    176      * Throws {@link RuntimeException} when it is empty.
    177      */
    178     private IMms getServiceGuarded() {
    179         ensureService();
    180         return mService;
    181     }
    182 
    183     private AppOpsManager getAppOpsManager() {
    184         if (mAppOpsManager == null) {
    185             mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
    186         }
    187         return mAppOpsManager;
    188     }
    189 
    190     private PackageManager getPackageManager() {
    191         if (mPackageManager == null) {
    192             mPackageManager = mContext.getPackageManager();
    193         }
    194         return mPackageManager;
    195     }
    196 
    197     private TelephonyManager getTelephonyManager() {
    198         if (mTelephonyManager == null) {
    199             mTelephonyManager = (TelephonyManager) mContext.getSystemService(
    200                     Context.TELEPHONY_SERVICE);
    201         }
    202         return mTelephonyManager;
    203     }
    204 
    205     /*
    206      * Throws a security exception unless the caller has carrier privilege.
    207      */
    208     private void enforceCarrierPrivilege() {
    209         String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
    210         for (String pkg : packages) {
    211             if (getTelephonyManager().checkCarrierPrivilegesForPackage(pkg) ==
    212                     TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
    213                 return;
    214             }
    215         }
    216         throw new SecurityException("No carrier privilege");
    217     }
    218 
    219     // Service API calls implementation, proxied to the real MmsService in "com.android.mms.service"
    220     private final class BinderService extends IMms.Stub {
    221         @Override
    222         public void sendMessage(long subId, String callingPkg, Uri contentUri,
    223                 String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
    224                         throws RemoteException {
    225             mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
    226             if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
    227                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    228                 return;
    229             }
    230             getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
    231                     configOverrides, sentIntent);
    232         }
    233 
    234         @Override
    235         public void downloadMessage(long subId, String callingPkg, String locationUrl,
    236                 Uri contentUri, Bundle configOverrides,
    237                 PendingIntent downloadedIntent) throws RemoteException {
    238             mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
    239                     "Download MMS message");
    240             if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(),
    241                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    242                 return;
    243             }
    244             getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
    245                     configOverrides, downloadedIntent);
    246         }
    247 
    248         @Override
    249         public void updateMmsSendStatus(int messageRef, byte[] pdu, int status)
    250                 throws RemoteException {
    251             enforceCarrierPrivilege();
    252             getServiceGuarded().updateMmsSendStatus(messageRef, pdu, status);
    253         }
    254 
    255         @Override
    256         public void updateMmsDownloadStatus(int messageRef, int status) throws RemoteException {
    257             enforceCarrierPrivilege();
    258             getServiceGuarded().updateMmsDownloadStatus(messageRef, status);
    259         }
    260 
    261         @Override
    262         public Bundle getCarrierConfigValues(long subId) throws RemoteException {
    263             return getServiceGuarded().getCarrierConfigValues(subId);
    264         }
    265 
    266         @Override
    267         public Uri importTextMessage(String callingPkg, String address, int type, String text,
    268                 long timestampMillis, boolean seen, boolean read) throws RemoteException {
    269             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message");
    270             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    271                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    272                 // Silently fail AppOps failure due to not being the default SMS app
    273                 // while writing the TelephonyProvider
    274                 return FAKE_SMS_SENT_URI;
    275             }
    276             return getServiceGuarded().importTextMessage(
    277                     callingPkg, address, type, text, timestampMillis, seen, read);
    278         }
    279 
    280         @Override
    281         public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
    282                 String messageId, long timestampSecs, boolean seen, boolean read)
    283                         throws RemoteException {
    284             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message");
    285             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    286                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    287                 // Silently fail AppOps failure due to not being the default SMS app
    288                 // while writing the TelephonyProvider
    289                 return FAKE_MMS_SENT_URI;
    290             }
    291             return getServiceGuarded().importMultimediaMessage(
    292                     callingPkg, contentUri, messageId, timestampSecs, seen, read);
    293         }
    294 
    295         @Override
    296         public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
    297                 throws RemoteException {
    298             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
    299                     "Delete SMS/MMS message");
    300             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    301                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    302                 return false;
    303             }
    304             return getServiceGuarded().deleteStoredMessage(callingPkg, messageUri);
    305         }
    306 
    307         @Override
    308         public boolean deleteStoredConversation(String callingPkg, long conversationId)
    309                 throws RemoteException {
    310             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
    311             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    312                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    313                 return false;
    314             }
    315             return getServiceGuarded().deleteStoredConversation(callingPkg, conversationId);
    316         }
    317 
    318         @Override
    319         public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
    320                 ContentValues statusValues) throws RemoteException {
    321             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
    322                     "Update SMS/MMS message");
    323             return getServiceGuarded()
    324                     .updateStoredMessageStatus(callingPkg, messageUri, statusValues);
    325         }
    326 
    327         @Override
    328         public boolean archiveStoredConversation(String callingPkg, long conversationId,
    329                 boolean archived) throws RemoteException {
    330             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
    331                     "Update SMS/MMS message");
    332             return getServiceGuarded()
    333                     .archiveStoredConversation(callingPkg, conversationId, archived);
    334         }
    335 
    336         @Override
    337         public Uri addTextMessageDraft(String callingPkg, String address, String text)
    338                 throws RemoteException {
    339             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft");
    340             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    341                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    342                 // Silently fail AppOps failure due to not being the default SMS app
    343                 // while writing the TelephonyProvider
    344                 return FAKE_SMS_DRAFT_URI;
    345             }
    346             return getServiceGuarded().addTextMessageDraft(callingPkg, address, text);
    347         }
    348 
    349         @Override
    350         public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
    351                 throws RemoteException {
    352             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS draft");
    353             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    354                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    355                 // Silently fail AppOps failure due to not being the default SMS app
    356                 // while writing the TelephonyProvider
    357                 return FAKE_MMS_DRAFT_URI;
    358             }
    359             return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
    360         }
    361 
    362         @Override
    363         public void sendStoredMessage(long subId, String callingPkg, Uri messageUri,
    364                 Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
    365             mContext.enforceCallingPermission(Manifest.permission.SEND_SMS,
    366                     "Send stored MMS message");
    367             if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
    368                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    369                 return;
    370             }
    371             getServiceGuarded().sendStoredMessage(subId, callingPkg, messageUri, configOverrides,
    372                     sentIntent);
    373         }
    374 
    375         @Override
    376         public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
    377             mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist");
    378             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
    379                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
    380                 return;
    381             }
    382             getServiceGuarded().setAutoPersisting(callingPkg, enabled);
    383         }
    384 
    385         @Override
    386         public boolean getAutoPersisting() throws RemoteException {
    387             return getServiceGuarded().getAutoPersisting();
    388         }
    389     }
    390 }
    391