blob: 30bbb04b72d3a815ee922ea53f7d6f9a67754ab2 [file] [log] [blame]
/*
* Copyright (C) 2006 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 android.webkit;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.http.*;
import android.os.*;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Map;
import junit.framework.Assert;
class Network {
private static final String LOGTAG = "network";
/**
* Static instance of a Network object.
*/
private static Network sNetwork;
/**
* Flag to store the state of platform notifications, for the case
* when the Network object has not been constructed yet
*/
private static boolean sPlatformNotifications;
/**
* Reference count for platform notifications as the network class is a
* static and can exist over multiple activities, thus over multiple
* onPause/onResume pairs.
*/
private static int sPlatformNotificationEnableRefCount;
/**
* Proxy username if known (used for pre-emptive proxy authentication).
*/
private String mProxyUsername;
/**
* Proxy password if known (used for pre-emptive proxy authentication).
*/
private String mProxyPassword;
/**
* Network request queue (requests are added from the browser thread).
*/
private RequestQueue mRequestQueue;
/**
* SSL error handler: takes care of synchronization of multiple async
* loaders with SSL-related problems.
*/
private SslErrorHandlerImpl mSslErrorHandler;
/**
* HTTP authentication handler: takes care of synchronization of HTTP
* authentication requests.
*/
private HttpAuthHandlerImpl mHttpAuthHandler;
private Context mContext;
/**
* True if the currently used network connection is a roaming phone
* connection.
*/
private boolean mRoaming;
/**
* Tracks if we are roaming.
*/
private RoamingMonitor mRoamingMonitor;
/**
* @return The singleton instance of the network.
*/
public static synchronized Network getInstance(Context context) {
if (sNetwork == null) {
// Note Context of the Application is used here, rather than
// the what is passed in (usually a Context derived from an
// Activity) so the intent receivers belong to the application
// rather than an activity - this fixes the issue where
// Activities are created and destroyed during the lifetime of
// an Application
sNetwork = new Network(context.getApplicationContext());
if (sPlatformNotifications) {
// Adjust the ref count before calling enable as it is already
// taken into account when the static function was called
// directly
--sPlatformNotificationEnableRefCount;
enablePlatformNotifications();
}
}
return sNetwork;
}
/**
* Enables data state and proxy tracking
*/
public static void enablePlatformNotifications() {
if (++sPlatformNotificationEnableRefCount == 1) {
if (sNetwork != null) {
sNetwork.mRequestQueue.enablePlatformNotifications();
sNetwork.monitorRoaming();
} else {
sPlatformNotifications = true;
}
}
}
/**
* If platform notifications are enabled, this should be called
* from onPause() or onStop()
*/
public static void disablePlatformNotifications() {
if (--sPlatformNotificationEnableRefCount == 0) {
if (sNetwork != null) {
sNetwork.mRequestQueue.disablePlatformNotifications();
sNetwork.stopMonitoringRoaming();
} else {
sPlatformNotifications = false;
}
}
}
/**
* Creates a new Network object.
* XXX: Must be created in the same thread as WebCore!!!!!
*/
private Network(Context context) {
if (DebugFlags.NETWORK) {
Assert.assertTrue(Thread.currentThread().
getName().equals(WebViewCore.THREAD_NAME));
}
mContext = context;
mSslErrorHandler = new SslErrorHandlerImpl();
mHttpAuthHandler = new HttpAuthHandlerImpl(this);
mRequestQueue = new RequestQueue(context);
}
private class RoamingMonitor extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()))
return;
NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if (info != null)
mRoaming = info.isRoaming();
};
};
private void monitorRoaming() {
mRoamingMonitor = new RoamingMonitor();
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(sNetwork.mRoamingMonitor, filter);
}
private void stopMonitoringRoaming() {
if (mRoamingMonitor != null) {
mContext.unregisterReceiver(mRoamingMonitor);
mRoamingMonitor = null;
}
}
/**
* Request a url from either the network or the file system.
* @param url The url to load.
* @param method The http method.
* @param headers The http headers.
* @param postData The body of the request.
* @param loader A LoadListener for receiving the results of the request.
* @return True if the request was successfully queued.
*/
public boolean requestURL(String method,
Map<String, String> headers,
byte [] postData,
LoadListener loader) {
String url = loader.url();
// Not a valid url, return false because we won't service the request!
if (!URLUtil.isValidUrl(url)) {
return false;
}
// asset, res, file system or data stream are handled in the other code
// path. This only handles network request.
if (URLUtil.isAssetUrl(url) || URLUtil.isResourceUrl(url)
|| URLUtil.isFileUrl(url) || URLUtil.isDataUrl(url)) {
return false;
}
// If this is a prefetch, abort it if we're roaming.
if (mRoaming && headers.containsKey("X-Moz") && "prefetch".equals(headers.get("X-Moz"))) {
return false;
}
/* FIXME: this is lame. Pass an InputStream in, rather than
making this lame one here */
InputStream bodyProvider = null;
int bodyLength = 0;
if (postData != null) {
bodyLength = postData.length;
bodyProvider = new ByteArrayInputStream(postData);
}
RequestQueue q = mRequestQueue;
RequestHandle handle = null;
if (loader.isSynchronous()) {
handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
method, headers, loader, bodyProvider, bodyLength);
loader.attachRequestHandle(handle);
handle.processRequest();
loader.loadSynchronousMessages();
} else {
handle = q.queueRequest(url, loader.getWebAddress(), method,
headers, loader, bodyProvider, bodyLength);
// FIXME: Although this is probably a rare condition, normal network
// requests are processed in a separate thread. This means that it
// is possible to process part of the request before setting the
// request handle on the loader. We should probably refactor this to
// ensure the handle is attached before processing begins.
loader.attachRequestHandle(handle);
}
return true;
}
/**
* @return True iff there is a valid proxy set.
*/
public boolean isValidProxySet() {
// The proxy host and port can be set within a different thread during
// an Intent broadcast.
synchronized (mRequestQueue) {
return mRequestQueue.getProxyHost() != null;
}
}
/**
* Get the proxy hostname.
* @return The proxy hostname obtained from the network queue and proxy
* settings.
*/
public String getProxyHostname() {
return mRequestQueue.getProxyHost().getHostName();
}
/**
* @return The proxy username or null if none.
*/
public synchronized String getProxyUsername() {
return mProxyUsername;
}
/**
* Sets the proxy username.
* @param proxyUsername Username to use when
* connecting through the proxy.
*/
public synchronized void setProxyUsername(String proxyUsername) {
if (DebugFlags.NETWORK) {
Assert.assertTrue(isValidProxySet());
}
mProxyUsername = proxyUsername;
}
/**
* @return The proxy password or null if none.
*/
public synchronized String getProxyPassword() {
return mProxyPassword;
}
/**
* Sets the proxy password.
* @param proxyPassword Password to use when
* connecting through the proxy.
*/
public synchronized void setProxyPassword(String proxyPassword) {
if (DebugFlags.NETWORK) {
Assert.assertTrue(isValidProxySet());
}
mProxyPassword = proxyPassword;
}
/**
* Saves the state of network handlers (user SSL and HTTP-authentication
* preferences).
* @param outState The out-state to save (write) to.
* @return True iff succeeds.
*/
public boolean saveState(Bundle outState) {
if (DebugFlags.NETWORK) {
Log.v(LOGTAG, "Network.saveState()");
}
return mSslErrorHandler.saveState(outState);
}
/**
* Restores the state of network handlers (user SSL and HTTP-authentication
* preferences).
* @param inState The in-state to load (read) from.
* @return True iff succeeds.
*/
public boolean restoreState(Bundle inState) {
if (DebugFlags.NETWORK) {
Log.v(LOGTAG, "Network.restoreState()");
}
return mSslErrorHandler.restoreState(inState);
}
/**
* Clears user SSL-error preference table.
*/
public void clearUserSslPrefTable() {
mSslErrorHandler.clear();
}
/**
* Handles SSL error(s) on the way up to the user: the user must decide
* whether errors should be ignored or not.
* @param loader The loader that resulted in SSL errors.
*/
public void handleSslErrorRequest(LoadListener loader) {
if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
if (loader != null) {
mSslErrorHandler.handleSslErrorRequest(loader);
}
}
/* package */ boolean checkSslPrefTable(LoadListener loader,
SslError error) {
if (loader != null && error != null) {
return mSslErrorHandler.checkSslPrefTable(loader, error);
}
return false;
}
/**
* Handles authentication requests on their way up to the user (the user
* must provide credentials).
* @param loader The loader that resulted in an HTTP
* authentication request.
*/
public void handleAuthRequest(LoadListener loader) {
if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
if (loader != null) {
mHttpAuthHandler.handleAuthRequest(loader);
}
}
// Performance probe
public void startTiming() {
mRequestQueue.startTiming();
}
public void stopTiming() {
mRequestQueue.stopTiming();
}
}