• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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