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