Fetch config asynchronously.

Update CarrierConfigLoader to fetch config from config apps
asynchronously. This prevents the handler thread (which is the main
thread) from becoming blocked.

Bug: 63176442
Test: runtest carrierconfig-unit & manual
Change-Id: I32eae30b192deac74f56e34c3d466f7121c71039
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 4d09c32..a26e3d8 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.service.carrier.CarrierService.ICarrierServiceWrapper.KEY_CONFIG_BUNDLE;
+import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -30,16 +32,15 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.AsyncResult;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
@@ -54,9 +55,7 @@
 import com.android.internal.telephony.ICarrierConfigLoader;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -112,14 +111,14 @@
     private static final int EVENT_CONNECTED_TO_DEFAULT = 3;
     // Has connected to carrier app.
     private static final int EVENT_CONNECTED_TO_CARRIER = 4;
-    // Config has been loaded from default app.
-    private static final int EVENT_LOADED_FROM_DEFAULT = 5;
-    // Config has been loaded from carrier app.
-    private static final int EVENT_LOADED_FROM_CARRIER = 6;
+    // Config has been loaded from default app (or cache).
+    private static final int EVENT_FETCH_DEFAULT_DONE = 5;
+    // Config has been loaded from carrier app (or cache).
+    private static final int EVENT_FETCH_CARRIER_DONE = 6;
     // Attempt to fetch from default app or read from XML.
-    private static final int EVENT_FETCH_DEFAULT = 7;
+    private static final int EVENT_DO_FETCH_DEFAULT = 7;
     // Attempt to fetch from carrier app or read from XML.
-    private static final int EVENT_FETCH_CARRIER = 8;
+    private static final int EVENT_DO_FETCH_CARRIER = 8;
     // A package has been installed, uninstalled, or updated.
     private static final int EVENT_PACKAGE_CHANGED = 9;
     // Bind timed out for the default app.
@@ -130,6 +129,10 @@
     private static final int EVENT_CHECK_SYSTEM_UPDATE = 12;
     // Rerun carrier config binding after system is unlocked.
     private static final int EVENT_SYSTEM_UNLOCKED = 13;
+    // Fetching config timed out from the default app.
+    private static final int EVENT_FETCH_DEFAULT_TIMEOUT = 14;
+    // Fetching config timed out from a carrier app.
+    private static final int EVENT_FETCH_CARRIER_TIMEOUT = 15;
 
     private static final int BIND_TIMEOUT_MILLIS = 30000;
 
@@ -144,8 +147,8 @@
     // Handler to process various events.
     //
     // For each phoneId, the event sequence should be:
-    //     fetch default, connected to default, loaded from default,
-    //     fetch carrier, connected to carrier, loaded from carrier.
+    //     fetch default, connected to default, fetch default (async), fetch default done,
+    //     fetch carrier, connected to carrier, fetch carrier (async), fetch carrier done.
     //
     // If there is a saved config file for either the default app or the carrier app, we skip
     // binding to the app and go straight from fetch to loaded.
@@ -158,42 +161,41 @@
     // 2. loading from default app if there is no carrier app (even if read from a file)
     // 3. clearing config (e.g. due to sim removal)
     // 4. encountering bind or IPC error
-    private Handler mHandler = new Handler() {
-            @Override
+    private class ConfigHandler extends Handler {
+        @Override
         public void handleMessage(Message msg) {
-            int phoneId = msg.arg1;
+            final int phoneId = msg.arg1;
             log("mHandler: " + msg.what + " phoneId: " + phoneId);
-            String iccid;
-            CarrierIdentifier carrierId;
-            String carrierPackageName;
-            CarrierServiceConnection conn;
-            PersistableBundle config;
             switch (msg.what) {
                 case EVENT_CLEAR_CONFIG:
-                    if (mConfigFromDefaultApp[phoneId] == null &&
-                        mConfigFromCarrierApp[phoneId] == null)
-                        break;
+                {
+                    if (mConfigFromDefaultApp[phoneId] == null
+                            && mConfigFromCarrierApp[phoneId] == null) break;
                     mConfigFromDefaultApp[phoneId] = null;
                     mConfigFromCarrierApp[phoneId] = null;
                     mServiceConnection[phoneId] = null;
                     broadcastConfigChangedIntent(phoneId);
                     break;
+                }
 
                 case EVENT_SYSTEM_UNLOCKED:
+                {
                     for (int i = 0; i < TelephonyManager.from(mContext).getPhoneCount(); ++i) {
-                        // When user unlock device, we should only try to send broadcast again if
-                        // we have sent it before unlock. This will avoid we try to load carrier
-                        // config when SIM is still loading when unlock happens.
+                        // When user unlock device, we should only try to send broadcast again if we
+                        // have sent it before unlock. This will avoid we try to load carrier config
+                        // when SIM is still loading when unlock happens.
                         if (mHasSentConfigChange[i]) {
                             updateConfigForPhoneId(i);
                         }
                     }
                     break;
+                }
 
                 case EVENT_PACKAGE_CHANGED:
-                    carrierPackageName = (String) msg.obj;
-                    // Only update if there are cached config removed to avoid updating config
-                    // for unrelated packages.
+                {
+                    final String carrierPackageName = (String) msg.obj;
+                    // Only update if there are cached config removed to avoid updating config for
+                    // unrelated packages.
                     if (clearCachedConfigForPackage(carrierPackageName)) {
                         int numPhones = TelephonyManager.from(mContext).getPhoneCount();
                         for (int i = 0; i < numPhones; ++i) {
@@ -201,132 +203,228 @@
                         }
                     }
                     break;
+                }
 
-                case EVENT_FETCH_DEFAULT:
-                    iccid = getIccIdForPhoneId(phoneId);
-                    config = restoreConfigFromXml(mPlatformCarrierConfigPackage, iccid);
+                case EVENT_DO_FETCH_DEFAULT:
+                {
+                    final String iccid = getIccIdForPhoneId(phoneId);
+                    final PersistableBundle config =
+                            restoreConfigFromXml(mPlatformCarrierConfigPackage, iccid);
                     if (config != null) {
-                        log("Loaded config from XML. package=" + mPlatformCarrierConfigPackage
-                                + " phoneId=" + phoneId);
+                        log(
+                                "Loaded config from XML. package="
+                                        + mPlatformCarrierConfigPackage
+                                        + " phoneId="
+                                        + phoneId);
                         mConfigFromDefaultApp[phoneId] = config;
-                        Message newMsg = obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1);
+                        Message newMsg = obtainMessage(EVENT_FETCH_DEFAULT_DONE, phoneId, -1);
                         newMsg.getData().putBoolean("loaded_from_xml", true);
                         mHandler.sendMessage(newMsg);
                     } else {
-                        if (bindToConfigPackage(mPlatformCarrierConfigPackage,
-                                phoneId, EVENT_CONNECTED_TO_DEFAULT)) {
-                            sendMessageDelayed(obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
+                        // No cached config, so fetch it from the default app.
+                        if (bindToConfigPackage(
+                                mPlatformCarrierConfigPackage,
+                                phoneId,
+                                EVENT_CONNECTED_TO_DEFAULT)) {
+                            sendMessageDelayed(
+                                    obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
                                     BIND_TIMEOUT_MILLIS);
                         } else {
-                            // Send bcast if bind fails
+                            // Send broadcast if bind fails.
                             broadcastConfigChangedIntent(phoneId);
+                            // TODO: We *must* call unbindService even if bindService returns false.
+                            // (And possibly if SecurityException was thrown.)
                         }
                     }
                     break;
+                }
 
                 case EVENT_CONNECTED_TO_DEFAULT:
+                {
                     removeMessages(EVENT_BIND_DEFAULT_TIMEOUT);
-                    carrierId = getCarrierIdForPhoneId(phoneId);
-                    conn = (CarrierServiceConnection) msg.obj;
+                    final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
                     // If new service connection has been created, unbind.
                     if (mServiceConnection[phoneId] != conn || conn.service == null) {
                         mContext.unbindService(conn);
                         break;
                     }
+                    final CarrierIdentifier carrierId = getCarrierIdForPhoneId(phoneId);
+                    final String iccid = getIccIdForPhoneId(phoneId);
+                    // ResultReceiver callback will execute in this Handler's thread.
+                    final ResultReceiver resultReceiver =
+                            new ResultReceiver(this) {
+                                @Override
+                                public void onReceiveResult(int resultCode, Bundle resultData) {
+                                    mContext.unbindService(conn);
+                                    // If new service connection has been created, this is stale.
+                                    if (mServiceConnection[phoneId] != conn) {
+                                        loge("Received response for stale request.");
+                                        return;
+                                    }
+                                    removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT);
+                                    if (resultCode == RESULT_ERROR || resultData == null) {
+                                        // On error, abort config fetching.
+                                        loge("Failed to get carrier config");
+                                        broadcastConfigChangedIntent(phoneId);
+                                        return;
+                                    }
+                                    PersistableBundle config =
+                                            resultData.getParcelable(KEY_CONFIG_BUNDLE);
+                                    saveConfigToXml(
+                                            mPlatformCarrierConfigPackage, iccid, config);
+                                    mConfigFromDefaultApp[phoneId] = config;
+                                    sendMessage(
+                                            obtainMessage(
+                                                    EVENT_FETCH_DEFAULT_DONE, phoneId, -1));
+                                }
+                            };
+                    // Now fetch the config asynchronously from the ICarrierService.
                     try {
-                        ICarrierService carrierService = ICarrierService.Stub
-                                .asInterface(conn.service);
-                        config = carrierService.getCarrierConfig(carrierId);
-                        iccid = getIccIdForPhoneId(phoneId);
-                        saveConfigToXml(mPlatformCarrierConfigPackage, iccid, config);
-                        mConfigFromDefaultApp[phoneId] = config;
-                        sendMessage(obtainMessage(EVENT_LOADED_FROM_DEFAULT, phoneId, -1));
-                    } catch (Exception ex) {
-                        // The bound app could throw exceptions that binder will pass to us.
-                        loge("Failed to get carrier config: " + ex.toString());
-                    } finally {
-                        mContext.unbindService(mServiceConnection[phoneId]);
+                        ICarrierService carrierService =
+                                ICarrierService.Stub.asInterface(conn.service);
+                        carrierService.getCarrierConfig(carrierId, resultReceiver);
+                    } catch (RemoteException e) {
+                        loge("Failed to get carrier config: " + e.toString());
+                        mContext.unbindService(conn);
+                        break; // So we don't set a timeout.
                     }
+                    sendMessageDelayed(
+                            obtainMessage(EVENT_FETCH_DEFAULT_TIMEOUT, phoneId, -1),
+                            BIND_TIMEOUT_MILLIS);
                     break;
+                }
 
                 case EVENT_BIND_DEFAULT_TIMEOUT:
+                case EVENT_FETCH_DEFAULT_TIMEOUT:
+                {
+                    // If a ResponseReceiver callback is in the queue when this happens, we will
+                    // unbind twice and throw an exception.
                     mContext.unbindService(mServiceConnection[phoneId]);
+                    removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT);
                     broadcastConfigChangedIntent(phoneId);
                     break;
+                }
 
-                case EVENT_LOADED_FROM_DEFAULT:
+                case EVENT_FETCH_DEFAULT_DONE:
+                {
                     // If we attempted to bind to the app, but the service connection is null, then
                     // config was cleared while we were waiting and we should not continue.
                     if (!msg.getData().getBoolean("loaded_from_xml", false)
                             && mServiceConnection[phoneId] == null) {
                         break;
                     }
-                    carrierPackageName = getCarrierPackageForPhoneId(phoneId);
+                    final String carrierPackageName = getCarrierPackageForPhoneId(phoneId);
                     if (carrierPackageName != null) {
                         log("Found carrier config app: " + carrierPackageName);
-                        sendMessage(obtainMessage(EVENT_FETCH_CARRIER, phoneId));
+                        sendMessage(obtainMessage(EVENT_DO_FETCH_CARRIER, phoneId));
                     } else {
                         broadcastConfigChangedIntent(phoneId);
                     }
                     break;
+                }
 
-                case EVENT_FETCH_CARRIER:
-                    carrierPackageName = getCarrierPackageForPhoneId(phoneId);
-                    iccid = getIccIdForPhoneId(phoneId);
-                    config = restoreConfigFromXml(carrierPackageName, iccid);
+                case EVENT_DO_FETCH_CARRIER:
+                {
+                    final String carrierPackageName = getCarrierPackageForPhoneId(phoneId);
+                    final String iccid = getIccIdForPhoneId(phoneId);
+                    final PersistableBundle config =
+                            restoreConfigFromXml(carrierPackageName, iccid);
                     if (config != null) {
-                        log("Loaded config from XML. package=" + carrierPackageName + " phoneId="
-                                + phoneId);
+                        log(
+                                "Loaded config from XML. package="
+                                        + carrierPackageName
+                                        + " phoneId="
+                                        + phoneId);
                         mConfigFromCarrierApp[phoneId] = config;
-                        Message newMsg = obtainMessage(EVENT_LOADED_FROM_CARRIER, phoneId, -1);
+                        Message newMsg = obtainMessage(EVENT_FETCH_CARRIER_DONE, phoneId, -1);
                         newMsg.getData().putBoolean("loaded_from_xml", true);
                         sendMessage(newMsg);
                     } else {
+                        // No cached config, so fetch it from a carrier app.
                         if (carrierPackageName != null
-                            && bindToConfigPackage(carrierPackageName, phoneId,
-                                    EVENT_CONNECTED_TO_CARRIER)) {
-                            sendMessageDelayed(obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1),
+                                && bindToConfigPackage(
+                                        carrierPackageName,
+                                        phoneId,
+                                        EVENT_CONNECTED_TO_CARRIER)) {
+                            sendMessageDelayed(
+                                    obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1),
                                     BIND_TIMEOUT_MILLIS);
                         } else {
-                            // Send bcast if bind fails
+                            // Send broadcast if bind fails.
                             broadcastConfigChangedIntent(phoneId);
                         }
                     }
                     break;
+                }
 
                 case EVENT_CONNECTED_TO_CARRIER:
+                {
                     removeMessages(EVENT_BIND_CARRIER_TIMEOUT);
-                    carrierId = getCarrierIdForPhoneId(phoneId);
-                    conn = (CarrierServiceConnection) msg.obj;
+                    final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
                     // If new service connection has been created, unbind.
-                    if (mServiceConnection[phoneId] != conn ||
-                            conn.service == null) {
+                    if (mServiceConnection[phoneId] != conn || conn.service == null) {
                         mContext.unbindService(conn);
                         break;
                     }
+                    final CarrierIdentifier carrierId = getCarrierIdForPhoneId(phoneId);
+                    final String iccid = getIccIdForPhoneId(phoneId);
+                    // ResultReceiver callback will execute in this Handler's thread.
+                    final ResultReceiver resultReceiver =
+                            new ResultReceiver(this) {
+                                @Override
+                                public void onReceiveResult(int resultCode, Bundle resultData) {
+                                    mContext.unbindService(conn);
+                                    // If new service connection has been created, this is stale.
+                                    if (mServiceConnection[phoneId] != conn) {
+                                        loge("Received response for stale request.");
+                                        return;
+                                    }
+                                    removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
+                                    if (resultCode == RESULT_ERROR || resultData == null) {
+                                        // On error, abort config fetching.
+                                        loge("Failed to get carrier config");
+                                        broadcastConfigChangedIntent(phoneId);
+                                        return;
+                                    }
+                                    PersistableBundle config =
+                                            resultData.getParcelable(KEY_CONFIG_BUNDLE);
+                                    saveConfigToXml(
+                                            mPlatformCarrierConfigPackage, iccid, config);
+                                    mConfigFromCarrierApp[phoneId] = config;
+                                    sendMessage(
+                                            obtainMessage(
+                                                    EVENT_FETCH_CARRIER_DONE, phoneId, -1));
+                                }
+                            };
+                    // Now fetch the config asynchronously from the ICarrierService.
                     try {
-                        ICarrierService carrierService = ICarrierService.Stub
-                                .asInterface(conn.service);
-                        config = carrierService.getCarrierConfig(carrierId);
-                        carrierPackageName = getCarrierPackageForPhoneId(phoneId);
-                        iccid = getIccIdForPhoneId(phoneId);
-                        saveConfigToXml(carrierPackageName, iccid, config);
-                        mConfigFromCarrierApp[phoneId] = config;
-                        sendMessage(obtainMessage(EVENT_LOADED_FROM_CARRIER, phoneId, -1));
-                    } catch (Exception ex) {
-                        // The bound app could throw exceptions that binder will pass to us.
-                        loge("Failed to get carrier config: " + ex.toString());
-                    } finally {
-                        mContext.unbindService(mServiceConnection[phoneId]);
+                        ICarrierService carrierService =
+                                ICarrierService.Stub.asInterface(conn.service);
+                        carrierService.getCarrierConfig(carrierId, resultReceiver);
+                    } catch (RemoteException e) {
+                        loge("Failed to get carrier config: " + e.toString());
+                        mContext.unbindService(conn);
+                        break; // So we don't set a timeout.
                     }
+                    sendMessageDelayed(
+                            obtainMessage(EVENT_FETCH_CARRIER_TIMEOUT, phoneId, -1),
+                            BIND_TIMEOUT_MILLIS);
                     break;
+                }
 
                 case EVENT_BIND_CARRIER_TIMEOUT:
+                case EVENT_FETCH_CARRIER_TIMEOUT:
+                {
+                    // If a ResponseReceiver callback is in the queue when this happens, we will
+                    // unbind twice and throw an exception.
                     mContext.unbindService(mServiceConnection[phoneId]);
+                    removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
                     broadcastConfigChangedIntent(phoneId);
                     break;
+                }
 
-                case EVENT_LOADED_FROM_CARRIER:
+                case EVENT_FETCH_CARRIER_DONE:
+                {
                     // If we attempted to bind to the app, but the service connection is null, then
                     // config was cleared while we were waiting and we should not continue.
                     if (!msg.getData().getBoolean("loaded_from_xml", false)
@@ -335,21 +433,32 @@
                     }
                     broadcastConfigChangedIntent(phoneId);
                     break;
+                }
 
                 case EVENT_CHECK_SYSTEM_UPDATE:
+                {
                     SharedPreferences sharedPrefs =
                             PreferenceManager.getDefaultSharedPreferences(mContext);
                     final String lastFingerprint = sharedPrefs.getString(KEY_FINGERPRINT, null);
                     if (!Build.FINGERPRINT.equals(lastFingerprint)) {
-                        log("Build fingerprint changed. old: "
-                                + lastFingerprint + " new: " + Build.FINGERPRINT);
+                        log(
+                                "Build fingerprint changed. old: "
+                                        + lastFingerprint
+                                        + " new: "
+                                        + Build.FINGERPRINT);
                         clearCachedConfigForPackage(null);
-                        sharedPrefs.edit().putString(KEY_FINGERPRINT, Build.FINGERPRINT).apply();
+                        sharedPrefs
+                                .edit()
+                                .putString(KEY_FINGERPRINT, Build.FINGERPRINT)
+                                .apply();
                     }
                     break;
+                }
             }
         }
-    };
+    }
+
+    private final Handler mHandler;
 
     /**
      * Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
@@ -359,6 +468,7 @@
         mContext = context;
         mPlatformCarrierConfigPackage =
                 mContext.getString(R.string.platform_carrier_config_package);
+        mHandler = new ConfigHandler();
 
         IntentFilter bootFilter = new IntentFilter();
         bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
@@ -636,7 +746,8 @@
         }
     }
 
-    /** Read up to date config.
+    /**
+     * Read up to date config.
      *
      * This reads config bundles for the given phoneId. That means getting the latest bundle from
      * the default app and a privileged carrier app, if present. This will not bind to an app if we
@@ -649,7 +760,7 @@
                 getCarrierPackageForPhoneId(phoneId) == null) {
             mConfigFromCarrierApp[phoneId] = null;
         }
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_FETCH_DEFAULT, phoneId, -1));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_DO_FETCH_DEFAULT, phoneId, -1));
     }
 
     @Override public