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