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