blob: cabb00997d6ef95dfa3738af614f8ebe9cb7f361 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.service.carrier.CarrierMessagingService;
import android.service.carrier.CarrierMessagingServiceWrapper;
import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper;
import android.service.carrier.MessagePdu;
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccController;
import com.android.telephony.Rlog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Filters incoming SMS with carrier services.
* <p> A new instance must be created for filtering each message.
*/
public class CarrierServicesSmsFilter {
protected static final boolean DBG = true;
/** onFilterComplete is not called. */
public static final int EVENT_ON_FILTER_COMPLETE_NOT_CALLED = 1;
/** onFilterComplete timeout. */
public static final int FILTER_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000; //10 minutes
private final Context mContext;
private final Phone mPhone;
private final byte[][] mPdus;
private final int mDestPort;
private final String mPduFormat;
private final CarrierServicesSmsFilterCallbackInterface mCarrierServicesSmsFilterCallback;
private final String mLogTag;
private final CallbackTimeoutHandler mCallbackTimeoutHandler;
private final LocalLog mLocalLog;
private FilterAggregator mFilterAggregator;
@VisibleForTesting
public CarrierServicesSmsFilter(
Context context,
Phone phone,
byte[][] pdus,
int destPort,
String pduFormat,
CarrierServicesSmsFilterCallbackInterface carrierServicesSmsFilterCallback,
String logTag,
LocalLog localLog) {
mContext = context;
mPhone = phone;
mPdus = pdus;
mDestPort = destPort;
mPduFormat = pduFormat;
mCarrierServicesSmsFilterCallback = carrierServicesSmsFilterCallback;
mLogTag = logTag;
mCallbackTimeoutHandler = new CallbackTimeoutHandler();
mLocalLog = localLog;
}
/**
* @return {@code true} if the SMS was handled by carrier services.
*/
@VisibleForTesting
public boolean filter() {
Optional<String> carrierAppForFiltering = getCarrierAppPackageForFiltering();
List<String> smsFilterPackages = new ArrayList<>();
if (carrierAppForFiltering.isPresent()) {
smsFilterPackages.add(carrierAppForFiltering.get());
}
String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
new Intent(CarrierMessagingService.SERVICE_INTERFACE));
if (carrierImsPackage != null) {
smsFilterPackages.add(carrierImsPackage);
}
if (mFilterAggregator != null) {
String errMsg = "Cannot reuse the same CarrierServiceSmsFilter object for filtering.";
loge(errMsg);
throw new RuntimeException(errMsg);
}
int numPackages = smsFilterPackages.size();
if (numPackages > 0) {
mFilterAggregator = new FilterAggregator(numPackages);
//start the timer
mCallbackTimeoutHandler.sendMessageDelayed(mCallbackTimeoutHandler
.obtainMessage(EVENT_ON_FILTER_COMPLETE_NOT_CALLED),
FILTER_COMPLETE_TIMEOUT_MS);
for (String smsFilterPackage : smsFilterPackages) {
filterWithPackage(smsFilterPackage, mFilterAggregator);
}
return true;
} else {
return false;
}
}
private Optional<String> getCarrierAppPackageForFiltering() {
List<String> carrierPackages = null;
UiccCard card = UiccController.getInstance().getUiccCard(mPhone.getPhoneId());
if (card != null) {
carrierPackages = card.getCarrierPackageNamesForIntent(
mContext.getPackageManager(),
new Intent(CarrierMessagingService.SERVICE_INTERFACE));
} else {
Rlog.e(mLogTag, "UiccCard not initialized.");
}
if (carrierPackages != null && carrierPackages.size() == 1) {
log("Found carrier package.");
return Optional.of(carrierPackages.get(0));
}
// It is possible that carrier app is not present as a CarrierPackage, but instead as a
// system app
List<String> systemPackages =
getSystemAppForIntent(new Intent(CarrierMessagingService.SERVICE_INTERFACE));
if (systemPackages != null && systemPackages.size() == 1) {
log("Found system package.");
return Optional.of(systemPackages.get(0));
}
logv("Unable to find carrier package: " + carrierPackages
+ ", nor systemPackages: " + systemPackages);
return Optional.empty();
}
private void filterWithPackage(String packageName, FilterAggregator filterAggregator) {
CarrierSmsFilter smsFilter = new CarrierSmsFilter(mPdus, mDestPort, mPduFormat);
CarrierSmsFilterCallback smsFilterCallback =
new CarrierSmsFilterCallback(filterAggregator, smsFilter);
filterAggregator.addToCallbacks(smsFilterCallback);
smsFilter.filterSms(packageName, smsFilterCallback);
}
private List<String> getSystemAppForIntent(Intent intent) {
List<String> packages = new ArrayList<String>();
PackageManager packageManager = mContext.getPackageManager();
List<ResolveInfo> receivers = packageManager.queryIntentServices(intent, 0);
String carrierFilterSmsPerm = "android.permission.CARRIER_FILTER_SMS";
for (ResolveInfo info : receivers) {
if (info.serviceInfo == null) {
loge("Can't get service information from " + info);
continue;
}
String packageName = info.serviceInfo.packageName;
if (packageManager.checkPermission(carrierFilterSmsPerm, packageName)
== packageManager.PERMISSION_GRANTED) {
packages.add(packageName);
if (DBG) log("getSystemAppForIntent: added package " + packageName);
}
}
return packages;
}
private void log(String message) {
Rlog.d(mLogTag, message);
}
private void loge(String message) {
Rlog.e(mLogTag, message);
}
private void logv(String message) {
Rlog.v(mLogTag, message);
}
/**
* Result of filtering SMS is returned in this callback.
*/
@VisibleForTesting
public interface CarrierServicesSmsFilterCallbackInterface {
void onFilterComplete(int result);
}
/**
* Asynchronously binds to the carrier messaging service, and filters out the message if
* instructed to do so by the carrier messaging service. A new instance must be used for every
* message.
*/
private final class CarrierSmsFilter extends CarrierMessagingServiceWrapper {
private final byte[][] mPdus;
private final int mDestPort;
private final String mSmsFormat;
// Instantiated in filterSms.
private volatile CarrierSmsFilterCallback mSmsFilterCallback;
CarrierSmsFilter(byte[][] pdus, int destPort, String smsFormat) {
mPdus = pdus;
mDestPort = destPort;
mSmsFormat = smsFormat;
}
/**
* Attempts to bind to a {@link CarrierMessagingService}. Filtering is initiated
* asynchronously once the service is ready using {@link #onServiceReady()}.
*/
void filterSms(String carrierPackageName, CarrierSmsFilterCallback smsFilterCallback) {
mSmsFilterCallback = smsFilterCallback;
if (!bindToCarrierMessagingService(mContext, carrierPackageName)) {
loge("bindService() for carrier messaging service failed");
smsFilterCallback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
} else {
logv("bindService() for carrier messaging service succeeded");
}
}
/**
* Invokes the {@code carrierMessagingService} to filter messages. The filtering result is
* delivered to {@code smsFilterCallback}.
*/
@Override
public void onServiceReady() {
try {
log("onServiceReady: calling filterSms");
filterSms(new MessagePdu(Arrays.asList(mPdus)), mSmsFormat, mDestPort,
mPhone.getSubId(), mSmsFilterCallback);
} catch (RuntimeException e) {
loge("Exception filtering the SMS: " + e);
mSmsFilterCallback.onFilterComplete(
CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
}
}
}
/**
* A callback used to notify the platform of the carrier messaging app filtering result. Once
* the result is ready, the carrier messaging service connection is disposed.
*/
private final class CarrierSmsFilterCallback extends CarrierMessagingCallbackWrapper {
private final FilterAggregator mFilterAggregator;
private final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper;
private boolean mIsOnFilterCompleteCalled;
CarrierSmsFilterCallback(FilterAggregator filterAggregator,
CarrierMessagingServiceWrapper carrierMessagingServiceWrapper) {
mFilterAggregator = filterAggregator;
mCarrierMessagingServiceWrapper = carrierMessagingServiceWrapper;
mIsOnFilterCompleteCalled = false;
}
/**
* This method should be called only once.
*/
@Override
public void onFilterComplete(int result) {
log("onFilterComplete called with result: " + result);
// in the case that timeout has already passed and triggered, but the initial callback
// is run afterwards, we should not follow through
if (!mIsOnFilterCompleteCalled) {
mIsOnFilterCompleteCalled = true;
mCarrierMessagingServiceWrapper.disposeConnection(mContext);
mFilterAggregator.onFilterComplete(result);
}
}
@Override
public void onSendSmsComplete(int result, int messageRef) {
loge("Unexpected onSendSmsComplete call with result: " + result);
}
@Override
public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
loge("Unexpected onSendMultipartSmsComplete call with result: " + result);
}
@Override
public void onSendMmsComplete(int result, byte[] sendConfPdu) {
loge("Unexpected onSendMmsComplete call with result: " + result);
}
@Override
public void onDownloadMmsComplete(int result) {
loge("Unexpected onDownloadMmsComplete call with result: " + result);
}
}
private final class FilterAggregator {
private final Object mFilterLock = new Object();
private int mNumPendingFilters;
private final Set<CarrierSmsFilterCallback> mCallbacks;
private int mFilterResult;
FilterAggregator(int numFilters) {
mNumPendingFilters = numFilters;
mCallbacks = new HashSet<>();
mFilterResult = CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT;
}
void onFilterComplete(int result) {
synchronized (mFilterLock) {
mNumPendingFilters--;
combine(result);
if (mNumPendingFilters == 0) {
// Calling identity was the CarrierMessagingService in this callback, change it
// back to ours.
long token = Binder.clearCallingIdentity();
try {
mCarrierServicesSmsFilterCallback.onFilterComplete(mFilterResult);
} finally {
// return back to the CarrierMessagingService, restore the calling identity.
Binder.restoreCallingIdentity(token);
}
//all onFilterCompletes called before timeout has triggered
//remove the pending message
log("onFilterComplete: called successfully with result = " + result);
mCallbackTimeoutHandler.removeMessages(EVENT_ON_FILTER_COMPLETE_NOT_CALLED);
} else {
log("onFilterComplete: waiting for pending filters " + mNumPendingFilters);
}
}
}
private void combine(int result) {
mFilterResult = mFilterResult | result;
}
private void addToCallbacks(CarrierSmsFilterCallback callback) {
mCallbacks.add(callback);
}
}
protected final class CallbackTimeoutHandler extends Handler {
private static final boolean DBG = true;
@Override
public void handleMessage(Message msg) {
if (DBG) {
log("CallbackTimeoutHandler handleMessage(" + msg.what + ")");
}
switch(msg.what) {
case EVENT_ON_FILTER_COMPLETE_NOT_CALLED:
mLocalLog.log("CarrierServicesSmsFilter: onFilterComplete timeout: not"
+ " called before " + FILTER_COMPLETE_TIMEOUT_MS + " milliseconds.");
handleFilterCallbacksTimeout();
break;
}
}
private void handleFilterCallbacksTimeout() {
for (CarrierSmsFilterCallback callback : mFilterAggregator.mCallbacks) {
log("handleFilterCallbacksTimeout: calling onFilterComplete");
callback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
}
}
}
}