blob: 18e15548ae0224d212bbc9433ea845e062807590 [file] [log] [blame]
/*
* Copyright (C) 2015 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.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.service.carrier.CarrierService;
import com.android.internal.telephony.IccCardConstants;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Manages long-lived bindings to carrier services
* @hide
*/
public class CarrierServiceBindHelper {
private static final String LOG_TAG = CarrierServiceBindHelper.class.getSimpleName();
private Context mContext;
private AppBinding[] mBindings;
private final BroadcastReceiver mReceiver = new PackageChangedBroadcastReceiver();
private static final int EVENT_BIND = 0;
private static final int EVENT_UNBIND = 1;
private static final int EVENT_BIND_TIMEOUT = 2;
private static final int EVENT_PACKAGE_CHANGED = 3;
private static final int BIND_TIMEOUT_MILLIS = 10000;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String carrierPackageName;
AppBinding binding;
log("mHandler: " + msg.what);
CarrierServiceConnection connection;
switch (msg.what) {
case EVENT_BIND:
binding = (AppBinding) msg.obj;
log("Binding to phoneId: " + binding.getPhoneId());
binding.bind();
break;
case EVENT_BIND_TIMEOUT:
binding = (AppBinding) msg.obj;
log("Bind timeout for phoneId: " + binding.getPhoneId());
binding.unbind();
break;
case EVENT_UNBIND:
binding = (AppBinding) msg.obj;
log("Unbinding for phoneId: " + binding.getPhoneId());
binding.unbind();
break;
case EVENT_PACKAGE_CHANGED:
carrierPackageName = (String) msg.obj;
for (AppBinding appBinding : mBindings) {
if (carrierPackageName.equals(appBinding.getPackage())) {
log(carrierPackageName + " changed and corresponds to a phone. Rebinding.");
appBinding.bind();
}
}
break;
}
}
};
public CarrierServiceBindHelper(Context context) {
mContext = context;
int numPhones = TelephonyManager.from(context).getPhoneCount();
mBindings = new AppBinding[numPhones];
for (int phoneId = 0; phoneId < numPhones; phoneId++) {
mBindings[phoneId] = new AppBinding(phoneId);
}
// Register for package updates. Update app or uninstall app update will have all 3 intents,
// in the order or removed, added, replaced, all with extra_replace set to true.
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
pkgFilter.addDataScheme("package");
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, pkgFilter, null, null);
}
public void updateForPhoneId(int phoneId, String simState) {
log("update binding for phoneId: " + phoneId + " simState: " + simState);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
return;
}
// requires Java 7 for switch on string.
switch (simState) {
case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR:
case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN:
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNBIND, mBindings[phoneId]));
break;
case IccCardConstants.INTENT_VALUE_ICC_LOADED:
case IccCardConstants.INTENT_VALUE_ICC_LOCKED:
mHandler.sendMessage(mHandler.obtainMessage(EVENT_BIND, mBindings[phoneId]));
break;
}
}
private class AppBinding {
private int phoneId;
private CarrierServiceConnection connection;
private int bindCount;
private long lastBindStartMillis;
private int unbindCount;
private long lastUnbindMillis;
private String carrierPackage;
public AppBinding(int phoneId) {
this.phoneId = phoneId;
}
public int getPhoneId() {
return phoneId;
}
public String getPackage() {
return carrierPackage;
}
public void handleConnectionDown() {
connection = null;
}
public boolean bind() {
// Make sure there is no existing binding for this phone
unbind();
// Get the package name for the carrier app
List<String> carrierPackageNames =
TelephonyManager.from(mContext).getCarrierPackageNamesForIntentAndPhone(
new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId
);
if (carrierPackageNames == null || carrierPackageNames.size() <= 0) {
log("No carrier app for: " + phoneId);
return false;
}
log("Found carrier app: " + carrierPackageNames);
carrierPackage = carrierPackageNames.get(0);
// Log debug information
bindCount++;
lastBindStartMillis = System.currentTimeMillis();
// Look up the carrier service
Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
carrierService.setPackage(carrierPackage);
ResolveInfo carrierResolveInfo = mContext.getPackageManager().resolveService(
carrierService, PackageManager.GET_META_DATA);
Bundle metadata = null;
if (carrierResolveInfo != null) {
metadata = carrierResolveInfo.serviceInfo.metaData;
}
// Only bind if the service wants it
if (metadata == null ||
!metadata.getBoolean("android.service.carrier.LONG_LIVED_BINDING", false)) {
log("Carrier app does not want a long lived binding");
return false;
}
log("Binding to " + carrierPackage + " for phone " + phoneId);
connection = new CarrierServiceConnection(this);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(EVENT_BIND_TIMEOUT, this),
BIND_TIMEOUT_MILLIS);
String error;
try {
if (mContext.bindService(carrierService, connection, Context.BIND_AUTO_CREATE)) {
return true;
}
error = "bindService returned false";
} catch (SecurityException ex) {
error = ex.getMessage();
}
log("Unable to bind to " + carrierPackage + " for phone " + phoneId +
". Error: " + error);
return false;
}
public void unbind() {
mHandler.removeMessages(EVENT_BIND_TIMEOUT, this);
if (connection == null) {
return;
}
// Log debug information
unbindCount++;
lastUnbindMillis = System.currentTimeMillis();
// Actually unbind
log("Unbinding from carrier app");
mContext.unbindService(connection);
connection = null;
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Carrier app binding for phone " + phoneId);
pw.println(" connection: " + connection);
pw.println(" bindCount: " + bindCount);
pw.println(" lastBindStartMillis: " + lastBindStartMillis);
pw.println(" unbindCount: " + unbindCount);
pw.println(" lastUnbindMillis: " + lastUnbindMillis);
pw.println();
}
}
private class CarrierServiceConnection implements ServiceConnection {
private IBinder service;
private AppBinding binding;
public CarrierServiceConnection(AppBinding binding) {
this.binding = binding;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
log("Connected to carrier app: " + name.flattenToString());
mHandler.removeMessages(EVENT_BIND_TIMEOUT, binding);
this.service = service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
log("Disconnected from carrier app: " + name.flattenToString());
this.service = null;
this.binding.handleConnectionDown();
}
}
private class PackageChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
log("Receive action: " + action);
switch (action) {
case Intent.ACTION_PACKAGE_ADDED:
case Intent.ACTION_PACKAGE_REMOVED:
case Intent.ACTION_PACKAGE_REPLACED:
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
String packageName = mContext.getPackageManager().getNameForUid(uid);
if (packageName != null) {
// We don't have a phoneId for arg1.
mHandler.sendMessage(
mHandler.obtainMessage(EVENT_PACKAGE_CHANGED, packageName));
}
break;
}
}
}
private static void log(String message) {
Log.d(LOG_TAG, message);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("CarrierServiceBindHelper:");
for (AppBinding binding : mBindings) {
binding.dump(fd, pw, args);
}
}
}