Merge "Default Carrier app for traffic mitigation"
am: 4533b1ccdd
Change-Id: I999285660c3a53fb2da8f18ddde9042f611f4e96
This commit is contained in:
14
packages/CarrierDefaultApp/Android.mk
Normal file
14
packages/CarrierDefaultApp/Android.mk
Normal file
@@ -0,0 +1,14 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := CarrierDefaultApp
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
# This finds and builds the test apk as well, so a single make does both.
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
41
packages/CarrierDefaultApp/AndroidManifest.xml
Normal file
41
packages/CarrierDefaultApp/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.carrierdefaultapp"
|
||||
android:sharedUserId="android.uid.phone" >
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
|
||||
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<application android:label="@string/app_name" >
|
||||
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CARRIER_SIGNAL_REDIRECTED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:excludeFromRecents="true"/>
|
||||
</application>
|
||||
</manifest>
|
||||
26
packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
Normal file
26
packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (C) 2016 Google Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/glif_icon_size"
|
||||
android:height="@dimen/glif_icon_size"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorPrimary"
|
||||
android:pathData="M39.98,8c0,-2.21 -1.77,-4 -3.98,-4L20,4L8,16v24c0,2.21 1.79,4 4,4h24.02c2.21,0 3.98,-1.79 3.98,-4l-0.02,-32zM18,38h-4v-4h4v4zM34,38h-4v-4h4v4zM18,30h-4v-8h4v8zM26,38h-4v-8h4v8zM26,26h-4v-4h4v4zM34,30h-4v-8h4v8z" />
|
||||
</vector>
|
||||
3
packages/CarrierDefaultApp/res/values/dimens.xml
Normal file
3
packages/CarrierDefaultApp/res/values/dimens.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<dimen name="glif_icon_size">32dp</dimen>
|
||||
</resources>
|
||||
13
packages/CarrierDefaultApp/res/values/strings.xml
Normal file
13
packages/CarrierDefaultApp/res/values/strings.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">CarrierDefaultApp</string>
|
||||
<string name="portal_notification_id">Activate your service</string>
|
||||
<string name="no_data_notification_id">No data service</string>
|
||||
<string name="portal_notification_detail">Tap to activate your service</string>
|
||||
<string name="no_data_notification_detail">No Service, please contact your service provider</string>
|
||||
<string name="progress_dialogue_network_connection">Connecting to captive portal...</string>
|
||||
<string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string>
|
||||
<string name="alert_dialogue_network_timeout_title">Network unavailable</string>
|
||||
<string name="quit">Quit</string>
|
||||
<string name="wait">Wait</string>
|
||||
</resources>
|
||||
3
packages/CarrierDefaultApp/res/values/styles.xml
Normal file
3
packages/CarrierDefaultApp/res/values/styles.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/>
|
||||
</resources>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/CarrierDefaultApp/tests/Android.mk
Normal file
25
packages/CarrierDefaultApp/tests/Android.mk
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
# Include all makefiles in subdirectories
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
|
||||
|
||||
|
||||
|
||||
34
packages/CarrierDefaultApp/tests/unit/Android.mk
Normal file
34
packages/CarrierDefaultApp/tests/unit/Android.mk
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# We only want this apk build for tests.
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4
|
||||
|
||||
# Include all test java files.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := CarrierDefaultAppUnitTests
|
||||
|
||||
LOCAL_INSTRUMENTATION_FOR := CarrierDefaultApp
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
31
packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
Normal file
31
packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.carrierdefaultapp.tests.unit">
|
||||
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.carrierdefaultapp"
|
||||
android:label="CarrierDefaultApp Unit Test Cases">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Google Inc.
|
||||
*
|
||||
* 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.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.test.ActivityUnitTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CarrierDefaultActivityTestCase<T extends Activity> extends ActivityUnitTestCase<T> {
|
||||
|
||||
protected TestContext mContext;
|
||||
|
||||
private T mActivity;
|
||||
|
||||
CarrierDefaultActivityTestCase(Class<T> activityClass) {
|
||||
super(activityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = new TestContext(getInstrumentation().getTargetContext());
|
||||
setActivityContext(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
protected T startActivity() throws Throwable {
|
||||
runTestOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mActivity = startActivity(createActivityIntent(), null, null);
|
||||
}
|
||||
});
|
||||
return mActivity;
|
||||
}
|
||||
|
||||
protected void stopActivity() throws Exception {
|
||||
getInstrumentation().callActivityOnStop(mActivity);
|
||||
}
|
||||
|
||||
protected Intent createActivityIntent() {
|
||||
Intent intent = new Intent();
|
||||
return intent;
|
||||
}
|
||||
|
||||
protected <S> void injectSystemService(Class<S> cls, S service) {
|
||||
mContext.injectSystemService(cls, service);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Google Inc.
|
||||
*
|
||||
* 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.Intent;
|
||||
import android.os.PersistableBundle;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.Rlog;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import com.android.internal.telephony.PhoneConstants;
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class CarrierDefaultReceiverTest extends InstrumentationTestCase {
|
||||
@Mock
|
||||
private NotificationManager mNotificationMgr;
|
||||
@Mock
|
||||
private TelephonyManager mTelephonyMgr;
|
||||
@Mock
|
||||
private CarrierConfigManager mCarrierConfigMgr;
|
||||
@Captor
|
||||
private ArgumentCaptor<Integer> mInt;
|
||||
@Captor
|
||||
private ArgumentCaptor<Notification> mNotification;
|
||||
@Captor
|
||||
private ArgumentCaptor<String> mString;
|
||||
private TestContext mContext;
|
||||
private CarrierDefaultBroadcastReceiver mReceiver;
|
||||
private static String TAG;
|
||||
|
||||
private static final String PORTAL_NOTIFICATION_TAG = "CarrierDefault.Portal.Notification";
|
||||
private static final int PORTAL_NOTIFICATION_ID = 0;
|
||||
private static int subId = 0;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
TAG = this.getClass().getSimpleName();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = new TestContext(getInstrumentation().getTargetContext());
|
||||
mContext.injectSystemService(NotificationManager.class, mNotificationMgr);
|
||||
mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr);
|
||||
mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr);
|
||||
|
||||
mReceiver = new CarrierDefaultBroadcastReceiver();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnReceiveRedirection() {
|
||||
// carrier action idx list includes 4(portal notification) & 1(disable metered APNs)
|
||||
// upon redirection signal
|
||||
PersistableBundle b = new PersistableBundle();
|
||||
b.putStringArray(CarrierConfigManager
|
||||
.KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"});
|
||||
doReturn(b).when(mCarrierConfigMgr).getConfig();
|
||||
|
||||
Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
|
||||
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
|
||||
Rlog.d(TAG, "OnReceive redirection intent");
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
mContext.waitForMs(100);
|
||||
|
||||
Rlog.d(TAG, "verify carrier action: showPortalNotification");
|
||||
verify(mNotificationMgr, times(1)).notify(mString.capture(), mInt.capture(),
|
||||
mNotification.capture());
|
||||
assertEquals(PORTAL_NOTIFICATION_ID, (int) mInt.getValue());
|
||||
assertEquals(PORTAL_NOTIFICATION_TAG, mString.getValue());
|
||||
PendingIntent pendingIntent = mNotification.getValue().contentIntent;
|
||||
assertNotNull(pendingIntent);
|
||||
|
||||
Rlog.d(TAG, "verify carrier action: disable all metered apns");
|
||||
verify(mTelephonyMgr).carrierActionSetMeteredApnsEnabled(eq(subId), eq(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.android.carrierdefaultapp;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class LaunchCaptivePortalActivityTest extends
|
||||
CarrierDefaultActivityTestCase<CaptivePortalLaunchActivity> {
|
||||
|
||||
@Mock
|
||||
private ConnectivityManager mCm;
|
||||
@Mock
|
||||
private NetworkInfo mNetworkInfo;
|
||||
@Mock
|
||||
private Network mNetwork;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Integer> mInt;
|
||||
@Captor
|
||||
private ArgumentCaptor<NetworkRequest> mNetworkReq;
|
||||
|
||||
private NetworkCapabilities mNetworkCapabilities;
|
||||
|
||||
public LaunchCaptivePortalActivityTest() {
|
||||
super(CaptivePortalLaunchActivity.class);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
injectSystemService(ConnectivityManager.class, mCm);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent createActivityIntent() {
|
||||
Intent intent = new Intent(getInstrumentation().getTargetContext(),
|
||||
CaptivePortalLaunchActivity.class);
|
||||
intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, "url");
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutInternetConnection() throws Throwable {
|
||||
startActivity();
|
||||
TestContext.waitForMs(100);
|
||||
verify(mCm, atLeast(1)).requestNetwork(mNetworkReq.capture(), any(), mInt.capture());
|
||||
// verify network request
|
||||
assert(mNetworkReq.getValue().networkCapabilities.hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_INTERNET));
|
||||
assert(mNetworkReq.getValue().networkCapabilities.hasTransport(
|
||||
NetworkCapabilities.TRANSPORT_CELLULAR));
|
||||
assertFalse(mNetworkReq.getValue().networkCapabilities.hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
|
||||
assertEquals(CaptivePortalLaunchActivity.NETWORK_REQUEST_TIMEOUT_IN_MS,
|
||||
(int) mInt.getValue());
|
||||
// verify captive portal app is not launched due to unavailable network
|
||||
assertNull(getStartedActivityIntent());
|
||||
stopActivity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithInternetConnection() throws Throwable {
|
||||
// Mock internet connection
|
||||
mNetworkCapabilities = new NetworkCapabilities()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
|
||||
doReturn(new Network[]{mNetwork}).when(mCm).getAllNetworks();
|
||||
doReturn(mNetworkCapabilities).when(mCm).getNetworkCapabilities(eq(mNetwork));
|
||||
doReturn(mNetworkInfo).when(mCm).getNetworkInfo(eq(mNetwork));
|
||||
doReturn(true).when(mNetworkInfo).isConnected();
|
||||
|
||||
startActivity();
|
||||
TestContext.waitForMs(100);
|
||||
// verify there is no network request with internet connection
|
||||
verify(mCm, times(0)).requestNetwork(any(), any(), anyInt());
|
||||
// verify captive portal app is launched
|
||||
assertNotNull(getStartedActivityIntent());
|
||||
assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN,
|
||||
getStartedActivityIntent().getAction());
|
||||
stopActivity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Google Inc.
|
||||
*
|
||||
* 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.ContextWrapper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class TestContext extends ContextWrapper {
|
||||
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
|
||||
private HashMap<String, Object> mInjectedSystemServices = new HashMap<>();
|
||||
|
||||
public TestContext(Context base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
public <S> void injectSystemService(Class<S> cls, S service) {
|
||||
final String name = getSystemServiceName(cls);
|
||||
mInjectedSystemServices.put(name, service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (mInjectedSystemServices.containsKey(name)) {
|
||||
Log.d(TAG, "return mocked system service for " + name);
|
||||
return mInjectedSystemServices.get(name);
|
||||
}
|
||||
Log.d(TAG, "return real system service for " + name);
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
|
||||
public static void waitForMs(long ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -842,6 +842,58 @@ public class CarrierConfigManager {
|
||||
public static final String KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY =
|
||||
"signal_pco_receiver_string_array";
|
||||
|
||||
/**
|
||||
* Defines carrier-specific actions which act upon
|
||||
* android.intent.action.CARRIER_SIGNAL_REDIRECTED, used for customization of the
|
||||
* default carrier app
|
||||
* Format: "CARRIER_ACTION_IDX, ..."
|
||||
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
|
||||
* {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
|
||||
* Example:
|
||||
* {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
|
||||
* disable_metered_apns}
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY =
|
||||
"carrier_default_actions_on_redirection_string_array";
|
||||
|
||||
/**
|
||||
* Defines carrier-specific actions which act upon
|
||||
* android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
|
||||
* and configured signal args:
|
||||
* {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType},
|
||||
* {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode}
|
||||
* used for customization of the default carrier app
|
||||
* Format:
|
||||
* {
|
||||
* "APN_1, ERROR_CODE_1 : CARRIER_ACTION_IDX_1, CARRIER_ACTION_IDX_2...",
|
||||
* "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 "
|
||||
* }
|
||||
* Where {@code APN_1} is a string defined in
|
||||
* {@link com.android.internal.telephony.PhoneConstants PhoneConstants}
|
||||
* Example: "default"
|
||||
*
|
||||
* {@code ERROR_CODE_1} is an integer defined in
|
||||
* {@link com.android.internal.telephony.dataconnection.DcFailCause DcFailure}
|
||||
* Example:
|
||||
* {@link com.android.internal.telephony.dataconnection.DcFailCause#MISSING_UNKNOWN_APN}
|
||||
*
|
||||
* {@code CARRIER_ACTION_IDX_1} is an integer defined in
|
||||
* {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
|
||||
* Example:
|
||||
* {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS}
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY =
|
||||
"carrier_default_actions_on_dcfailure_string_array";
|
||||
|
||||
/**
|
||||
* Defines a list of acceptable redirection url for default carrier app
|
||||
* @hides
|
||||
*/
|
||||
public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY =
|
||||
"carrier_default_redirection_url_string_array";
|
||||
|
||||
/**
|
||||
* Determines whether the carrier supports making non-emergency phone calls while the phone is
|
||||
* in emergency callback mode. Default value is {@code true}, meaning that non-emergency calls
|
||||
@@ -1229,6 +1281,15 @@ public class CarrierConfigManager {
|
||||
sDefaults.putStringArray(KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY, null);
|
||||
sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
|
||||
|
||||
// Default carrier app configurations
|
||||
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY,
|
||||
new String[]{
|
||||
"4, 1"
|
||||
//4: CARRIER_ACTION_DISABLE_METERED_APNS
|
||||
//1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
|
||||
});
|
||||
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
|
||||
|
||||
// Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP},
|
||||
// {LTE, LTE_CA}
|
||||
// Order is important - lowest precidence first
|
||||
|
||||
Reference in New Issue
Block a user