1/*
2 * Copyright (C) 2017 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.internal.telephony;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.os.Binder;
24import android.os.RemoteException;
25import android.service.carrier.CarrierMessagingService;
26import android.service.carrier.ICarrierMessagingCallback;
27import android.service.carrier.ICarrierMessagingService;
28import android.service.carrier.MessagePdu;
29import android.telephony.CarrierMessagingServiceManager;
30import android.telephony.Rlog;
31
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.internal.telephony.uicc.UiccCard;
34import com.android.internal.telephony.uicc.UiccController;
35
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.List;
39import java.util.Optional;
40
41/**
42 * Filters incoming SMS with carrier services.
43 * <p> A new instance must be created for filtering each message.
44 */
45public class CarrierServicesSmsFilter {
46    protected static final boolean DBG = true;
47
48    private final Context mContext;
49    private final Phone mPhone;
50    private final byte[][] mPdus;
51    private final int mDestPort;
52    private final String mPduFormat;
53    private final CarrierServicesSmsFilterCallbackInterface mCarrierServicesSmsFilterCallback;
54    private final String mLogTag;
55
56    @VisibleForTesting
57    public CarrierServicesSmsFilter(
58            Context context,
59            Phone phone,
60            byte[][] pdus,
61            int destPort,
62            String pduFormat,
63            CarrierServicesSmsFilterCallbackInterface carrierServicesSmsFilterCallback,
64            String logTag) {
65        mContext = context;
66        mPhone = phone;
67        mPdus = pdus;
68        mDestPort = destPort;
69        mPduFormat = pduFormat;
70        mCarrierServicesSmsFilterCallback = carrierServicesSmsFilterCallback;
71        mLogTag = logTag;
72    }
73
74    /**
75     * @return {@code true} if the SMS was handled by carrier services.
76     */
77    @VisibleForTesting
78    public boolean filter() {
79        Optional<String> carrierAppForFiltering = getCarrierAppPackageForFiltering();
80        List<String> smsFilterPackages = new ArrayList<>();
81        if (carrierAppForFiltering.isPresent()) {
82            smsFilterPackages.add(carrierAppForFiltering.get());
83        }
84        String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
85                new Intent(CarrierMessagingService.SERVICE_INTERFACE));
86        if (carrierImsPackage != null) {
87            smsFilterPackages.add(carrierImsPackage);
88        }
89        FilterAggregator filterAggregator = new FilterAggregator(smsFilterPackages.size());
90        for (String smsFilterPackage : smsFilterPackages) {
91            filterWithPackage(smsFilterPackage, filterAggregator);
92        }
93        boolean handled = smsFilterPackages.size() > 0;
94        return handled;
95    }
96
97    private Optional<String> getCarrierAppPackageForFiltering() {
98        List<String> carrierPackages = null;
99        UiccCard card = UiccController.getInstance().getUiccCard(mPhone.getPhoneId());
100        if (card != null) {
101            carrierPackages = card.getCarrierPackageNamesForIntent(
102                    mContext.getPackageManager(),
103                    new Intent(CarrierMessagingService.SERVICE_INTERFACE));
104        } else {
105            Rlog.e(mLogTag, "UiccCard not initialized.");
106        }
107        if (carrierPackages != null && carrierPackages.size() == 1) {
108            log("Found carrier package.");
109            return Optional.of(carrierPackages.get(0));
110        }
111
112        // It is possible that carrier app is not present as a CarrierPackage, but instead as a
113        // system app
114        List<String> systemPackages =
115                getSystemAppForIntent(new Intent(CarrierMessagingService.SERVICE_INTERFACE));
116
117        if (systemPackages != null && systemPackages.size() == 1) {
118            log("Found system package.");
119            return Optional.of(systemPackages.get(0));
120        }
121        logv("Unable to find carrier package: " + carrierPackages
122                + ", nor systemPackages: " + systemPackages);
123        return Optional.empty();
124    }
125
126    private void filterWithPackage(String packageName, FilterAggregator filterAggregator) {
127        CarrierSmsFilter smsFilter = new CarrierSmsFilter(mPdus, mDestPort, mPduFormat);
128        CarrierSmsFilterCallback smsFilterCallback =
129                new CarrierSmsFilterCallback(filterAggregator, smsFilter);
130        smsFilter.filterSms(packageName, smsFilterCallback);
131    }
132
133    private List<String> getSystemAppForIntent(Intent intent) {
134        List<String> packages = new ArrayList<String>();
135        PackageManager packageManager = mContext.getPackageManager();
136        List<ResolveInfo> receivers = packageManager.queryIntentServices(intent, 0);
137        String carrierFilterSmsPerm = "android.permission.CARRIER_FILTER_SMS";
138
139        for (ResolveInfo info : receivers) {
140            if (info.serviceInfo == null) {
141                loge("Can't get service information from " + info);
142                continue;
143            }
144            String packageName = info.serviceInfo.packageName;
145            if (packageManager.checkPermission(carrierFilterSmsPerm, packageName)
146                    == packageManager.PERMISSION_GRANTED) {
147                packages.add(packageName);
148                if (DBG) log("getSystemAppForIntent: added package " + packageName);
149            }
150        }
151        return packages;
152    }
153
154    private void log(String message) {
155        Rlog.d(mLogTag, message);
156    }
157
158    private void loge(String message) {
159        Rlog.e(mLogTag, message);
160    }
161
162    private void logv(String message) {
163        Rlog.e(mLogTag, message);
164    }
165
166    /**
167     * Result of filtering SMS is returned in this callback.
168     */
169    @VisibleForTesting
170    public interface CarrierServicesSmsFilterCallbackInterface {
171        void onFilterComplete(int result);
172    }
173
174    /**
175     * Asynchronously binds to the carrier messaging service, and filters out the message if
176     * instructed to do so by the carrier messaging service. A new instance must be used for every
177     * message.
178     */
179    private final class CarrierSmsFilter extends CarrierMessagingServiceManager {
180        private final byte[][] mPdus;
181        private final int mDestPort;
182        private final String mSmsFormat;
183        // Instantiated in filterSms.
184        private volatile CarrierSmsFilterCallback mSmsFilterCallback;
185
186        CarrierSmsFilter(byte[][] pdus, int destPort, String smsFormat) {
187            mPdus = pdus;
188            mDestPort = destPort;
189            mSmsFormat = smsFormat;
190        }
191
192        /**
193         * Attempts to bind to a {@link ICarrierMessagingService}. Filtering is initiated
194         * asynchronously once the service is ready using {@link #onServiceReady}.
195         */
196        void filterSms(String carrierPackageName, CarrierSmsFilterCallback smsFilterCallback) {
197            mSmsFilterCallback = smsFilterCallback;
198            if (!bindToCarrierMessagingService(mContext, carrierPackageName)) {
199                loge("bindService() for carrier messaging service failed");
200                smsFilterCallback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
201            } else {
202                logv("bindService() for carrier messaging service succeeded");
203            }
204        }
205
206        /**
207         * Invokes the {@code carrierMessagingService} to filter messages. The filtering result is
208         * delivered to {@code smsFilterCallback}.
209         */
210        @Override
211        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
212            try {
213                carrierMessagingService.filterSms(
214                        new MessagePdu(Arrays.asList(mPdus)), mSmsFormat, mDestPort,
215                        mPhone.getSubId(), mSmsFilterCallback);
216            } catch (RemoteException e) {
217                loge("Exception filtering the SMS: " + e);
218                mSmsFilterCallback.onFilterComplete(
219                        CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
220            }
221        }
222    }
223
224    /**
225     * A callback used to notify the platform of the carrier messaging app filtering result. Once
226     * the result is ready, the carrier messaging service connection is disposed.
227     */
228    private final class CarrierSmsFilterCallback extends ICarrierMessagingCallback.Stub {
229        private final FilterAggregator mFilterAggregator;
230        private final CarrierMessagingServiceManager mCarrierMessagingServiceManager;
231
232        CarrierSmsFilterCallback(FilterAggregator filterAggregator,
233                                 CarrierMessagingServiceManager carrierMessagingServiceManager) {
234            mFilterAggregator = filterAggregator;
235            mCarrierMessagingServiceManager = carrierMessagingServiceManager;
236        }
237
238        /**
239         * This method should be called only once.
240         */
241        @Override
242        public void onFilterComplete(int result) {
243            mCarrierMessagingServiceManager.disposeConnection(mContext);
244            mFilterAggregator.onFilterComplete(result);
245        }
246
247        @Override
248        public void onSendSmsComplete(int result, int messageRef) {
249            loge("Unexpected onSendSmsComplete call with result: " + result);
250        }
251
252        @Override
253        public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
254            loge("Unexpected onSendMultipartSmsComplete call with result: " + result);
255        }
256
257        @Override
258        public void onSendMmsComplete(int result, byte[] sendConfPdu) {
259            loge("Unexpected onSendMmsComplete call with result: " + result);
260        }
261
262        @Override
263        public void onDownloadMmsComplete(int result) {
264            loge("Unexpected onDownloadMmsComplete call with result: " + result);
265        }
266    }
267
268    private final class FilterAggregator {
269        private final Object mFilterLock = new Object();
270        private int mNumPendingFilters;
271        private int mFilterResult;
272
273        FilterAggregator(int numFilters) {
274            mNumPendingFilters = numFilters;
275            mFilterResult = CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT;
276        }
277
278        void onFilterComplete(int result) {
279            synchronized (mFilterLock) {
280                mNumPendingFilters--;
281                combine(result);
282                if (mNumPendingFilters == 0) {
283                    // Calling identity was the CarrierMessagingService in this callback, change it
284                    // back to ours.
285                    long token = Binder.clearCallingIdentity();
286                    try {
287                        mCarrierServicesSmsFilterCallback.onFilterComplete(mFilterResult);
288                    } finally {
289                        // return back to the CarrierMessagingService, restore the calling identity.
290                        Binder.restoreCallingIdentity(token);
291                    }
292                }
293            }
294        }
295
296        private void combine(int result) {
297            mFilterResult = mFilterResult | result;
298        }
299    }
300}
301