summaryrefslogtreecommitdiff
path: root/packages/CarrierDefaultApp/src
diff options
context:
space:
mode:
authorfionaxu <fionaxu@google.com>2016-12-13 17:15:11 -0800
committerfionaxu <fionaxu@google.com>2017-01-12 15:50:18 -0800
commita21a87b7a4153839cebb3152d0003ee86d7a5252 (patch)
treef041be8ac255572f6aa355d21454a9a4c46052b0 /packages/CarrierDefaultApp/src
parentf84e5e8f767c872ec6d43f893f6860edac9b464e (diff)
Default Carrier app for traffic mitigation
- have the basic function working, support traffic mitigation and captive portal login - support carrier customization, OEM could configure a list of carrier actions to act upon certain signals - unit test Test: Manual test with live sim card & runtest --path frameworks/base/packages/CarrierDefaultApp Bug: 30958215 Change-Id: Ie99be3b95e8a1dd60fc51bef703836478fbde09d
Diffstat (limited to 'packages/CarrierDefaultApp/src')
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java233
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java175
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java37
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java166
4 files changed, 611 insertions, 0 deletions
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
new file mode 100644
index 000000000000..b7fde12f1d22
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
@@ -0,0 +1,233 @@
+/*
+ * 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.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.CaptivePortal;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.net.ICaptivePortal;
+import android.view.ContextThemeWrapper;
+import android.view.WindowManager;
+import com.android.carrierdefaultapp.R;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.ArrayUtils;
+
+import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
+
+/**
+ * Activity that launches in response to the captive portal notification
+ * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
+ * This activity requests network connection if there is no available one, launches the
+ * {@link com.android.captiveportallogin portalApp} and keeps track of the portal activation result.
+ */
+public class CaptivePortalLaunchActivity extends Activity {
+ private static final String TAG = CaptivePortalLaunchActivity.class.getSimpleName();
+ private static final boolean DBG = true;
+ public static final int NETWORK_REQUEST_TIMEOUT_IN_MS = 5 * 1000;
+
+ private ConnectivityManager mCm = null;
+ private ConnectivityManager.NetworkCallback mCb = null;
+ /* Progress dialogue when request network connection for captive portal */
+ private AlertDialog mProgressDialog = null;
+ /* Alert dialogue when network request is timeout */
+ private AlertDialog mAlertDialog = null;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCm = ConnectivityManager.from(this);
+ // Check network connection before loading portal
+ Network network = getNetworkForCaptivePortal();
+ NetworkInfo nwInfo = mCm.getNetworkInfo(network);
+ if (nwInfo == null || !nwInfo.isConnected()) {
+ if (DBG) logd("Network unavailable, request restricted connection");
+ requestNetwork(getIntent());
+ } else {
+ launchCaptivePortal(getIntent(), network);
+ }
+ }
+
+ // show progress dialog during network connecting
+ private void showConnectingProgressDialog() {
+ mProgressDialog = new ProgressDialog(getApplicationContext());
+ mProgressDialog.setMessage(getString(R.string.progress_dialogue_network_connection));
+ mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mProgressDialog.show();
+ }
+
+ // if network request is timeout, show alert dialog with two option: cancel & wait
+ private void showConnectionTimeoutAlertDialog() {
+ mAlertDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialog))
+ .setMessage(getString(R.string.alert_dialogue_network_timeout))
+ .setTitle(getString(R.string.alert_dialogue_network_timeout_title))
+ .setNegativeButton(getString(R.string.quit),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // cancel
+ dismissDialog(mAlertDialog);
+ finish();
+ }
+ })
+ .setPositiveButton(getString(R.string.wait),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // wait, request network again
+ dismissDialog(mAlertDialog);
+ requestNetwork(getIntent());
+ }
+ })
+ .create();
+ mAlertDialog.show();
+ }
+
+ private void requestNetwork(final Intent intent) {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+
+ mCb = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ if (DBG) logd("Network available: " + network);
+ dismissDialog(mProgressDialog);
+ mCm.bindProcessToNetwork(network);
+ launchCaptivePortal(intent, network);
+ }
+
+ @Override
+ public void onUnavailable() {
+ if (DBG) logd("Network unavailable");
+ dismissDialog(mProgressDialog);
+ showConnectionTimeoutAlertDialog();
+ }
+ };
+ showConnectingProgressDialog();
+ mCm.requestNetwork(request, mCb, NETWORK_REQUEST_TIMEOUT_IN_MS);
+ }
+
+ private void releaseNetworkRequest() {
+ logd("release Network Request");
+ if (mCb != null) {
+ mCm.unregisterNetworkCallback(mCb);
+ mCb = null;
+ }
+ }
+
+ private void dismissDialog(AlertDialog dialog) {
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ }
+
+ private Network getNetworkForCaptivePortal() {
+ Network[] info = mCm.getAllNetworks();
+ if (!ArrayUtils.isEmpty(info)) {
+ for (Network nw : info) {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw);
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return nw;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void launchCaptivePortal(final Intent intent, Network network) {
+ String redirectUrl = intent.getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ if (TextUtils.isEmpty(redirectUrl) || !matchUrl(redirectUrl, subId)) {
+ loge("Launch portal fails due to incorrect redirection URL: " +
+ Rlog.pii(TAG, redirectUrl));
+ return;
+ }
+ final Intent portalIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
+ portalIntent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
+ portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
+ new CaptivePortal(new ICaptivePortal.Stub() {
+ @Override
+ public void appResponse(int response) {
+ logd("portal response code: " + response);
+ releaseNetworkRequest();
+ if (response == APP_RETURN_DISMISSED) {
+ // Upon success http response code, trigger re-evaluation
+ CarrierActionUtils.applyCarrierAction(
+ CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, intent,
+ getApplicationContext());
+ CarrierActionUtils.applyCarrierAction(
+ CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, intent,
+ getApplicationContext());
+ CarrierActionUtils.applyCarrierAction(
+ CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS,
+ intent, getApplicationContext());
+ }
+ }
+ }));
+ portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, redirectUrl);
+ portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ if (DBG) logd("launching portal");
+ startActivity(portalIntent);
+ finish();
+ }
+
+ // match configured redirection url
+ private boolean matchUrl(String url, int subId) {
+ CarrierConfigManager configManager = getApplicationContext()
+ .getSystemService(CarrierConfigManager.class);
+ String[] redirectURLs = configManager.getConfigForSubId(subId).getStringArray(
+ CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
+ if (ArrayUtils.isEmpty(redirectURLs)) {
+ if (DBG) logd("match is unnecessary without any configured redirection url");
+ return true;
+ }
+ for (String redirectURL : redirectURLs) {
+ if (url.startsWith(redirectURL)) {
+ return true;
+ }
+ }
+ if (DBG) loge("no match found for configured redirection url");
+ return false;
+ }
+
+ private static void logd(String s) {
+ Rlog.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Rlog.d(TAG, s);
+ }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
new file mode 100644
index 000000000000..db4890fc0187
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -0,0 +1,175 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.carrierdefaultapp.R;
+/**
+ * This util class provides common logic for carrier actions
+ */
+public class CarrierActionUtils {
+ private static final String TAG = CarrierActionUtils.class.getSimpleName();
+
+ private static final String PORTAL_NOTIFICATION_TAG = "CarrierDefault.Portal.Notification";
+ private static final String NO_DATA_NOTIFICATION_TAG = "CarrierDefault.NoData.Notification";
+ private static final int PORTAL_NOTIFICATION_ID = 0;
+ private static final int NO_DATA_NOTIFICATION_ID = 1;
+ private static boolean ENABLE = true;
+
+ // A list of supported carrier action idx
+ public static final int CARRIER_ACTION_ENABLE_METERED_APNS = 0;
+ public static final int CARRIER_ACTION_DISABLE_METERED_APNS = 1;
+ public static final int CARRIER_ACTION_DISABLE_RADIO = 2;
+ public static final int CARRIER_ACTION_ENABLE_RADIO = 3;
+ public static final int CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION = 4;
+ public static final int CARRIER_ACTION_SHOW_NO_DATA_SERVICE_NOTIFICATION = 5;
+ public static final int CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS = 6;
+
+ public static void applyCarrierAction(int actionIdx, Intent intent, Context context) {
+ switch (actionIdx) {
+ case CARRIER_ACTION_ENABLE_METERED_APNS:
+ onEnableAllMeteredApns(intent, context);
+ break;
+ case CARRIER_ACTION_DISABLE_METERED_APNS:
+ onDisableAllMeteredApns(intent, context);
+ break;
+ case CARRIER_ACTION_DISABLE_RADIO:
+ onDisableRadio(intent, context);
+ break;
+ case CARRIER_ACTION_ENABLE_RADIO:
+ onEnableRadio(intent, context);
+ break;
+ case CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION:
+ onShowCaptivePortalNotification(intent, context);
+ break;
+ case CARRIER_ACTION_SHOW_NO_DATA_SERVICE_NOTIFICATION:
+ onShowNoDataServiceNotification(context);
+ break;
+ case CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS:
+ onCancelAllNotifications(context);
+ break;
+ default:
+ loge("unsupported carrier action index: " + actionIdx);
+ }
+ }
+
+ private static void onDisableAllMeteredApns(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onDisableAllMeteredApns subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, !ENABLE);
+ }
+
+ private static void onEnableAllMeteredApns(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onEnableAllMeteredApns subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, ENABLE);
+ }
+
+ private static void onDisableRadio(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onDisableRadio subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionSetRadioEnabled(subId, !ENABLE);
+ }
+
+ private static void onEnableRadio(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onEnableRadio subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionSetRadioEnabled(subId, ENABLE);
+ }
+
+ private static void onShowCaptivePortalNotification(Intent intent, Context context) {
+ logd("onShowCaptivePortalNotification");
+ final NotificationManager notificationMgr = context.getSystemService(
+ NotificationManager.class);
+ Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class);
+ portalIntent.putExtras(intent);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ Notification notification = getNotification(context, R.string.portal_notification_id,
+ R.string.portal_notification_detail, pendingIntent);
+ try {
+ notificationMgr.notify(PORTAL_NOTIFICATION_TAG, PORTAL_NOTIFICATION_ID, notification);
+ } catch (NullPointerException npe) {
+ loge("setNotificationVisible: " + npe);
+ }
+ }
+
+ private static void onShowNoDataServiceNotification(Context context) {
+ logd("onShowNoDataServiceNotification");
+ final NotificationManager notificationMgr = context.getSystemService(
+ NotificationManager.class);
+ Notification notification = getNotification(context, R.string.no_data_notification_id,
+ R.string.no_data_notification_detail, null);
+ try {
+ notificationMgr.notify(NO_DATA_NOTIFICATION_TAG, NO_DATA_NOTIFICATION_ID, notification);
+ } catch (NullPointerException npe) {
+ loge("setNotificationVisible: " + npe);
+ }
+ }
+
+ private static void onCancelAllNotifications(Context context) {
+ logd("onCancelAllNotifications");
+ final NotificationManager notificationMgr = context.getSystemService(
+ NotificationManager.class);
+ notificationMgr.cancelAll();
+ }
+
+ private static Notification getNotification(Context context, int titleId, int textId,
+ PendingIntent pendingIntent) {
+ Resources resources = context.getResources();
+ Notification.Builder builder = new Notification.Builder(context)
+ .setContentTitle(resources.getString(titleId))
+ .setContentText(resources.getString(textId))
+ .setSmallIcon(R.drawable.ic_sim_card)
+ .setOngoing(true)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setWhen(System.currentTimeMillis())
+ .setShowWhen(false);
+
+ if (pendingIntent != null) {
+ builder.setContentIntent(pendingIntent);
+ }
+ return builder.build();
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
new file mode 100644
index 000000000000..bc0fa026fd77
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.List;
+
+public class CarrierDefaultBroadcastReceiver extends BroadcastReceiver{
+ private static final String TAG = CarrierDefaultBroadcastReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive intent: " + intent.getAction());
+ List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent);
+ for (int actionIdx : actionList) {
+ Log.d(TAG, "apply carrier action idx: " + actionIdx);
+ CarrierActionUtils.applyCarrierAction(actionIdx, intent, context);
+ }
+ }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
new file mode 100644
index 000000000000..e1125d9d503a
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.util.Pair;
+
+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 {@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
+ * @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;
+ 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
+ *
+ * @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);
+ }
+ }
+ }
+ }
+}