Merge "integrate portal webview to the default app"
This commit is contained in:
@@ -25,7 +25,6 @@
|
|||||||
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
|
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
|
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
||||||
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
|
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
|
||||||
|
|
||||||
<application android:label="@string/app_name" >
|
<application android:label="@string/app_name" >
|
||||||
@@ -34,10 +33,16 @@
|
|||||||
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
|
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
|
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
|
||||||
android:excludeFromRecents="true"/>
|
|
||||||
<service android:name="com.android.carrierdefaultapp.ProvisionObserver"
|
<service android:name="com.android.carrierdefaultapp.ProvisionObserver"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"/>
|
android:permission="android.permission.BIND_JOB_SERVICE"/>
|
||||||
|
<activity
|
||||||
|
android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
|
||||||
|
android:label="@string/action_bar_label"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize" >
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 682 B |
@@ -22,4 +22,4 @@
|
|||||||
<path
|
<path
|
||||||
android:fillColor="#757575"
|
android:fillColor="#757575"
|
||||||
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/>
|
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
|
||||||
|
tools:ignore="MergeRootFrame">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/url_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal" />
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentBottom="false"
|
||||||
|
android:layout_alignParentRight="false" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<dimen name="glif_icon_size">32dp</dimen>
|
<dimen name="glif_icon_size">32dp</dimen>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
<string name="no_data_notification_id">Your mobile data has been deactivated</string>
|
<string name="no_data_notification_id">Your mobile data has been deactivated</string>
|
||||||
<string name="portal_notification_detail">Tap to visit the %s website</string>
|
<string name="portal_notification_detail">Tap to visit the %s website</string>
|
||||||
<string name="no_data_notification_detail">Please contact your service provider %s</string>
|
<string name="no_data_notification_detail">Please contact your service provider %s</string>
|
||||||
<string name="progress_dialogue_network_connection">Connecting to captive portal...</string>
|
<string name="action_bar_label">Sign in to mobile network</string>
|
||||||
<string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string>
|
<string name="ssl_error_warning">The network you’re trying to join has security issues.</string>
|
||||||
<string name="alert_dialogue_network_timeout_title">Network unavailable</string>
|
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
|
||||||
<string name="quit">Quit</string>
|
<string name="ssl_error_continue">Continue anyway via browser</string>
|
||||||
<string name="wait">Wait</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/>
|
<style name="AppBaseTheme" parent="@android:style/Theme.Material.Settings">
|
||||||
|
<!--
|
||||||
|
Theme customizations available in newer API levels can go in
|
||||||
|
res/values-vXX/styles.xml, while customizations related to
|
||||||
|
backward-compatibility can go here.
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Application theme. -->
|
||||||
|
<style name="AppTheme" parent="AppBaseTheme">
|
||||||
|
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||||
|
<!-- Setting's theme's accent color makes ProgressBar useless, reset back. -->
|
||||||
|
<item name="android:colorAccent">@*android:color/material_deep_teal_500</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,433 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.LoadedApk;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.ConnectivityManager.NetworkCallback;
|
||||||
|
import android.net.Network;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
|
import android.net.NetworkRequest;
|
||||||
|
import android.net.Proxy;
|
||||||
|
import android.net.TrafficStats;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.telephony.CarrierConfigManager;
|
||||||
|
import android.telephony.Rlog;
|
||||||
|
import android.telephony.SubscriptionManager;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.webkit.SslErrorHandler;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.internal.telephony.PhoneConstants;
|
||||||
|
import com.android.internal.telephony.TelephonyIntents;
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 before loading the real
|
||||||
|
* portal page and apply carrier actions on the portal activation result.
|
||||||
|
*/
|
||||||
|
public class CaptivePortalLoginActivity extends Activity {
|
||||||
|
private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
|
||||||
|
private static final boolean DBG = true;
|
||||||
|
|
||||||
|
private static final int SOCKET_TIMEOUT_MS = 10 * 1000;
|
||||||
|
private static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
|
||||||
|
|
||||||
|
private URL mUrl;
|
||||||
|
private Network mNetwork;
|
||||||
|
private NetworkCallback mNetworkCallback;
|
||||||
|
private ConnectivityManager mCm;
|
||||||
|
private WebView mWebView;
|
||||||
|
private MyWebViewClient mWebViewClient;
|
||||||
|
private boolean mLaunchBrowser = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mCm = ConnectivityManager.from(this);
|
||||||
|
mUrl = getUrlForCaptivePortal();
|
||||||
|
if (mUrl == null) {
|
||||||
|
done(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DBG) logd(String.format("onCreate for %s", mUrl.toString()));
|
||||||
|
setContentView(R.layout.activity_captive_portal_login);
|
||||||
|
getActionBar().setDisplayShowHomeEnabled(false);
|
||||||
|
|
||||||
|
mWebView = (WebView) findViewById(R.id.webview);
|
||||||
|
mWebView.clearCache(true);
|
||||||
|
WebSettings webSettings = mWebView.getSettings();
|
||||||
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
|
||||||
|
mWebViewClient = new MyWebViewClient();
|
||||||
|
mWebView.setWebViewClient(mWebViewClient);
|
||||||
|
mWebView.setWebChromeClient(new MyWebChromeClient());
|
||||||
|
|
||||||
|
mNetwork = getNetworkForCaptivePortal();
|
||||||
|
if (mNetwork == null) {
|
||||||
|
requestNetworkForCaptivePortal();
|
||||||
|
} else {
|
||||||
|
mCm.bindProcessToNetwork(mNetwork);
|
||||||
|
// Start initial page load so WebView finishes loading proxy settings.
|
||||||
|
// Actual load of mUrl is initiated by MyWebViewClient.
|
||||||
|
mWebView.loadData("", "text/html", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
WebView myWebView = (WebView) findViewById(R.id.webview);
|
||||||
|
if (myWebView.canGoBack() && mWebViewClient.allowBack()) {
|
||||||
|
myWebView.goBack();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
releaseNetworkRequest();
|
||||||
|
if (mLaunchBrowser) {
|
||||||
|
// Give time for this network to become default. After 500ms just proceed.
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
// TODO: This misses when mNetwork underlies a VPN.
|
||||||
|
if (mNetwork.equals(mCm.getActiveNetwork())) break;
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String url = mUrl.toString();
|
||||||
|
if (DBG) logd("starting activity with intent ACTION_VIEW for " + url);
|
||||||
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
|
||||||
|
private void setWebViewProxy() {
|
||||||
|
LoadedApk loadedApk = getApplication().mLoadedApk;
|
||||||
|
try {
|
||||||
|
Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
|
||||||
|
receiversField.setAccessible(true);
|
||||||
|
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
|
||||||
|
for (Object receiverMap : receivers.values()) {
|
||||||
|
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
|
||||||
|
Class clazz = rec.getClass();
|
||||||
|
if (clazz.getName().contains("ProxyChangeListener")) {
|
||||||
|
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
|
||||||
|
Intent.class);
|
||||||
|
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
||||||
|
onReceiveMethod.invoke(rec, getApplicationContext(), intent);
|
||||||
|
Log.v(TAG, "Prompting WebView proxy reload.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
loge("Exception while setting WebView proxy: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void done(boolean success) {
|
||||||
|
if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString()));
|
||||||
|
if (success) {
|
||||||
|
// Trigger re-evaluation upon success http response code
|
||||||
|
CarrierActionUtils.applyCarrierAction(
|
||||||
|
CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(),
|
||||||
|
getApplicationContext());
|
||||||
|
CarrierActionUtils.applyCarrierAction(
|
||||||
|
CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(),
|
||||||
|
getApplicationContext());
|
||||||
|
CarrierActionUtils.applyCarrierAction(
|
||||||
|
CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
|
||||||
|
getApplicationContext());
|
||||||
|
|
||||||
|
}
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL getUrlForCaptivePortal() {
|
||||||
|
String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
url = mCm.getCaptivePortalServerUrl();
|
||||||
|
}
|
||||||
|
final CarrierConfigManager configManager = getApplicationContext()
|
||||||
|
.getSystemService(CarrierConfigManager.class);
|
||||||
|
final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
|
||||||
|
SubscriptionManager.getDefaultVoiceSubscriptionId());
|
||||||
|
final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
|
||||||
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
|
||||||
|
if (!ArrayUtils.isEmpty(portalURLs)) {
|
||||||
|
for (String portalUrl : portalURLs) {
|
||||||
|
if (url.startsWith(portalUrl)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new URL(url);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
loge("Invalid captive portal URL " + url);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testForCaptivePortal() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
// Give time for captive portal to open.
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
HttpURLConnection urlConnection = null;
|
||||||
|
int httpResponseCode = 500;
|
||||||
|
TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
|
||||||
|
try {
|
||||||
|
urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
|
||||||
|
urlConnection.setInstanceFollowRedirects(false);
|
||||||
|
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
|
||||||
|
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
|
||||||
|
urlConnection.setUseCaches(false);
|
||||||
|
urlConnection.getInputStream();
|
||||||
|
httpResponseCode = urlConnection.getResponseCode();
|
||||||
|
} catch (IOException e) {
|
||||||
|
} finally {
|
||||||
|
if (urlConnection != null) urlConnection.disconnect();
|
||||||
|
}
|
||||||
|
if (httpResponseCode == 204) {
|
||||||
|
done(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 requestNetworkForCaptivePortal() {
|
||||||
|
NetworkRequest request = new NetworkRequest.Builder()
|
||||||
|
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||||
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
|
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAvailable(Network network) {
|
||||||
|
if (DBG) logd("Network available: " + network);
|
||||||
|
mCm.bindProcessToNetwork(network);
|
||||||
|
mNetwork = network;
|
||||||
|
runOnUiThreadIfNotFinishing(() -> {
|
||||||
|
// Start initial page load so WebView finishes loading proxy settings.
|
||||||
|
// Actual load of mUrl is initiated by MyWebViewClient.
|
||||||
|
mWebView.loadData("", "text/html", null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnavailable() {
|
||||||
|
if (DBG) logd("Network unavailable");
|
||||||
|
runOnUiThreadIfNotFinishing(() -> {
|
||||||
|
// Instead of not loading anything in webview, simply load the page and return
|
||||||
|
// HTTP error page in the absence of network connection.
|
||||||
|
mWebView.loadUrl(mUrl.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
logd("request Network for captive portal");
|
||||||
|
mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseNetworkRequest() {
|
||||||
|
logd("release Network for captive portal");
|
||||||
|
if (mNetworkCallback != null) {
|
||||||
|
mCm.unregisterNetworkCallback(mNetworkCallback);
|
||||||
|
mNetworkCallback = null;
|
||||||
|
mNetwork = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyWebViewClient extends WebViewClient {
|
||||||
|
private static final String INTERNAL_ASSETS = "file:///android_asset/";
|
||||||
|
private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
|
||||||
|
// How many Android device-independent-pixels per scaled-pixel
|
||||||
|
// dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
|
||||||
|
private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
|
||||||
|
getResources().getDisplayMetrics())
|
||||||
|
/ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
|
||||||
|
getResources().getDisplayMetrics());
|
||||||
|
private int mPagesLoaded;
|
||||||
|
|
||||||
|
// If we haven't finished cleaning up the history, don't allow going back.
|
||||||
|
public boolean allowBack() {
|
||||||
|
return mPagesLoaded > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
if (url.contains(mBrowserBailOutToken)) {
|
||||||
|
mLaunchBrowser = true;
|
||||||
|
done(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The first page load is used only to cause the WebView to
|
||||||
|
// fetch the proxy settings. Don't update the URL bar, and
|
||||||
|
// don't check if the captive portal is still there.
|
||||||
|
if (mPagesLoaded == 0) return;
|
||||||
|
// For internally generated pages, leave URL bar listing prior URL as this is the URL
|
||||||
|
// the page refers to.
|
||||||
|
if (!url.startsWith(INTERNAL_ASSETS)) {
|
||||||
|
final TextView myUrlBar = (TextView) findViewById(R.id.url_bar);
|
||||||
|
myUrlBar.setText(url);
|
||||||
|
}
|
||||||
|
if (mNetwork != null) {
|
||||||
|
testForCaptivePortal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
mPagesLoaded++;
|
||||||
|
if (mPagesLoaded == 1) {
|
||||||
|
// Now that WebView has loaded at least one page we know it has read in the proxy
|
||||||
|
// settings. Now prompt the WebView read the Network-specific proxy settings.
|
||||||
|
setWebViewProxy();
|
||||||
|
// Load the real page.
|
||||||
|
view.loadUrl(mUrl.toString());
|
||||||
|
return;
|
||||||
|
} else if (mPagesLoaded == 2) {
|
||||||
|
// Prevent going back to empty first page.
|
||||||
|
view.clearHistory();
|
||||||
|
}
|
||||||
|
if (mNetwork != null) {
|
||||||
|
testForCaptivePortal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Android device-independent-pixels (dp) to HTML size.
|
||||||
|
private String dp(int dp) {
|
||||||
|
// HTML px's are scaled just like dp's, so just add "px" suffix.
|
||||||
|
return Integer.toString(dp) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Android scaled-pixels (sp) to HTML size.
|
||||||
|
private String sp(int sp) {
|
||||||
|
// Convert sp to dp's.
|
||||||
|
float dp = sp * mDpPerSp;
|
||||||
|
// Apply a scale factor to make things look right.
|
||||||
|
dp *= 1.3;
|
||||||
|
// Convert dp's to HTML size.
|
||||||
|
return dp((int) dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A web page consisting of a large broken lock icon to indicate SSL failure.
|
||||||
|
private final String SSL_ERROR_HTML = "<html><head><style>"
|
||||||
|
+ "body { margin-left:" + dp(48) + "; margin-right:" + dp(48) + "; "
|
||||||
|
+ "margin-top:" + dp(96) + "; background-color:#fafafa; }"
|
||||||
|
+ "img { width:" + dp(48) + "; height:" + dp(48) + "; }"
|
||||||
|
+ "div.warn { font-size:" + sp(16) + "; margin-top:" + dp(16) + "; "
|
||||||
|
+ " opacity:0.87; line-height:1.28; }"
|
||||||
|
+ "div.example { font-size:" + sp(14) + "; margin-top:" + dp(16) + "; "
|
||||||
|
+ " opacity:0.54; line-height:1.21905; }"
|
||||||
|
+ "a { font-size:" + sp(14) + "; text-decoration:none; text-transform:uppercase; "
|
||||||
|
+ " margin-top:" + dp(24) + "; display:inline-block; color:#4285F4; "
|
||||||
|
+ " height:" + dp(48) + "; font-weight:bold; }"
|
||||||
|
+ "</style></head><body><p><img src=quantum_ic_warning_amber_96.png><br>"
|
||||||
|
+ "<div class=warn>%s</div>"
|
||||||
|
+ "<div class=example>%s</div>" + "<a href=%s>%s</a></body></html>";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: "
|
||||||
|
// Only show host to avoid leaking private info.
|
||||||
|
+ Uri.parse(error.getUrl()).getHost() + " certificate: "
|
||||||
|
+ error.getCertificate() + "); displaying SSL warning.");
|
||||||
|
final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning),
|
||||||
|
getString(R.string.ssl_error_example), mBrowserBailOutToken,
|
||||||
|
getString(R.string.ssl_error_continue));
|
||||||
|
view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
if (url.startsWith("tel:")) {
|
||||||
|
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyWebChromeClient extends WebChromeClient {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(WebView view, int newProgress) {
|
||||||
|
final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
|
||||||
|
myProgressBar.setProgress(newProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runOnUiThreadIfNotFinishing(Runnable r) {
|
||||||
|
if (!isFinishing()) {
|
||||||
|
runOnUiThread(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logd(String s) {
|
||||||
|
Rlog.d(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loge(String s) {
|
||||||
|
Rlog.d(TAG, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -112,8 +112,10 @@ public class CarrierActionUtils {
|
|||||||
logd("onShowCaptivePortalNotification");
|
logd("onShowCaptivePortalNotification");
|
||||||
final NotificationManager notificationMgr = context.getSystemService(
|
final NotificationManager notificationMgr = context.getSystemService(
|
||||||
NotificationManager.class);
|
NotificationManager.class);
|
||||||
Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class);
|
Intent portalIntent = new Intent(context, CaptivePortalLoginActivity.class);
|
||||||
portalIntent.putExtras(intent);
|
portalIntent.putExtras(intent);
|
||||||
|
portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
|
||||||
|
| Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent,
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
Notification notification = getNotification(context, R.string.portal_notification_id,
|
Notification notification = getNotification(context, R.string.portal_notification_id,
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user