| /* |
| * 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); |
| } |
| } |
| } |