Home | History | Annotate | Download | only in telephony
      1 /*
      2 * Copyright (C) 2015 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 
     17 package com.android.internal.telephony;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.ServiceConnection;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.ResolveInfo;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.IBinder;
     30 import android.os.Message;
     31 import android.os.Process;
     32 import android.os.SystemClock;
     33 import android.os.UserHandle;
     34 import android.service.carrier.CarrierService;
     35 import android.telephony.SubscriptionManager;
     36 import android.telephony.TelephonyManager;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 
     40 import com.android.internal.content.PackageMonitor;
     41 
     42 import java.io.FileDescriptor;
     43 import java.io.PrintWriter;
     44 import java.util.List;
     45 
     46 /**
     47  * Manages long-lived bindings to carrier services
     48  * @hide
     49  */
     50 public class CarrierServiceBindHelper {
     51     private static final String LOG_TAG = "CarrierSvcBindHelper";
     52 
     53     /**
     54      * How long to linger a binding after an app loses carrier privileges, as long as no new
     55      * binding comes in to take its place.
     56      */
     57     private static final int UNBIND_DELAY_MILLIS = 30 * 1000; // 30 seconds
     58 
     59     private Context mContext;
     60     private AppBinding[] mBindings;
     61     private String[] mLastSimState;
     62     private final PackageMonitor mPackageMonitor = new CarrierServicePackageMonitor();
     63 
     64     private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
     65         @Override
     66         public void onReceive(Context context, Intent intent) {
     67             final String action = intent.getAction();
     68             log("Received " + action);
     69 
     70             if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
     71                 // On user unlock, new components might become available, so reevaluate all
     72                 // bindings.
     73                 for (int phoneId = 0; phoneId < mBindings.length; phoneId++) {
     74                     mBindings[phoneId].rebind();
     75                 }
     76             }
     77         }
     78     };
     79 
     80     private static final int EVENT_REBIND = 0;
     81     private static final int EVENT_PERFORM_IMMEDIATE_UNBIND = 1;
     82 
     83     private Handler mHandler = new Handler() {
     84         @Override
     85         public void handleMessage(Message msg) {
     86             AppBinding binding;
     87             log("mHandler: " + msg.what);
     88 
     89             switch (msg.what) {
     90                 case EVENT_REBIND:
     91                     binding = (AppBinding) msg.obj;
     92                     log("Rebinding if necessary for phoneId: " + binding.getPhoneId());
     93                     binding.rebind();
     94                     break;
     95                 case EVENT_PERFORM_IMMEDIATE_UNBIND:
     96                     binding = (AppBinding) msg.obj;
     97                     binding.performImmediateUnbind();
     98                     break;
     99             }
    100         }
    101     };
    102 
    103     public CarrierServiceBindHelper(Context context) {
    104         mContext = context;
    105 
    106         int numPhones = TelephonyManager.from(context).getPhoneCount();
    107         mBindings = new AppBinding[numPhones];
    108         mLastSimState = new String[numPhones];
    109 
    110         for (int phoneId = 0; phoneId < numPhones; phoneId++) {
    111             mBindings[phoneId] = new AppBinding(phoneId);
    112         }
    113 
    114         mPackageMonitor.register(
    115                 context, mHandler.getLooper(), UserHandle.ALL, false /* externalStorage */);
    116         mContext.registerReceiverAsUser(mUserUnlockedReceiver, UserHandle.SYSTEM,
    117                 new IntentFilter(Intent.ACTION_USER_UNLOCKED), null /* broadcastPermission */,
    118                 mHandler);
    119     }
    120 
    121     void updateForPhoneId(int phoneId, String simState) {
    122         log("update binding for phoneId: " + phoneId + " simState: " + simState);
    123         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
    124             return;
    125         }
    126         if (TextUtils.isEmpty(simState) || phoneId >= mLastSimState.length) return;
    127         if (simState.equals(mLastSimState[phoneId])) {
    128             // ignore consecutive duplicated events
    129             return;
    130         } else {
    131             mLastSimState[phoneId] = simState;
    132         }
    133         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REBIND, mBindings[phoneId]));
    134     }
    135 
    136     private class AppBinding {
    137         private int phoneId;
    138         private CarrierServiceConnection connection;
    139         private int bindCount;
    140         private long lastBindStartMillis;
    141         private int unbindCount;
    142         private long lastUnbindMillis;
    143         private String carrierPackage;
    144         private String carrierServiceClass;
    145         private long mUnbindScheduledUptimeMillis = -1;
    146 
    147         public AppBinding(int phoneId) {
    148             this.phoneId = phoneId;
    149         }
    150 
    151         public int getPhoneId() {
    152             return phoneId;
    153         }
    154 
    155         /** Return the package that is currently being bound to, or null if there is no binding. */
    156         public String getPackage() {
    157             return carrierPackage;
    158         }
    159 
    160         /**
    161          * Update the bindings for the current carrier app for this phone.
    162          *
    163          * <p>Safe to call even if a binding already exists. If the current binding is invalid, it
    164          * will be dropped. If it is valid, it will be left untouched.
    165          */
    166         void rebind() {
    167             // Get the package name for the carrier app
    168             List<String> carrierPackageNames =
    169                 TelephonyManager.from(mContext).getCarrierPackageNamesForIntentAndPhone(
    170                     new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId
    171                 );
    172 
    173             if (carrierPackageNames == null || carrierPackageNames.size() <= 0) {
    174                 log("No carrier app for: " + phoneId);
    175                 // Unbind after a delay in case this is a temporary blip in carrier privileges.
    176                 unbind(false /* immediate */);
    177                 return;
    178             }
    179 
    180             log("Found carrier app: " + carrierPackageNames);
    181             String candidateCarrierPackage = carrierPackageNames.get(0);
    182             // If we are binding to a different package, unbind immediately from the current one.
    183             if (!TextUtils.equals(carrierPackage, candidateCarrierPackage)) {
    184                 unbind(true /* immediate */);
    185             }
    186 
    187             // Look up the carrier service
    188             Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
    189             carrierService.setPackage(candidateCarrierPackage);
    190 
    191             ResolveInfo carrierResolveInfo = mContext.getPackageManager().resolveService(
    192                 carrierService, PackageManager.GET_META_DATA);
    193             Bundle metadata = null;
    194             String candidateServiceClass = null;
    195             if (carrierResolveInfo != null) {
    196                 metadata = carrierResolveInfo.serviceInfo.metaData;
    197                 candidateServiceClass =
    198                         carrierResolveInfo.getComponentInfo().getComponentName().getClassName();
    199             }
    200 
    201             // Only bind if the service wants it
    202             if (metadata == null ||
    203                 !metadata.getBoolean("android.service.carrier.LONG_LIVED_BINDING", false)) {
    204                 log("Carrier app does not want a long lived binding");
    205                 unbind(true /* immediate */);
    206                 return;
    207             }
    208 
    209             if (!TextUtils.equals(carrierServiceClass, candidateServiceClass)) {
    210                 // Unbind immediately if the carrier service component has changed.
    211                 unbind(true /* immediate */);
    212             } else if (connection != null) {
    213                 // Component is unchanged and connection is up - do nothing, but cancel any
    214                 // scheduled unbinds.
    215                 cancelScheduledUnbind();
    216                 return;
    217             }
    218 
    219             carrierPackage = candidateCarrierPackage;
    220             carrierServiceClass = candidateServiceClass;
    221 
    222             log("Binding to " + carrierPackage + " for phone " + phoneId);
    223 
    224             // Log debug information
    225             bindCount++;
    226             lastBindStartMillis = System.currentTimeMillis();
    227 
    228             connection = new CarrierServiceConnection();
    229 
    230             String error;
    231             try {
    232                 if (mContext.bindServiceAsUser(carrierService, connection,
    233                         Context.BIND_AUTO_CREATE |  Context.BIND_FOREGROUND_SERVICE,
    234                         mHandler, Process.myUserHandle())) {
    235                     return;
    236                 }
    237 
    238                 error = "bindService returned false";
    239             } catch (SecurityException ex) {
    240                 error = ex.getMessage();
    241             }
    242 
    243             log("Unable to bind to " + carrierPackage + " for phone " + phoneId +
    244                 ". Error: " + error);
    245             unbind(true /* immediate */);
    246         }
    247 
    248         /**
    249          * Release the binding.
    250          *
    251          * @param immediate whether the binding should be released immediately or after a short
    252          *                  delay. This should be true unless the reason for the unbind is that no
    253          *                  app has carrier privileges, in which case it is useful to delay
    254          *                  unbinding in case this is a temporary SIM blip.
    255          */
    256         void unbind(boolean immediate) {
    257             if (connection == null) {
    258                 // Already fully unbound.
    259                 return;
    260             }
    261 
    262             // Only let the binding linger if a delayed unbind is requested *and* the connection is
    263             // currently active. If the connection is down, unbind immediately as the app is likely
    264             // not running anyway and it may be a permanent disconnection (e.g. the app was
    265             // disabled).
    266             if (immediate || !connection.connected) {
    267                 cancelScheduledUnbind();
    268                 performImmediateUnbind();
    269             } else if (mUnbindScheduledUptimeMillis == -1) {
    270                 long currentUptimeMillis = SystemClock.uptimeMillis();
    271                 mUnbindScheduledUptimeMillis = currentUptimeMillis + UNBIND_DELAY_MILLIS;
    272                 log("Scheduling unbind in " + UNBIND_DELAY_MILLIS + " millis");
    273                 mHandler.sendMessageAtTime(
    274                         mHandler.obtainMessage(EVENT_PERFORM_IMMEDIATE_UNBIND, this),
    275                         mUnbindScheduledUptimeMillis);
    276             }
    277         }
    278 
    279         private void performImmediateUnbind() {
    280             // Log debug information
    281             unbindCount++;
    282             lastUnbindMillis = System.currentTimeMillis();
    283 
    284             // Clear package state now that no binding is desired.
    285             carrierPackage = null;
    286             carrierServiceClass = null;
    287 
    288             // Actually unbind
    289             log("Unbinding from carrier app");
    290             mContext.unbindService(connection);
    291             connection = null;
    292             mUnbindScheduledUptimeMillis = -1;
    293         }
    294 
    295         private void cancelScheduledUnbind() {
    296             mHandler.removeMessages(EVENT_PERFORM_IMMEDIATE_UNBIND);
    297             mUnbindScheduledUptimeMillis = -1;
    298         }
    299 
    300         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    301             pw.println("Carrier app binding for phone " + phoneId);
    302             pw.println("  connection: " + connection);
    303             pw.println("  bindCount: " + bindCount);
    304             pw.println("  lastBindStartMillis: " + lastBindStartMillis);
    305             pw.println("  unbindCount: " + unbindCount);
    306             pw.println("  lastUnbindMillis: " + lastUnbindMillis);
    307             pw.println("  mUnbindScheduledUptimeMillis: " + mUnbindScheduledUptimeMillis);
    308             pw.println();
    309         }
    310     }
    311 
    312     private class CarrierServiceConnection implements ServiceConnection {
    313         private boolean connected;
    314 
    315         @Override
    316         public void onServiceConnected(ComponentName name, IBinder service) {
    317             log("Connected to carrier app: " + name.flattenToString());
    318             connected = true;
    319         }
    320 
    321         @Override
    322         public void onServiceDisconnected(ComponentName name) {
    323             log("Disconnected from carrier app: " + name.flattenToString());
    324             connected = false;
    325         }
    326 
    327         @Override
    328         public String toString() {
    329             return "CarrierServiceConnection[connected=" + connected + "]";
    330         }
    331     }
    332 
    333     private class CarrierServicePackageMonitor extends PackageMonitor {
    334         @Override
    335         public void onPackageAdded(String packageName, int reason) {
    336             evaluateBinding(packageName, true /* forceUnbind */);
    337         }
    338 
    339         @Override
    340         public void onPackageRemoved(String packageName, int reason) {
    341             evaluateBinding(packageName, true /* forceUnbind */);
    342         }
    343 
    344         @Override
    345         public void onPackageUpdateFinished(String packageName, int uid) {
    346             evaluateBinding(packageName, true /* forceUnbind */);
    347         }
    348 
    349         @Override
    350         public void onPackageModified(String packageName) {
    351             evaluateBinding(packageName, false /* forceUnbind */);
    352         }
    353 
    354         @Override
    355         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
    356             if (doit) {
    357                 for (String packageName : packages) {
    358                     evaluateBinding(packageName, true /* forceUnbind */);
    359                 }
    360             }
    361             return super.onHandleForceStop(intent, packages, uid, doit);
    362         }
    363 
    364         private void evaluateBinding(String carrierPackageName, boolean forceUnbind) {
    365             for (AppBinding appBinding : mBindings) {
    366                 String appBindingPackage = appBinding.getPackage();
    367                 boolean isBindingForPackage = carrierPackageName.equals(appBindingPackage);
    368                 // Only log if this package was a carrier package to avoid log spam in the common
    369                 // case that there are no carrier packages, but evaluate the binding if the package
    370                 // is unset, in case this package change resulted in a new carrier package becoming
    371                 // available for binding.
    372                 if (isBindingForPackage) {
    373                     log(carrierPackageName + " changed and corresponds to a phone. Rebinding.");
    374                 }
    375                 if (appBindingPackage == null || isBindingForPackage) {
    376                     if (forceUnbind) {
    377                         appBinding.unbind(true /* immediate */);
    378                     }
    379                     appBinding.rebind();
    380                 }
    381             }
    382         }
    383     }
    384 
    385     private static void log(String message) {
    386         Log.d(LOG_TAG, message);
    387     }
    388 
    389     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    390         pw.println("CarrierServiceBindHelper:");
    391         for (AppBinding binding : mBindings) {
    392             binding.dump(fd, pw, args);
    393         }
    394     }
    395 }
    396