1 /* 2 * Copyright (C) 2016 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 package com.android.carrierdefaultapp; 17 18 import android.content.Context; 19 import android.content.Intent; 20 import android.os.PersistableBundle; 21 import android.telephony.CarrierConfigManager; 22 import android.telephony.TelephonyManager; 23 import android.telephony.data.ApnSetting; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import com.android.internal.util.ArrayUtils; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Default carrier app allows carrier customization. OEMs could configure a list 34 * of carrier actions defined in {@link com.android.carrierdefaultapp.CarrierActionUtils 35 * CarrierActionUtils} to act upon certain signal or even different args of the same signal. 36 * This allows different interpretations of the signal between carriers and could easily alter the 37 * app's behavior in a configurable way. This helper class loads and parses the carrier configs 38 * and return a list of predefined carrier actions for the given input signal. 39 */ 40 public class CustomConfigLoader { 41 // delimiters for parsing carrier configs of the form "arg1, arg2 : action1, action2" 42 private static final String INTRA_GROUP_DELIMITER = "\\s*,\\s*"; 43 private static final String INTER_GROUP_DELIMITER = "\\s*:\\s*"; 44 45 private static final String TAG = CustomConfigLoader.class.getSimpleName(); 46 private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 47 48 /** 49 * loads and parses the carrier config, return a list of carrier action for the given signal 50 * @param context 51 * @param intent passing signal for config match 52 * @return a list of carrier action for the given signal based on the carrier config. 53 * 54 * Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED 55 * This intent allows fined-grained matching based on both intent type & extra values: 56 * apnType and errorCode. 57 * apnType read from passing intent is "default" and errorCode is 0x26 for example and 58 * returned carrier config from carrier_default_actions_on_redirection_string_array is 59 * { 60 * "default, 0x26:1,4", // 0x26(NETWORK_FAILURE) 61 * "default, 0x70:2,3" // 0x70(APN_TYPE_CONFLICT) 62 * } 63 * [1, 4] // 1(CARRIER_ACTION_DISABLE_METERED_APNS), 4(CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION) 64 * returns as the action index list based on the matching rule. 65 */ loadCarrierActionList(Context context, Intent intent)66 public static List<Integer> loadCarrierActionList(Context context, Intent intent) { 67 CarrierConfigManager carrierConfigManager = (CarrierConfigManager) context.getSystemService( 68 Context.CARRIER_CONFIG_SERVICE); 69 // return an empty list if no match found 70 List<Integer> actionList = new ArrayList<>(); 71 if (carrierConfigManager == null) { 72 Log.e(TAG, "load carrier config failure with carrier config manager uninitialized"); 73 return actionList; 74 } 75 PersistableBundle b = carrierConfigManager.getConfig(); 76 if (b != null) { 77 String[] configs = null; 78 // used for intents which allow fine-grained interpretation based on intent extras 79 String arg1 = null; 80 String arg2 = null; 81 switch (intent.getAction()) { 82 case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED: 83 configs = b.getStringArray(CarrierConfigManager 84 .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY); 85 break; 86 case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED: 87 configs = b.getStringArray(CarrierConfigManager 88 .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY); 89 arg1 = String.valueOf(intent.getIntExtra(TelephonyManager.EXTRA_APN_TYPE, -1)); 90 arg2 = intent.getStringExtra(TelephonyManager.EXTRA_DATA_FAIL_CAUSE); 91 break; 92 case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET: 93 configs = b.getStringArray(CarrierConfigManager 94 .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET); 95 break; 96 case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE: 97 configs = b.getStringArray(CarrierConfigManager 98 .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE); 99 arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager 100 .EXTRA_DEFAULT_NETWORK_AVAILABLE, false)); 101 break; 102 default: 103 Log.e(TAG, "load carrier config failure with un-configured key: " 104 + intent.getAction()); 105 break; 106 } 107 if (!ArrayUtils.isEmpty(configs)) { 108 for (String config : configs) { 109 // parse each config until find the matching one 110 matchConfig(config, arg1, arg2, actionList); 111 if (!actionList.isEmpty()) { 112 // return the first match 113 if (VDBG) Log.d(TAG, "found match action list: " + actionList.toString()); 114 return actionList; 115 } 116 } 117 } 118 Log.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1 119 + "arg2: " + arg2); 120 } 121 return actionList; 122 } 123 124 /** 125 * Match based on the config's format and input args 126 * passing arg1, arg2 should match the format of the config 127 * case 1: config {actionIdx1, actionIdx2...} arg1 and arg2 must be null 128 * case 2: config {arg1, arg2 : actionIdx1, actionIdx2...} requires full match of non-null args 129 * case 3: config {arg1 : actionIdx1, actionIdx2...} only need to match arg1 130 * 131 * @param config action list config obtained from CarrierConfigManager 132 * @param arg1 first intent argument, set if required for config match 133 * @param arg2 second intent argument, set if required for config match 134 * @param actionList append each parsed action to the passing list 135 */ matchConfig(String config, String arg1, String arg2, List<Integer> actionList)136 private static void matchConfig(String config, String arg1, String arg2, 137 List<Integer> actionList) { 138 String[] splitStr = config.trim().split(INTER_GROUP_DELIMITER, 2); 139 String actionStr = null; 140 141 if (splitStr.length == 1 && arg1 == null && arg2 == null) { 142 // case 1 143 actionStr = splitStr[0]; 144 } else if (splitStr.length == 2 && arg1 != null && arg2 != null) { 145 // case 2. The only thing that uses this is CARRIER_SIGNAL_REQUEST_NETWORK_FAILED, 146 // and the carrier config for that can provide either an int or string for the apn type, 147 // depending on when it was introduced. Therefore, return a positive match if either 148 // the int version or the string version of the apn type in the broadcast matches. 149 String apnInIntFormat = arg1; 150 String apnInStringFormat = null; 151 try { 152 int apnInt = Integer.parseInt(apnInIntFormat); 153 apnInStringFormat = ApnSetting.getApnTypeString(apnInt); 154 } catch (NumberFormatException e) { 155 Log.e(TAG, "Got invalid apn type from broadcast: " + apnInIntFormat); 156 } 157 158 String[] args = splitStr[0].split(INTRA_GROUP_DELIMITER); 159 boolean doesArg1Match = TextUtils.equals(apnInIntFormat, args[0]) 160 || (apnInStringFormat != null && TextUtils.equals(apnInStringFormat, args[0])); 161 if (args.length == 2 && doesArg1Match 162 && TextUtils.equals(arg2, args[1])) { 163 actionStr = splitStr[1]; 164 } 165 } else if ((splitStr.length == 2) && (arg1 != null) && (arg2 == null)) { 166 // case 3 167 String[] args = splitStr[0].split(INTRA_GROUP_DELIMITER); 168 if (args.length == 1 && TextUtils.equals(arg1, args[0])) { 169 actionStr = splitStr[1]; 170 } 171 } 172 // convert from string -> action idx list if found a matching entry 173 String[] actions = null; 174 if (!TextUtils.isEmpty(actionStr)) { 175 actions = actionStr.split(INTRA_GROUP_DELIMITER); 176 } 177 if (!ArrayUtils.isEmpty(actions)) { 178 for (String idx : actions) { 179 try { 180 actionList.add(Integer.parseInt(idx)); 181 } catch (NumberFormatException e) { 182 Log.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): " 183 + e); 184 } 185 } 186 } 187 } 188 } 189