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