/*
 * Copyright (C) 2016 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.carrierdefaultapp;

import android.content.Context;
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

Default carrier app allows carrier customization. OEMs could configure a list of carrier actions defined in CarrierActionUtils to act upon certain signal or even different args of the same signal. This allows different interpretations of the signal between carriers and could easily alter the app's behavior in a configurable way. This helper class loads and parses the carrier configs and return a list of predefined carrier actions for the given input signal.
/** * Default carrier app allows carrier customization. OEMs could configure a list * of carrier actions defined in {@link com.android.carrierdefaultapp.CarrierActionUtils * CarrierActionUtils} to act upon certain signal or even different args of the same signal. * This allows different interpretations of the signal between carriers and could easily alter the * app's behavior in a configurable way. This helper class loads and parses the carrier configs * and return a list of predefined carrier actions for the given input signal. */
public class CustomConfigLoader { // delimiters for parsing carrier configs of the form "arg1, arg2 : action1, action2" private static final String INTRA_GROUP_DELIMITER = "\\s*,\\s*"; private static final String INTER_GROUP_DELIMITER = "\\s*:\\s*"; private static final String TAG = CustomConfigLoader.class.getSimpleName(); private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
loads and parses the carrier config, return a list of carrier action for the given signal
Params:
  • context –
  • intent – passing signal for config match
Returns:a list of carrier action for the given signal based on the carrier config. Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED This intent allows fined-grained matching based on both intent type & extra values: apnType and errorCode. apnType read from passing intent is "default" and errorCode is 0x26 for example and returned carrier config from carrier_default_actions_on_redirection_string_array is { "default, 0x26:1,4", // 0x26(NETWORK_FAILURE) "default, 0x70:2,3" // 0x70(APN_TYPE_CONFLICT) } [1, 4] // 1(CARRIER_ACTION_DISABLE_METERED_APNS), 4(CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION) returns as the action index list based on the matching rule.
/** * loads and parses the carrier config, return a list of carrier action for the given signal * @param context * @param intent passing signal for config match * @return a list of carrier action for the given signal based on the carrier config. * * Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED * This intent allows fined-grained matching based on both intent type & extra values: * apnType and errorCode. * apnType read from passing intent is "default" and errorCode is 0x26 for example and * returned carrier config from carrier_default_actions_on_redirection_string_array is * { * "default, 0x26:1,4", // 0x26(NETWORK_FAILURE) * "default, 0x70:2,3" // 0x70(APN_TYPE_CONFLICT) * } * [1, 4] // 1(CARRIER_ACTION_DISABLE_METERED_APNS), 4(CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION) * returns as the action index list based on the matching rule. */
public static List<Integer> loadCarrierActionList(Context context, Intent intent) { CarrierConfigManager carrierConfigManager = (CarrierConfigManager) context.getSystemService( Context.CARRIER_CONFIG_SERVICE); // return an empty list if no match found List<Integer> actionList = new ArrayList<>(); if (carrierConfigManager == null) { Rlog.e(TAG, "load carrier config failure with carrier config manager uninitialized"); return actionList; } PersistableBundle b = carrierConfigManager.getConfig(); if (b != null) { String[] configs = null; // used for intents which allow fine-grained interpretation based on intent extras String arg1 = null; String arg2 = null; switch (intent.getAction()) { case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY); break; case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY); arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY); arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY); break; case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET); break; case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: configs = b.getStringArray(CarrierConfigManager .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE); arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false)); break; default: Rlog.e(TAG, "load carrier config failure with un-configured key: " + intent.getAction()); break; } if (!ArrayUtils.isEmpty(configs)) { for (String config : configs) { // parse each config until find the matching one matchConfig(config, arg1, arg2, actionList); if (!actionList.isEmpty()) { // return the first match if (VDBG) Rlog.d(TAG, "found match action list: " + actionList.toString()); return actionList; } } } Rlog.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1 + "arg2: " + arg2); } return actionList; }
Match based on the config's format and input args passing arg1, arg2 should match the format of the config case 1: config {actionIdx1, actionIdx2...} arg1 and arg2 must be null case 2: config {arg1, arg2 : actionIdx1, actionIdx2...} requires full match of non-null args case 3: config {arg1 : actionIdx1, actionIdx2...} only need to match arg1
Params:
  • config – action list config obtained from CarrierConfigManager
  • arg1 – first intent argument, set if required for config match
  • arg2 – second intent argument, set if required for config match
  • actionList – append each parsed action to the passing list
/** * Match based on the config's format and input args * passing arg1, arg2 should match the format of the config * case 1: config {actionIdx1, actionIdx2...} arg1 and arg2 must be null * case 2: config {arg1, arg2 : actionIdx1, actionIdx2...} requires full match of non-null args * case 3: config {arg1 : actionIdx1, actionIdx2...} only need to match arg1 * * @param config action list config obtained from CarrierConfigManager * @param arg1 first intent argument, set if required for config match * @param arg2 second intent argument, set if required for config match * @param actionList append each parsed action to the passing list */
private static void matchConfig(String config, String arg1, String arg2, List<Integer> actionList) { String[] splitStr = config.trim().split(INTER_GROUP_DELIMITER, 2); String actionStr = null; if (splitStr.length == 1 && arg1 == null && arg2 == null) { // case 1 actionStr = splitStr[0]; } else if (splitStr.length == 2 && arg1 != null && arg2 != null) { // case 2 String[] args = splitStr[0].split(INTRA_GROUP_DELIMITER); if (args.length == 2 && TextUtils.equals(arg1, args[0]) && TextUtils.equals(arg2, args[1])) { actionStr = splitStr[1]; } } else if ((splitStr.length == 2) && (arg1 != null) && (arg2 == null)) { // case 3 String[] args = splitStr[0].split(INTRA_GROUP_DELIMITER); if (args.length == 1 && TextUtils.equals(arg1, args[0])) { actionStr = splitStr[1]; } } // convert from string -> action idx list if found a matching entry String[] actions = null; if (!TextUtils.isEmpty(actionStr)) { actions = actionStr.split(INTRA_GROUP_DELIMITER); } if (!ArrayUtils.isEmpty(actions)) { for (String idx : actions) { try { actionList.add(Integer.parseInt(idx)); } catch (NumberFormatException e) { Rlog.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): " + e); } } } } }