M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,592 @@
/*
* Copyright (C) 2008 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.net;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.os.Binder;
import android.os.RemoteException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Class that answers queries about the state of network connectivity. It also
* notifies applications when network connectivity changes. Get an instance
* of this class by calling
* {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.CONNECTIVITY_SERVICE)}.
* <p>
* The primary responsibilities of this class are to:
* <ol>
* <li>Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)</li>
* <li>Send broadcast intents when network connectivity changes</li>
* <li>Attempt to "fail over" to another network when connectivity to a network
* is lost</li>
* <li>Provide an API that allows applications to query the coarse-grained or fine-grained
* state of the available networks</li>
* </ol>
*/
public class ConnectivityManager
{
/**
* A change in network connectivity has occurred. A connection has either
* been established or lost. The NetworkInfo for the affected network is
* sent as an extra; it should be consulted to see what kind of
* connectivity event occurred.
* <p/>
* If this is a connection that was the result of failing over from a
* disconnected network, then the FAILOVER_CONNECTION boolean extra is
* set to true.
* <p/>
* For a loss of connectivity, if the connectivity manager is attempting
* to connect (or has already connected) to another network, the
* NetworkInfo for the new network is also passed as an extra. This lets
* any receivers of the broadcast know that they should not necessarily
* tell the user that no data traffic will be possible. Instead, the
* reciever should expect another broadcast soon, indicating either that
* the failover attempt succeeded (and so there is still overall data
* connectivity), or that the failover attempt failed, meaning that all
* connectivity has been lost.
* <p/>
* For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
* is set to {@code true} if there are no connected networks at all.
*/
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
/**
* The lookup key for a {@link NetworkInfo} object. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
*/
public static final String EXTRA_NETWORK_INFO = "networkInfo";
/**
* The lookup key for a boolean that indicates whether a connect event
* is for a network to which the connectivity manager was failing over
* following a disconnect on another network.
* Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
*/
public static final String EXTRA_IS_FAILOVER = "isFailover";
/**
* The lookup key for a {@link NetworkInfo} object. This is supplied when
* there is another network that it may be possible to connect to. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
*/
public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
/**
* The lookup key for a boolean that indicates whether there is a
* complete lack of connectivity, i.e., no network is available.
* Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
*/
public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
/**
* The lookup key for a string that indicates why an attempt to connect
* to a network failed. The string has no particular structure. It is
* intended to be used in notifications presented to users. Retrieve
* it with {@link android.content.Intent#getStringExtra(String)}.
*/
public static final String EXTRA_REASON = "reason";
/**
* The lookup key for a string that provides optionally supplied
* extra information about the network state. The information
* may be passed up from the lower networking layers, and its
* meaning may be specific to a particular network type. Retrieve
* it with {@link android.content.Intent#getStringExtra(String)}.
*/
public static final String EXTRA_EXTRA_INFO = "extraInfo";
/**
* The lookup key for an int that provides information about
* our connection to the internet at large. 0 indicates no connection,
* 100 indicates a great connection. Retrieve it with
* {@link android.content.Intent@getIntExtra(String)}.
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
/**
* Broadcast Action: The setting for background data usage has changed
* values. Use {@link #getBackgroundDataSetting()} to get the current value.
* <p>
* If an application uses the network in the background, it should listen
* for this broadcast and stop using the background data if the value is
* false.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
/**
* Broadcast Action: The network connection may not be good
* uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
* {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify
* the network and it's condition.
* @hide
*/
public static final String INET_CONDITION_ACTION =
"android.net.conn.INET_CONDITION_ACTION";
/**
* Broadcast Action: A tetherable connection has come or gone
* TODO - finish the doc
* @hide
*/
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
/**
* @hide
* gives a String[]
*/
public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
* @hide
* gives a String[]
*/
public static final String EXTRA_ACTIVE_TETHER = "activeArray";
/**
* @hide
* gives a String[]
*/
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/**
* The Default Mobile data connection. When active, all data traffic
* will use this connection by default. Should not coexist with other
* default connections.
*/
public static final int TYPE_MOBILE = 0;
/**
* The Default WIFI data connection. When active, all data traffic
* will use this connection by default. Should not coexist with other
* default connections.
*/
public static final int TYPE_WIFI = 1;
/**
* An MMS-specific Mobile data connection. This connection may be the
* same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Multimedia Messaging
* Service servers. It may coexist with default data connections.
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
* A SUPL-specific Mobile data connection. This connection may be the
* same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Secure User Plane
* Location servers for help locating the device. It may coexist with
* default data connections.
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
* A DUN-specific Mobile data connection. This connection may be the
* same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applicaitons performing a Dial Up Networking bridge so that
* the carrier is aware of DUN traffic. It may coexist with default data
* connections.
*/
public static final int TYPE_MOBILE_DUN = 4;
/**
* A High Priority Mobile data connection. This connection is typically
* the same as {@link #TYPE_MOBILE} but the routing setup is different.
* Only requesting processes will have access to the Mobile DNS servers
* and only IP's explicitly requested via {@link #requestRouteToHost}
* will route over this interface if a default route exists.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
/**
* The Default WiMAX data connection. When active, all data traffic
* will use this connection by default. Should not coexist with other
* default connections.
*/
public static final int TYPE_WIMAX = 6;
/**
* Bluetooth data connection.
* @hide
*/
public static final int TYPE_BLUETOOTH = 7;
/** {@hide} */
public static final int TYPE_DUMMY = 8;
/** {@hide} */
public static final int TYPE_ETHERNET = 9;
/** {@hide} TODO: Need to adjust this for WiMAX. */
public static final int MAX_RADIO_TYPE = TYPE_ETHERNET;
/** {@hide} TODO: Need to adjust this for WiMAX. */
public static final int MAX_NETWORK_TYPE = TYPE_ETHERNET;
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
private IConnectivityManager mService;
static public boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
}
public void setNetworkPreference(int preference) {
try {
mService.setNetworkPreference(preference);
} catch (RemoteException e) {
}
}
public int getNetworkPreference() {
try {
return mService.getNetworkPreference();
} catch (RemoteException e) {
return -1;
}
}
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
} catch (RemoteException e) {
return null;
}
}
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
} catch (RemoteException e) {
return null;
}
}
public NetworkInfo[] getAllNetworkInfo() {
try {
return mService.getAllNetworkInfo();
} catch (RemoteException e) {
return null;
}
}
/** {@hide} */
public boolean setRadios(boolean turnOn) {
try {
return mService.setRadios(turnOn);
} catch (RemoteException e) {
return false;
}
}
/** {@hide} */
public boolean setRadio(int networkType, boolean turnOn) {
try {
return mService.setRadio(networkType, turnOn);
} catch (RemoteException e) {
return false;
}
}
/**
* Tells the underlying networking system that the caller wants to
* begin using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
* @param networkType specifies which network the request pertains to
* @param feature the name of the feature to be used
* @return an integer value representing the outcome of the request.
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
try {
return mService.startUsingNetworkFeature(networkType, feature,
new Binder());
} catch (RemoteException e) {
return -1;
}
}
/**
* Tells the underlying networking system that the caller is finished
* using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
* @param networkType specifies which network the request pertains to
* @param feature the name of the feature that is no longer needed
* @return an integer value representing the outcome of the request.
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
try {
return mService.stopUsingNetworkFeature(networkType, feature);
} catch (RemoteException e) {
return -1;
}
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
* @param networkType the type of the network over which traffic to the specified
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
if (inetAddress == null) {
return false;
}
return requestRouteToHostAddress(networkType, inetAddress);
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
* @param networkType the type of the network over which traffic to the specified
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
* @hide
*/
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
String address = hostAddress.getHostAddress();
try {
return mService.requestRouteToHostAddress(networkType, address);
} catch (RemoteException e) {
return false;
}
}
/**
* Returns the value of the setting for background data usage. If false,
* applications should not use the network if the application is not in the
* foreground. Developers should respect this setting, and check the value
* of this before performing any background data operations.
* <p>
* All applications that have background services that use the network
* should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
*
* @return Whether background data usage is allowed.
*/
public boolean getBackgroundDataSetting() {
try {
return mService.getBackgroundDataSetting();
} catch (RemoteException e) {
// Err on the side of safety
return false;
}
}
/**
* Sets the value of the setting for background data usage.
*
* @param allowBackgroundData Whether an application should use data while
* it is in the background.
*
* @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
* @see #getBackgroundDataSetting()
* @hide
*/
public void setBackgroundDataSetting(boolean allowBackgroundData) {
try {
mService.setBackgroundDataSetting(allowBackgroundData);
} catch (RemoteException e) {
}
}
/**
* Gets the value of the setting for enabling Mobile data.
*
* @return Whether mobile data is enabled.
* @hide
*/
public boolean getMobileDataEnabled() {
try {
return mService.getMobileDataEnabled();
} catch (RemoteException e) {
return true;
}
}
/**
* Sets the persisted value for enabling/disabling Mobile data.
*
* @param enabled Whether the mobile data connection should be
* used or not.
* @hide
*/
public void setMobileDataEnabled(boolean enabled) {
try {
mService.setMobileDataEnabled(enabled);
} catch (RemoteException e) {
}
}
/**
* Don't allow use of default constructor.
*/
@SuppressWarnings({"UnusedDeclaration"})
private ConnectivityManager() {
}
/**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service) {
if (service == null) {
throw new IllegalArgumentException(
"ConnectivityManager() cannot be constructed with null service");
}
mService = service;
}
/**
* {@hide}
*/
public String[] getTetherableIfaces() {
try {
return mService.getTetherableIfaces();
} catch (RemoteException e) {
return new String[0];
}
}
/**
* {@hide}
*/
public String[] getTetheredIfaces() {
try {
return mService.getTetheredIfaces();
} catch (RemoteException e) {
return new String[0];
}
}
/**
* {@hide}
*/
public String[] getTetheringErroredIfaces() {
try {
return mService.getTetheringErroredIfaces();
} catch (RemoteException e) {
return new String[0];
}
}
/**
* @return error A TETHER_ERROR value indicating success or failure type
* {@hide}
*/
public int tether(String iface) {
try {
return mService.tether(iface);
} catch (RemoteException e) {
return TETHER_ERROR_SERVICE_UNAVAIL;
}
}
/**
* @return error A TETHER_ERROR value indicating success or failure type
* {@hide}
*/
public int untether(String iface) {
try {
return mService.untether(iface);
} catch (RemoteException e) {
return TETHER_ERROR_SERVICE_UNAVAIL;
}
}
/**
* {@hide}
*/
public boolean isTetheringSupported() {
try {
return mService.isTetheringSupported();
} catch (RemoteException e) {
return false;
}
}
/**
* {@hide}
*/
public String[] getTetherableUsbRegexs() {
try {
return mService.getTetherableUsbRegexs();
} catch (RemoteException e) {
return new String[0];
}
}
/**
* {@hide}
*/
public String[] getTetherableWifiRegexs() {
try {
return mService.getTetherableWifiRegexs();
} catch (RemoteException e) {
return new String[0];
}
}
/** {@hide} */
public static final int TETHER_ERROR_NO_ERROR = 0;
/** {@hide} */
public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
/** {@hide} */
public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
/** {@hide} */
public static final int TETHER_ERROR_UNSUPPORTED = 3;
/** {@hide} */
public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
/** {@hide} */
public static final int TETHER_ERROR_MASTER_ERROR = 5;
/** {@hide} */
public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
/** {@hide} */
public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
/** {@hide} */
public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8;
/** {@hide} */
public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
/** {@hide} */
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
/**
* @param iface The name of the interface we're interested in
* @return error The error code of the last error tethering or untethering the named
* interface
* {@hide}
*/
public int getLastTetherError(String iface) {
try {
return mService.getLastTetherError(iface);
} catch (RemoteException e) {
return TETHER_ERROR_SERVICE_UNAVAIL;
}
}
/**
* @param networkType The type of network you want to report on
* @param percentage The quality of the connection 0 is bad, 100 is good
* {@hide}
*/
public void reportInetCondition(int networkType, int percentage) {
try {
mService.reportInetCondition(networkType, percentage);
} catch (RemoteException e) {
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2007 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.net;
/**
* A class for representing UNIX credentials passed via ancillary data
* on UNIX domain sockets. See "man 7 unix" on a desktop linux distro.
*/
public class Credentials {
/** pid of process. root peers may lie. */
private final int pid;
/** uid of process. root peers may lie. */
private final int uid;
/** gid of process. root peers may lie. */
private final int gid;
public Credentials (int pid, int uid, int gid) {
this.pid = pid;
this.uid = uid;
this.gid = gid;
}
public int getPid() {
return pid;
}
public int getUid() {
return uid;
}
public int getGid() {
return gid;
}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2008, 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.net;
parcelable DhcpInfo;
+96
View File
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2008 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.net;
import android.os.Parcelable;
import android.os.Parcel;
/**
* A simple object for retrieving the results of a DHCP request.
*/
public class DhcpInfo implements Parcelable {
public int ipAddress;
public int gateway;
public int netmask;
public int dns1;
public int dns2;
public int serverAddress;
public int leaseDuration;
public DhcpInfo() {
super();
}
public String toString() {
StringBuffer str = new StringBuffer();
str.append("ipaddr "); putAddress(str, ipAddress);
str.append(" gateway "); putAddress(str, gateway);
str.append(" netmask "); putAddress(str, netmask);
str.append(" dns1 "); putAddress(str, dns1);
str.append(" dns2 "); putAddress(str, dns2);
str.append(" DHCP server "); putAddress(str, serverAddress);
str.append(" lease ").append(leaseDuration).append(" seconds");
return str.toString();
}
private static void putAddress(StringBuffer buf, int addr) {
buf.append(addr & 0xff).append('.').
append((addr >>>= 8) & 0xff).append('.').
append((addr >>>= 8) & 0xff).append('.').
append((addr >>>= 8) & 0xff);
}
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ipAddress);
dest.writeInt(gateway);
dest.writeInt(netmask);
dest.writeInt(dns1);
dest.writeInt(dns2);
dest.writeInt(serverAddress);
dest.writeInt(leaseDuration);
}
/** Implement the Parcelable interface {@hide} */
public static final Creator<DhcpInfo> CREATOR =
new Creator<DhcpInfo>() {
public DhcpInfo createFromParcel(Parcel in) {
DhcpInfo info = new DhcpInfo();
info.ipAddress = in.readInt();
info.gateway = in.readInt();
info.netmask = in.readInt();
info.dns1 = in.readInt();
info.dns2 = in.readInt();
info.serverAddress = in.readInt();
info.leaseDuration = in.readInt();
return info;
}
public DhcpInfo[] newArray(int size) {
return new DhcpInfo[size];
}
};
}
+645
View File
@@ -0,0 +1,645 @@
/*
* Copyright (C) 2008 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.net;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.provider.BaseColumns;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
/**
* The Download Manager
*
* @hide
*/
public final class Downloads {
/**
* Download status codes
*/
/**
* This download hasn't started yet
*/
public static final int STATUS_PENDING = 190;
/**
* This download has started
*/
public static final int STATUS_RUNNING = 192;
/**
* This download has successfully completed.
* Warning: there might be other status values that indicate success
* in the future.
* Use isSucccess() to capture the entire category.
*/
public static final int STATUS_SUCCESS = 200;
/**
* This download can't be performed because the content type cannot be
* handled.
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
/**
* This download has completed with an error.
* Warning: there will be other status values that indicate errors in
* the future. Use isStatusError() to capture the entire category.
*/
public static final int STATUS_UNKNOWN_ERROR = 491;
/**
* This download couldn't be completed because of an HTTP
* redirect response that the download manager couldn't
* handle.
*/
public static final int STATUS_UNHANDLED_REDIRECT = 493;
/**
* This download couldn't be completed due to insufficient storage
* space. Typically, this is because the SD card is full.
*/
public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
/**
* This download couldn't be completed because no external storage
* device was found. Typically, this is because the SD card is not
* mounted.
*/
public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
/**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
/**
* Download destinations
*/
/**
* This download will be saved to the external storage. This is the
* default behavior, and should be used for any file that the user
* can freely access, copy, delete. Even with that destination,
* unencrypted DRM files are saved in secure internal storage.
* Downloads to the external destination only write files for which
* there is a registered handler. The resulting files are accessible
* by filename to all applications.
*/
public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1;
/**
* This download will be saved to the download manager's private
* partition. This is the behavior used by applications that want to
* download private files that are used and deleted soon after they
* get downloaded. All file types are allowed, and only the initiating
* application can access the file (indirectly through a content
* provider). This requires the
* android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
*/
public static final int DOWNLOAD_DESTINATION_CACHE = 2;
/**
* This download will be saved to the download manager's private
* partition and will be purged as necessary to make space. This is
* for private files (similar to CACHE_PARTITION) that aren't deleted
* immediately after they are used, and are kept around by the download
* manager as long as space is available.
*/
public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3;
/**
* An invalid download id
*/
public static final long DOWNLOAD_ID_INVALID = -1;
/**
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when that download completes. The
* download's content: uri is specified in the intent's data.
*/
public static final String ACTION_DOWNLOAD_COMPLETED =
"android.intent.action.DOWNLOAD_COMPLETED";
/**
* If extras are specified when requesting a download they will be provided in the intent that
* is sent to the specified class and package when a download has finished.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
/**
* Status class for a download
*/
public static final class StatusInfo {
public boolean completed = false;
/** The filename of the active download. */
public String filename = null;
/** An opaque id for the download */
public long id = DOWNLOAD_ID_INVALID;
/** An opaque status code for the download */
public int statusCode = -1;
/** Approximate number of bytes downloaded so far, for debugging purposes. */
public long bytesSoFar = -1;
/**
* Returns whether the download is completed
* @return a boolean whether the download is complete.
*/
public boolean isComplete() {
return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
}
/**
* Returns whether the download is successful
* @return a boolean whether the download is successful.
*/
public boolean isSuccessful() {
return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
}
}
/**
* Class to access initiate and query download by server uri
*/
public static final class ByUri extends DownloadBase {
/** @hide */
private ByUri() {}
/**
* Query where clause by app data.
* @hide
*/
private static final String QUERY_WHERE_APP_DATA_CLAUSE =
android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?";
/**
* Gets a Cursor pointing to the download(s) of the current system update.
* @hide
*/
private static final Cursor getCurrentOtaDownloads(Context context, String url) {
return context.getContentResolver().query(
android.provider.Downloads.Impl.CONTENT_URI,
DOWNLOADS_PROJECTION,
QUERY_WHERE_APP_DATA_CLAUSE,
new String[] {url},
null);
}
/**
* Returns a StatusInfo with the result of trying to download the
* given URL. Returns null if no attempts have been made.
*/
public static final StatusInfo getStatus(
Context context,
String url,
long redownload_threshold) {
StatusInfo result = null;
boolean hasFailedDownload = false;
long failedDownloadModificationTime = 0;
Cursor c = getCurrentOtaDownloads(context, url);
try {
while (c != null && c.moveToNext()) {
if (result == null) {
result = new StatusInfo();
}
int status = getStatusOfDownload(c, redownload_threshold);
if (status == STATUS_DOWNLOADING_UPDATE ||
status == STATUS_DOWNLOADED_UPDATE) {
result.completed = (status == STATUS_DOWNLOADED_UPDATE);
result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
result.id = c.getLong(DOWNLOADS_COLUMN_ID);
result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
return result;
}
long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
if (hasFailedDownload &&
modTime < failedDownloadModificationTime) {
// older than the one already in result; skip it.
continue;
}
hasFailedDownload = true;
failedDownloadModificationTime = modTime;
result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
}
} finally {
if (c != null) {
c.close();
}
}
return result;
}
/**
* Query where clause for general querying.
*/
private static final String QUERY_WHERE_CLAUSE =
android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
/**
* Delete all the downloads for a package/class pair.
*/
public static final void removeAllDownloadsByPackage(
Context context,
String notification_package,
String notification_class) {
context.getContentResolver().delete(
android.provider.Downloads.Impl.CONTENT_URI,
QUERY_WHERE_CLAUSE,
new String[] { notification_package, notification_class });
}
/**
* The column for the id in the Cursor returned by
* getProgressCursor()
*/
public static final int getProgressColumnId() {
return 0;
}
/**
* The column for the current byte count in the Cursor returned by
* getProgressCursor()
*/
public static final int getProgressColumnCurrentBytes() {
return 1;
}
/**
* The column for the total byte count in the Cursor returned by
* getProgressCursor()
*/
public static final int getProgressColumnTotalBytes() {
return 2;
}
/** @hide */
private static final String[] PROJECTION = {
BaseColumns._ID,
android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
};
/**
* Returns a Cursor representing the progress of the download identified by the ID.
*/
public static final Cursor getProgressCursor(Context context, long id) {
Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI,
String.valueOf(id));
return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
}
}
/**
* Class to access downloads by opaque download id
*/
public static final class ById extends DownloadBase {
/** @hide */
private ById() {}
/**
* Get the mime tupe of the download specified by the download id
*/
public static String getMimeTypeForId(Context context, long downloadId) {
ContentResolver cr = context.getContentResolver();
String mimeType = null;
Cursor downloadCursor = null;
try {
Uri downloadUri = getDownloadUri(downloadId);
downloadCursor = cr.query(
downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE},
null, null, null);
if (downloadCursor.moveToNext()) {
mimeType = downloadCursor.getString(0);
}
} finally {
if (downloadCursor != null) downloadCursor.close();
}
return mimeType;
}
/**
* Delete a download by Id
*/
public static void deleteDownload(Context context, long downloadId) {
ContentResolver cr = context.getContentResolver();
String mimeType = null;
Uri downloadUri = getDownloadUri(downloadId);
cr.delete(downloadUri, null, null);
}
/**
* Open a filedescriptor to a particular download
*/
public static ParcelFileDescriptor openDownload(
Context context, long downloadId, String mode)
throws FileNotFoundException
{
ContentResolver cr = context.getContentResolver();
String mimeType = null;
Uri downloadUri = getDownloadUri(downloadId);
return cr.openFileDescriptor(downloadUri, mode);
}
/**
* Open a stream to a particular download
*/
public static InputStream openDownloadStream(Context context, long downloadId)
throws FileNotFoundException, IOException
{
ContentResolver cr = context.getContentResolver();
String mimeType = null;
Uri downloadUri = getDownloadUri(downloadId);
return cr.openInputStream(downloadUri);
}
private static Uri getDownloadUri(long downloadId) {
return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId);
}
/**
* Returns a StatusInfo with the result of trying to download the
* given URL. Returns null if no attempts have been made.
*/
public static final StatusInfo getStatus(
Context context,
long downloadId) {
StatusInfo result = null;
boolean hasFailedDownload = false;
long failedDownloadModificationTime = 0;
Uri downloadUri = getDownloadUri(downloadId);
ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(
downloadUri, DOWNLOADS_PROJECTION, null /* selection */, null /* selection args */,
null /* sort order */);
try {
if (!c.moveToNext()) {
return result;
}
if (result == null) {
result = new StatusInfo();
}
int status = getStatusOfDownload(c,0);
if (status == STATUS_DOWNLOADING_UPDATE ||
status == STATUS_DOWNLOADED_UPDATE) {
result.completed = (status == STATUS_DOWNLOADED_UPDATE);
result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
result.id = c.getLong(DOWNLOADS_COLUMN_ID);
result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
return result;
}
long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
} finally {
if (c != null) {
c.close();
}
}
return result;
}
}
/**
* Base class with common functionality for the various download classes
*/
public static class DownloadBase {
/** @hide */
DownloadBase() {}
/**
* Initiate a download where the download will be tracked by its URI.
*/
public static long startDownloadByUri(
Context context,
String url,
String cookieData,
boolean showDownload,
int downloadDestination,
boolean allowRoaming,
boolean skipIntegrityCheck,
String title,
String notification_package,
String notification_class,
String notification_extras) {
ContentResolver cr = context.getContentResolver();
// Tell download manager to start downloading update.
ContentValues values = new ContentValues();
values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
: android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
if (title != null) {
values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
}
values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
// NOTE: destination should be seperated from whether the download
// can happen when roaming
int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
switch (downloadDestination) {
case DOWNLOAD_DESTINATION_EXTERNAL:
destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
break;
case DOWNLOAD_DESTINATION_CACHE:
if (allowRoaming) {
destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
} else {
destination =
android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
}
break;
case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
destination =
android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
break;
}
values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
skipIntegrityCheck); // Don't check ETag
if (notification_package != null && notification_class != null) {
values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
notification_package);
values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
notification_class);
if (notification_extras != null) {
values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
notification_extras);
}
}
Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
long downloadId = DOWNLOAD_ID_INVALID;
if (downloadUri != null) {
downloadId = Long.parseLong(downloadUri.getLastPathSegment());
}
return downloadId;
}
}
/** @hide */
private static final int STATUS_INVALID = 0;
/** @hide */
private static final int STATUS_DOWNLOADING_UPDATE = 3;
/** @hide */
private static final int STATUS_DOWNLOADED_UPDATE = 4;
/**
* Column projection for the query to the download manager. This must match
* with the constants DOWNLOADS_COLUMN_*.
* @hide
*/
private static final String[] DOWNLOADS_PROJECTION = {
BaseColumns._ID,
android.provider.Downloads.Impl.COLUMN_APP_DATA,
android.provider.Downloads.Impl.COLUMN_STATUS,
android.provider.Downloads.Impl._DATA,
android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
};
/**
* The column index for the ID.
* @hide
*/
private static final int DOWNLOADS_COLUMN_ID = 0;
/**
* The column index for the URI.
* @hide
*/
private static final int DOWNLOADS_COLUMN_URI = 1;
/**
* The column index for the status code.
* @hide
*/
private static final int DOWNLOADS_COLUMN_STATUS = 2;
/**
* The column index for the filename.
* @hide
*/
private static final int DOWNLOADS_COLUMN_FILENAME = 3;
/**
* The column index for the last modification time.
* @hide
*/
private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
/**
* The column index for the number of bytes downloaded so far.
* @hide
*/
private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
/**
* Gets the status of a download.
*
* @param c A Cursor pointing to a download. The URL column is assumed to be valid.
* @return The status of the download.
* @hide
*/
private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
long realtime = SystemClock.elapsedRealtime();
// TODO(dougz): special handling of 503, 404? (eg, special
// explanatory messages to user)
if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
// Check if it's stuck
long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
long now = System.currentTimeMillis();
if (now < modified || now - modified > redownload_threshold) {
return STATUS_INVALID;
}
return STATUS_DOWNLOADING_UPDATE;
}
if (android.provider.Downloads.Impl.isStatusError(status)) {
return STATUS_INVALID;
}
String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
if (filename == null) {
return STATUS_INVALID;
}
return STATUS_DOWNLOADED_UPDATE;
}
/**
* @hide
*/
private Downloads() {}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.os.Parcelable;
import android.os.Parcel;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
/** @hide */
public class ExtraLinkCapabilities extends LinkCapabilities {
@Override
public void put (int key, String value) {
mCapabilities.put(key, value);
}
public void putAll (Map cap) {
mCapabilities.putAll(cap);
}
public void remove (int key) {
mCapabilities.remove(key);
}
}
+70
View File
@@ -0,0 +1,70 @@
/* Copyright (c) 2010, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
/** {@hide}
* This is an interface that the applications need to implement. An app
* This interface will be used by CnE to notify apps of various events
* related to connectivity.
*/
public interface FmcNotifier {
/* modified or add new entry? Do the same to FMC_STATUS_STR */
public final static int FMC_STATUS_ENABLED = 0; // fmc enabled
public final static int FMC_STATUS_CLOSED = 1; // fmc closed
public final static int FMC_STATUS_INITIALIZED = 2; // requested start from oem
public final static int FMC_STATUS_SHUTTING_DOWN = 3; // requested stop from oem
public final static int FMC_STATUS_NOT_YET_STARTED = 4; // requested stop having been start
public final static int FMC_STATUS_FAILURE = 5; // any unknown failure or memory allocation
public final static int FMC_STATUS_NOT_AVAIL = 6; // fmc not available(check persist)
public final static int FMC_STATUS_DS_NOT_AVAIL = 7; // data server not available
public final static int FMC_STATUS_RETRIED = 8; // out of coverage
public final static int FMC_STATUS_REGISTRATION_SUCCESS = 9; // successful registration
public final static int FMC_STATUS_MAX = 10;
public final static String[] FMC_STATUS_STR = {
"Enabled",
"Closed",
"Initialized...",
"Shuting down...",
"Has not started",
"Failure",
"Fmc not available",
"DS not available",
"OoC - retry...",
"Registration successful",
"Undefined FMC Status"
};
/** {@hide}
* This function notifies the calling function whether FMC was
* successfully enable, disable, stop, failure...
*/
public void onFmcStatus(int status);
}
+220
View File
@@ -0,0 +1,220 @@
/* Copyright (c) 2010 The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
import android.net.IConnectivityManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.IBinder;
import android.util.Log;
import android.os.Handler;
import android.os.Message;
import android.os.Looper;
import android.os.SystemProperties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/** {@hide}
* This class provides a means for applications to specify their requirements
* and request for a link. Apps can also report their satisfaction with the
* assigned link and switch links when a new link is available.
*/
public class FmcProvider
{
static final String LOG_TAG = "FmcProvider";
/* FmcNotifier object to provide notification.
*/
private FmcNotifier mFmcNotifier;
/* handle to connectivity service obj
*/
private IConnectivityManager mService;
private Handler mHandler;
private NotificationsThread mThread;
private FmcEventListener mListener;
private static final int ON_FMC_STATUS = 1;
/* Lock to synchronized the Nodify Handler
*/
private Lock mLock;
private Condition mHandlerAvail;
/** {@hide}
* This constructor can be used by apps to specify
* FMC notifier object to receive notifications.
* @param oem network name
* @param notifier FmcNotifier object to provide notification to
* the calling function
*/
public FmcProvider(FmcNotifier notifier) throws InterruptedException {
mFmcNotifier = notifier;
Log.d(LOG_TAG,"FmcProvider");
/* get handle to connectivity service */
IBinder b = ServiceManager.getService("connectivity");
mService = IConnectivityManager.Stub.asInterface(b);
/* check for mservice to be null and throw a exception */
if(mService == null){ throw new IllegalStateException(
"mService can not be null");
}
mListener = (FmcEventListener)
IFmcEventListener.Stub.asInterface(new FmcEventListener());
mLock = new ReentrantLock();
mHandlerAvail = mLock.newCondition();
mThread = new NotificationsThread();
mThread.start();
/* block until mHandler gets created. */
try{
mLock.lock();
if (mHandler == null) {
mHandlerAvail.await();
}
} finally {
mLock.unlock();
}
}
/** {@hide}
* This function will be used by apps to request to start FMC
* @return {@code true} if the request has been accepted,
* {@code false} otherwise. A return value of true does NOT mean that a
* FMC is available for the app to use. That will delivered via
* the FmcNotifier.
*/
public boolean startFmc(){
Log.d(LOG_TAG,"FmcProvider@startFmc");
if (!SystemProperties.getBoolean("persist.cne.fmc.mode", false)) {
Log.w(LOG_TAG, "startFmc: FMC is disabled. This API is invalid.");
return false;
}
try {
return mService.startFmc(mListener);
} catch ( RemoteException e ) {
Log.w(LOG_TAG,"FmcProvider@startFmc: RemoteException");
e.printStackTrace();
return false;
}
}
/** {@hide}
* This function will be used by apps to stop FMC.
* @return {@code true} if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean stopFmc(){
Log.d(LOG_TAG,"FmcProvider@stopFmc");
try {
return mService.stopFmc(mListener);
} catch ( RemoteException e ) {
Log.w(LOG_TAG,"FmcProvider@stopFmc: RemoteException");
e.printStackTrace();
return false;
}
}
/** {@hide}
* This function will be used by apps to stop FMC.
* @return {@code true} if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean getFmcStatus(){
Log.d(LOG_TAG,"FmcProvider@stopFmc");
try {
return mService.getFmcStatus(mListener);
} catch ( RemoteException e ) {
Log.w(LOG_TAG,"FmcProvider@getFmcStatus: RemoteException");
e.printStackTrace();
return false;
}
}
/** {@hide}
* Callback functions.
*/
private class FmcEventListener extends IFmcEventListener.Stub {
/** {@hide}
* Callback function to send status of current FMC.
* @param status notify calling function with status.
*/
public void onFmcStatus(int status) {
//Log.d(LOG_TAG,"FmcProvider@onFmcStatus: status = "+status);
Message msg = mHandler.obtainMessage(ON_FMC_STATUS, status, 0);
msg.setTarget(mHandler);
msg.sendToTarget();
return;
}
};
private class NotificationsThread extends Thread {
public void run() {
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ON_FMC_STATUS:{
int status = (int)msg.arg1;
if(mFmcNotifier != null){
mFmcNotifier.onFmcStatus(status);
} else {
Log.d(LOG_TAG,"FmcProvider@handleMessage: mFmcNotifier callback is NULL");
}
break;
}
default:
Log.w(LOG_TAG,"FmcProvider@handleMessage: msg.what" + msg.what);
break;
}
}
};
mLock.lock();
mHandlerAvail.signal();
mLock.unlock();
Looper.loop();
}
};
}
@@ -0,0 +1,39 @@
/* Copyright (c) 2009, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
import android.net.LinkInfo;
/** {@hide} */
interface IConSvcEventListener
{
void onLinkAvail(inout LinkInfo info);
void onBetterLinkAvail(inout LinkInfo info);
void onLinkLost(inout LinkInfo info);
void onGetLinkFailure(int reason);
}
+115
View File
@@ -0,0 +1,115 @@
/**
* Copyright (c) 2008, The Android Open Source Project
* Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.net.LinkCapabilities;
import android.net.LinkInfo;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.IBinder;
/**
* Interface that answers queries about, and allows changing, the
* state of network connectivity.
*/
/** {@hide} */
interface IConnectivityManager
{
void setNetworkPreference(int pref);
int getNetworkPreference();
NetworkInfo getActiveNetworkInfo();
NetworkInfo getNetworkInfo(int networkType);
NetworkInfo[] getAllNetworkInfo();
boolean setRadios(boolean onOff);
boolean setRadio(int networkType, boolean turnOn);
int startUsingNetworkFeature(int networkType, in String feature,
in IBinder binder);
int stopUsingNetworkFeature(int networkType, in String feature);
boolean requestRouteToHost(int networkType, int hostAddress);
boolean requestRouteToHostAddress(int networkType, in String hostAddress);
boolean getBackgroundDataSetting();
void setBackgroundDataSetting(boolean allowBackgroundData);
boolean getMobileDataEnabled();
void setMobileDataEnabled(boolean enabled);
int tether(String iface);
int untether(String iface);
int getLastTetherError(String iface);
boolean isTetheringSupported();
String[] getTetherableIfaces();
String[] getTetheredIfaces();
String[] getTetheringErroredIfaces();
String[] getTetherableUsbRegexs();
String[] getTetherableWifiRegexs();
void reportInetCondition(int networkType, int percentage);
/* LinkSocket */
int requestLink(in LinkCapabilities capabilities, IBinder callback);
void releaseLink(int id);
LinkCapabilities requestCapabilities(int id, in int[] capability_keys);
boolean requestQoS(int id, int localPort, String localAddress);
void setTrackedCapabilities(int id, in int[] capabilities);
/* LinkProvider */
boolean getLink_LP(int role, in Map linkReqs, int mPid, IBinder listener);
boolean reportLinkSatisfaction_LP(int role, int mPid, in LinkInfo info, boolean isSatisfied,
boolean isNotifyBetterCon);
boolean releaseLink_LP(int role,int mPid);
boolean switchLink_LP(int role, int mPid, in LinkInfo info, boolean isSwitch);
boolean rejectSwitch_LP(int role, int mPid, in LinkInfo info, boolean isSwitch);
/* FMC */
boolean startFmc(IBinder listener);
boolean stopFmc(IBinder listener);
boolean getFmcStatus(IBinder listener);
}
@@ -0,0 +1,35 @@
/* Copyright (c) 2010, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
/** {@hide} */
interface IFmcEventListener
{
void onFmcStatus(int status);
}
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2010, 2011, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
import android.net.LinkProperties;
import android.net.LinkCapabilities;
/** @hide
* Interface that reports connectivity status to a LinkSocket
*/
interface ILinkSocketMessageHandler
{
/*
* Notify a LinkSocket that a link is available
*
* LinkProperties should be filled in with the following values:
* setInterface(NetworkInterface iface); // iface to use
* addAddress(InetAddress address); // address to bind to
* addDns(InetAddress dns); // DNS addresses the iface is using
* setGateway(InetAddress gateway); // gateway address
* setHttpProxy(ProxyProperties proxy); // not sure about this one
* only addAddress is needed for LinkSocket to work
*/
void onLinkAvail(in LinkProperties properties);
/*
* Notify a LinkSocket that no link is available
*
* Reason codes:
* 0: Unknown failure
* 1: All networks are down
* 2: Networks are available, but none meet requirements
*/
void onGetLinkFailure(int reason);
/*
* Notify a LinkSocket that a better link might be available
*/
void onBetterLinkAvail();
/*
* Notify a LinkSocket that their link has been lost
*/
void onLinkLost();
/*
* We need to discuss these methods
*/
void onCapabilitiesChanged(in LinkCapabilities changedCapabilities);
}
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2009 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.net;
/**
* Callback class for receiving events from an INetworkManagementService
*
* @hide
*/
interface INetworkManagementEventObserver {
/**
* Interface link status has changed.
*
* @param iface The interface.
* @param link True if link is up.
*/
void interfaceLinkStatusChanged(String iface, boolean link);
/**
* An interface has been added to the system
*
* @param iface The interface.
*/
void interfaceAdded(String iface);
/**
* An interface has been removed from the system
*
* @param iface The interface.
*/
void interfaceRemoved(String iface);
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010, 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.net;
import android.os.IBinder;
/**
* Interface that answers queries about data transfer amounts and throttling
*/
/** {@hide} */
interface IThrottleManager
{
long getByteCount(String iface, int dir, int period, int ago);
int getThrottle(String iface);
long getResetTime(String iface);
long getPeriodStartTime(String iface);
long getCliffThreshold(String iface, int cliff);
int getCliffLevel(String iface, int cliff);
String getHelpUri();
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2008, 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.net;
parcelable InterfaceConfiguration;
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2008 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.net;
import android.os.Parcelable;
import android.os.Parcel;
/**
* A simple object for retrieving / setting an interfaces configuration
* @hide
*/
public class InterfaceConfiguration implements Parcelable {
public String hwAddr;
public int ipAddr;
public int netmask;
public String interfaceFlags;
public InterfaceConfiguration() {
super();
}
public String toString() {
StringBuffer str = new StringBuffer();
str.append("ipddress "); putAddress(str, ipAddr);
str.append(" netmask "); putAddress(str, netmask);
str.append(" flags ").append(interfaceFlags);
str.append(" hwaddr ").append(hwAddr);
return str.toString();
}
private static void putAddress(StringBuffer buf, int addr) {
buf.append((addr >> 24) & 0xff).append('.').
append((addr >> 16) & 0xff).append('.').
append((addr >> 8) & 0xff).append('.').
append(addr & 0xff);
}
/**
* This function determines if the interface is up and has a valid IP
* configuration (IP address has a non zero octet).
*
* Note: It is supposed to be quick and hence should not initiate
* any network activity
*/
public boolean isActive() {
try {
if(interfaceFlags.contains("up")) {
/* TODO: We should check for v6 address for the v6-only case */
/*if (ipAddr != 0)*/ return true;
}
} catch (NullPointerException e) {
return false;
}
return false;
}
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(hwAddr);
dest.writeInt(ipAddr);
dest.writeInt(netmask);
dest.writeString(interfaceFlags);
}
/** Implement the Parcelable interface {@hide} */
public static final Creator<InterfaceConfiguration> CREATOR =
new Creator<InterfaceConfiguration>() {
public InterfaceConfiguration createFromParcel(Parcel in) {
InterfaceConfiguration info = new InterfaceConfiguration();
info.hwAddr = in.readString();
info.ipAddr = in.readInt();
info.netmask = in.readInt();
info.interfaceFlags = in.readString();
return info;
}
public InterfaceConfiguration[] newArray(int size) {
return new InterfaceConfiguration[size];
}
};
}
@@ -0,0 +1,22 @@
/*
**
** Copyright (C) 2010 The Android Open Source Project
** Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
**
** 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.net;
parcelable LinkCapabilities;
@@ -0,0 +1,599 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.os.Parcelable;
import android.os.Parcel;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
/**
* A class representing the capabilities of a link.
* <p>
* LinkCapabilities is a mapping of {@link LinkCapabilities.Key}'s to Strings.
* The primary method of creating a LinkCapabilies map is to use the static
* helper function {@link LinkCapabilities#createNeeds(String)}, passing a
* String from {@link LinkCapabilities.Role}. The properties in the
* LinkCapabilities object can then be fine tuned using
* {@link LinkCapabilities#put(int, String)}, where the int is a
* {@link LinkCapabilities.Key}, and the String is the new parameter.
*
* @hide
*/
public class LinkCapabilities implements Parcelable {
private static final String TAG = "LinkCapabilities";
private static final boolean DBG = false;
/** The Map of Keys to Values */
protected HashMap<Integer, String> mCapabilities;
/**
* The set of keys defined for a links capabilities.
*
* Keys starting with RW are read + write, i.e. the application
* can request for a certain requirement corresponding to that key.
* Keys starting with RO are read only, i.e. the the application
* can read the value of that key from the socket but cannot request
* a corresponding requirement.
* <p>
* TODO: Provide a documentation technique for concisely and precisely
* define the syntax for each value string associated with a key.
*/
public static final class Key {
/** No constructor */
private Key() {}
/**
* A string containing the socket's role
*/
public final static int RW_ROLE = 0;
/**
* An integer representing the network type.
* @see ConnectivityManager
*/
public final static int RO_NETWORK_TYPE = 1;
/**
* Desired minimum forward link (download) bandwidth for the
* in kilobits per second (kbps). Values should be strings such
* "50", "100", "1500", etc.
*/
public final static int RW_DESIRED_FWD_BW = 2;
/**
* Required minimum forward link (download) bandwidth, in
* per second (kbps), below which the socket cannot function.
* Values should be strings such as "50", "100", "1500", etc.
*/
public final static int RW_REQUIRED_FWD_BW = 3;
/**
* Minimum available forward link (download) bandwidth for the socket.
* This value is in kilobits per second (kbps).
* Values will be strings such as "50", "100", "1500", etc.
*/
public final static int RO_MIN_AVAILABLE_FWD_BW = 4;
/**
* Maximum available forward link (download) bandwidth for the socket.
* This value is in kilobits per second (kbps).
* Values will be strings such as "50", "100", "1500", etc.
*/
public final static int RO_MAX_AVAILABLE_FWD_BW = 5;
/**
* Desired minimum reverse link (upload) bandwidth for the socket
* in kilobits per second (kbps).
* Values should be strings such as "50", "100", "1500", etc.
* <p>
* This key is set via the needs map.
*/
public final static int RW_DESIRED_REV_BW = 6;
/**
* Required minimum reverse link (upload) bandwidth, in kilobits
* per second (kbps), below which the socket cannot function.
* If a rate is not specified, the default rate of kbps will be
* Values should be strings such as "50", "100", "1500", etc.
*/
public final static int RW_REQUIRED_REV_BW = 7;
/**
* Minimum available reverse link (upload) bandwidth for the socket.
* This value is in kilobits per second (kbps).
* Values will be strings such as "50", "100", "1500", etc.
*/
public final static int RO_MIN_AVAILABLE_REV_BW = 8;
/**
* Maximum available reverse link (upload) bandwidth for the socket.
* This value is in kilobits per second (kbps).
* Values will be strings such as "50", "100", "1500", etc.
*/
public final static int RO_MAX_AVAILABLE_REV_BW = 9;
/**
* Maximum incoming flow (forward link) latency for the socket, in
* milliseconds, above which socket cannot function.
* Values should be strings such as "50", "300", "500",
* etc.
*/
public final static int RW_MAX_ALLOWED_FWD_LATENCY = 10;
/**
* Current estimated incoming flow (forward link) latency of the socket, in
* milliseconds. Values will be strings such as "50", "100",
* "1500", etc.
*/
public final static int RO_CURRENT_FWD_LATENCY = 11;
/**
* Maximum outgoing flow (reverse link) latency for the socket, in
* milliseconds, above which socket cannot function. Values
* should be strings such as "50", "300", "500", etc.
*/
public final static int RW_MAX_ALLOWED_REV_LATENCY = 12;
/**
* Current estimated outgoing flow (reverse link) latency of the socket, in
* milliseconds. Values will be strings such as "50", "100",
* "1500", etc.
*/
public final static int RO_CURRENT_REV_LATENCY = 13;
/**
* Interface that the socket is bound to. This can be a virtual
* interface (e.g. VPN or Mobile IP) or a physical interface
* (e.g. wlan0 or rmnet0).
* Values will be strings such as "wlan0", "rmnet0"
*/
// TODO: consider removing, this is information about the interface, not the socket
public final static int RO_BOUND_INTERFACE = 14;
/**
* Physical interface that the socket is routed on.
* This can be different from BOUND_INTERFACE in cases such as
* VPN or Mobile IP. The physical interface may change over time
* if seamless mobility is supported.
* Values will be strings such as "wlan0", "rmnet0"
*/
// TODO: consider removing, this is information about the interface, not the socket
public final static int RO_PHYSICAL_INTERFACE = 15;
/**
* Network types that the socket will restrict itself to.
* If the network type is not on this list, the socket will
* not bind to it.
* Values will be comma separated strings such as "wifi",
* "mobile", or "mobile, wimax".
*/
public final static int RW_ALLOWED_NETWORKS = 16;
/**
* Network types that the socket will not bind to.
* Values will be comma separated strings such as "wifi",
* "mobile", or "mobile, wimax".
*/
public final static int RW_PROHIBITED_NETWORKS = 17;
/**
* If set to true, Link[Datagram]Socket will not send callback
* notifications to the application.
* Values should be a String set to either "true" or "false"
*/
public final static int RW_DISABLE_NOTIFICATIONS = 18;
/**
* A string containing the socket's role, as classified by the carrier.
*/
public final static int RO_CARRIER_ROLE = 19;
/**
* A string containing the QoS status granted by the network for a socket.
* It can be any of the following strings: "active", "inactive",
* "failed", or "suspended".
*/
public final static int RO_QOS_STATE = 20;
/**
* A string representing the transport protocol, Its set to {@code udp}
* for a LinkDatagramSocket and {@code tcp} for a LinkSocket.
*/
public final static int RO_TRANSPORT_PROTO_TYPE = 21;
/**
* A string representing QOS filter spec for a range of destination
* IP Addresses.
* Values should be a String of comma separated IP address in presentation format.
* If a range needs to be specified, then it can be described by CIDR notation of
* IP address/mask. <br>
* <code>For e.g. "192.168.1.1, 10.14.224.25" or "10.14.224.20/30" </code>
*/
public final static int RW_DEST_IP_ADDRESSES = 22;
/**
* A string representing QOS filter spec for a range of destination ports.
* Values should be a String of comma separated port numbers, or a
* hyphen separated numbers to indicate a range. <br> <code>For e.g.
* "18000, 180001, 18002" or "18680-18682"</code>
*/
public final static int RW_DEST_PORTS = 23;
/**
* A String representing numeric traffic class IP_TOS value that will be applied as a
* filter-spec for a QoS reservation. Valid values are between "0" and "255" inclusive.
*/
public final static int RW_FILTERSPEC_IP_TOS = 24;
}
/**
* Role informs the Link[Datagram]Socket about the data usage patterns of your
* application. Application developers should choose the role that
* best matches their application.
* <P>
* {@code Role.DEFAULT} is the default role, and is used whenever
* a role isn't set.
*/
public static final class Role {
/** No constructor */
private Role() {}
/** Default Role */
public static final String DEFAULT = "default";
/** Video Streaming at 1080p */
public static final String VIDEO_STREAMING_1080P = "video_streaming_1080p";
/** Web Browser */
public static final String WEB_BROWSER = "web_browser";
/** Video telephony applications - real time, delay sensitive */
public static final String QOS_VIDEO_TELEPHONY = "qos_video_telephony";
}
/**
* The Carrier Role is used to classify Link[Datagram]Sockets for carrier-specified usage.
* <P>
* {@code Role.DEFAULT} is the default role, and is used whenever
* a role isn't set.
*/
public static final class CarrierRole {
/** No constructor */
private CarrierRole() {}
/** Default Role */
public static final String DEFAULT = "default";
/** Delay Sensitive - prioritize latency */
public static final String DELAY_SENSITIVE = "delay_sensitive";
/** High Throughput - prioritize throughput */
public static final String HIGH_THROUGHPUT = "high_throughput";
/** Short Lived sockets that will only be open for a few seconds at most */
public static final String SHORT_LIVED = "short_lived";
/** Bulk down load - low priority download */
public static final String BULK_DOWNLOAD = "bulk_download";
/** Bulk upload - low priority upload */
public static final String BULK_UPLOAD = "bulk_upload";
}
/**
* Constructor
*/
public LinkCapabilities() {
mCapabilities = new HashMap<Integer, String>();
}
/**
* Copy constructor.
*
* @param source
*/
public LinkCapabilities(LinkCapabilities source) {
if (source != null) {
mCapabilities = new HashMap<Integer, String>(source.mCapabilities);
} else {
mCapabilities = new HashMap<Integer, String>();
}
}
/**
* Create the {@code LinkCapabilities} with values depending on role type.
* @param applicationRole a {@link LinkCapabilities.Role}
* @return the {@code LinkCapabilities} associated with the applicationRole, empty if none
* @throws IllegalArgumentException if LinkCapabilities does not recognize the role
*/
public static LinkCapabilities createNeeds(String applicationRole) {
if (DBG) log("createNeeds(applicationRole) EX");
LinkCapabilities cap = new LinkCapabilities();
cap.put(Key.RW_ROLE, applicationRole);
// Map specific application roles to their respective needs
if (applicationRole == Role.VIDEO_STREAMING_1080P) {
cap.mCapabilities.put(Key.RO_CARRIER_ROLE, CarrierRole.HIGH_THROUGHPUT);
cap.mCapabilities.put(Key.RW_REQUIRED_FWD_BW, "2500"); // Require 2.5Mbps bandwidth
cap.mCapabilities.put(Key.RW_MAX_ALLOWED_FWD_LATENCY, "500"); // 500ms latency
} else if (applicationRole == Role.WEB_BROWSER) {
cap.mCapabilities.put(Key.RO_CARRIER_ROLE, CarrierRole.SHORT_LIVED);
} else {
cap.mCapabilities.put(Key.RO_CARRIER_ROLE, CarrierRole.DEFAULT);
}
return cap;
}
/**
* Remove all capabilities
*/
public void clear() {
mCapabilities.clear();
}
/**
* Returns whether this map is empty.
*/
public boolean isEmpty() {
return mCapabilities.isEmpty();
}
/**
* Returns the number of elements in this map.
*
* @return the number of elements in this map.
*/
public int size() {
return mCapabilities.size();
}
/**
* Returns the value of the capability string with the specified key.
*
* @param key a {@link LinkCapabilities.Key}
* @return the value of the capability string with the specified key,
* or {@code null} if no mapping for the specified key is found.
*/
public String get(int key) {
return mCapabilities.get(key);
}
/**
* Store the key/value capability pair.
*
* @param key a {@link LinkCapabilities.Key}
* @param value
* @throws IllegalArgumentException if LinkCapabilities does not recognize the key:value pair
*/
public void put(int key, String value) {
// check to make sure input is valid, otherwise ignore
if (validRWKeyValuePair(key, value) == false) {
Log.d(TAG, keyName(key) + ":\"" + value
+ "\" is an invalid key:\"value\" pair, rejecting.");
throw new IllegalArgumentException("This version of the LinkCapabilities API" +
"does not support the " + keyName(key) + ":\"" + value + "\" pair.");
}
mCapabilities.put(key, value);
}
/**
* Returns whether this map contains the specified key.
*
* @param key the {@link LinkCapabilities.Key} to search for.
* @return {@code true} if this map contains the specified key,
* {@code false} otherwise.
*/
public boolean containsKey(int key) {
return mCapabilities.containsKey(key);
}
/**
* Returns whether this map contains the specified value.
*
* @param value to search for.
* @return {@code true} if this map contains the specified value,
* {@code false} otherwise.
*/
public boolean containsValue(String value) {
return mCapabilities.containsValue(value);
}
/**
* Returns a set containing all of the mappings in this map. Each mapping is
* an instance of {@link java.util.Map.Entry}. As the set is backed by this map,
* changes in one will be reflected in the other.
*
* @return a set of the mappings.
*/
public Set<Entry<Integer, String>> entrySet() {
return mCapabilities.entrySet();
}
/**
* @return the set of the keys.
*/
public Set<Integer> keySet() {
return mCapabilities.keySet();
}
/**
* @return the set of values
*/
public Collection<String> values() {
return mCapabilities.values();
}
/**
* Implement the Parcelable interface
* @hide
*/
public int describeContents() {
return 0;
}
/**
* Convert to string for debugging
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean firstTime = true;
for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
if (firstTime) {
firstTime = false;
} else {
sb.append(", ");
}
sb.append(keyName(entry.getKey()));
sb.append(":\"");
sb.append(entry.getValue());
sb.append("\"");
}
sb.append("}");
return sb.toString();
}
/**
* Implement the Parcelable interface.
* @hide
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mCapabilities.size());
for (Entry<Integer, String> entry : mCapabilities.entrySet()) {
dest.writeInt(entry.getKey().intValue());
dest.writeString(entry.getValue());
}
}
/**
* Implement the Parcelable interface.
* @hide
*/
public static final Creator<LinkCapabilities> CREATOR =
new Creator<LinkCapabilities>() {
public LinkCapabilities createFromParcel(Parcel in) {
LinkCapabilities capabilities = new LinkCapabilities();
int size = in.readInt();
while (size-- != 0) {
int key = in.readInt();
String value = in.readString();
capabilities.mCapabilities.put(key, value);
}
return capabilities;
}
public LinkCapabilities[] newArray(int size) {
return new LinkCapabilities[size];
}
};
/**
* Debug logging
*/
protected static void log(String s) {
Log.d(TAG, s);
}
/*
* Check for a value R/W key:value pair. Method tries to return as soon as
* possible
*/
protected static boolean validRWKeyValuePair(int key, String value) {
switch (key) {
case Key.RW_ROLE:
// make sure role matches a field in class Role
Class<Role> c = Role.class;
Field roleFields[] = c.getFields();
for (Field f : roleFields) {
try {
if (value == f.get(null)) return true;
} catch (IllegalArgumentException e) {
// should never see this exception since we are
// accessing a static field
} catch (IllegalAccessException e) {
// should never see this exception because this method
// should always have access to class Role
}
}
return false;
case Key.RW_DESIRED_FWD_BW:
case Key.RW_REQUIRED_FWD_BW:
case Key.RW_DESIRED_REV_BW:
case Key.RW_REQUIRED_REV_BW:
case Key.RW_MAX_ALLOWED_FWD_LATENCY:
case Key.RW_MAX_ALLOWED_REV_LATENCY:
int testValue;
try {
testValue = Integer.parseInt(value);
} catch (NumberFormatException ex) {
return false; // not a valid integer
}
if (testValue < 0) return false; // values less than zero are invalid
return true;
case Key.RW_ALLOWED_NETWORKS:
case Key.RW_PROHIBITED_NETWORKS:
// TODO: implement checks for valid network names
return true;
case Key.RW_DISABLE_NOTIFICATIONS:
return true; // string->boolean is always successful
case Key.RO_TRANSPORT_PROTO_TYPE:
value.toLowerCase();
if (value.equals("udp") || value.equals("tcp")) return true;
return false;
case Key.RW_DEST_IP_ADDRESSES: //TODO validate arguments
case Key.RW_DEST_PORTS: //TODO validate arguments
case Key.RW_FILTERSPEC_IP_TOS: //TODO validate arguments
return true;
}
// if we made it this far, key is not a valid RW key.
return false;
}
/**
* convert a Key integer to a String name
*/
protected static String keyName(int key) {
Class<Key> c = Key.class;
Field keyFields[] = c.getFields();
for (Field f : keyFields) {
try {
if (key == f.getInt(null)) return f.getName();
} catch (IllegalArgumentException e) {
// should never see this exception since we are
// accessing a static field
} catch (IllegalAccessException e) {
// should never see this exception because this method
// should always have access to class Role
}
}
return "UNKNOWN_KEY";
}
}
@@ -0,0 +1,759 @@
/*
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.content.Context;
import android.net.LinkCapabilities.Role;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImplFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* An extension of Java datagram sockets. It provides datagram socket for both endpoints of a connection
* with a way to manage wireless connections and needs for socket[s].
* <p>
* Example Code: <br>
* <code>
* LinkDatagramSocket sock = new LinkDatagramSocket(instanceOfLinkSocketNotifier); <br>
* LinkCapabilities needs = LinkCapabilities.createNeeds(LinkCapabilities.Role.CONVERSATIONAL);
* <p>
* //create desired needs <br>
* needs.put(LinkCapabilities.Key.RW_DESIRED_FWD_BW, "1000"); <br>
* needs.put (LinkCapabilities.Key.RW_DEST_IP_ADDRESSES, "192.168.20.85" );<br>
* needs.put (LinkCapabilities.Key.RW_DEST_PORTS, "16884, 16886" ); <p>
* DatagramPacket pkt = new DatagramPacket(byte[] data, int length); <br>
* sock.send(pkt); <br>
* sock.close(); <br>
* </code>
*
* @see LinkCapabilities
* @see LinkSocketNotifier
* @see DatagramSocket
* @hide
*/
public class LinkDatagramSocket extends DatagramSocket {
// Public error codes
/** Link Failure: Unknown failure */
public static final int ERROR_UNKNOWN = 0;
/** Link Failure: All networks are down */
public static final int ERROR_ALL_NETWORKS_DOWN = 1;
/** Link Failure: Networks are available, but none meet requirements */
public static final int ERROR_NETWORKS_FAIL_REQUIREMENTS = 2;
// Debugging
private final static String TAG = "LinkDatagramSocket";
private final static boolean DBG = true;
// Flags
private boolean isWaitingForResponse = false; // waiting for ConnectivityServices?
private final static int NOT_SET = -1;
// Members
protected int mNetSelectTimeout = 30000; // network selection timeout
private IConnectivityManager mService = null; // handle to ConnectivityService
private LinkCapabilities mNeededCapabilities = null; // requested capabilities
private LinkProperties mProperties = null; // socket properties
private LinkSocketNotifier mNotifier = null; // callback to application
private String mHostname = null; // destination hostname
private int mBindPort = 0; // local bind port
private int mId = NOT_SET; // unique ID
private MessageHandler mMsgHandler = new MessageHandler(); // messages from ConnectivityServices
private Handler mHandler = null; // message handler
private MessageLoop mMsgLoop = new MessageLoop(); // message loop
/**
* Default Constructor
*
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public LinkDatagramSocket() throws SocketException {
super(false);
if (DBG) Log.v(TAG, "LinkDatagramSocket() EX");
constructor(null, 0);
}
/**
* Creates a new unconnected link datagram socket.
*
* @param notifier a reference to a class that implements
* {@code LinkSocketNotifier}
*/
public LinkDatagramSocket(LinkSocketNotifier notifier) throws SocketException {
super(false);
if (DBG) Log.v(TAG, "LinkDatagramSocket(notifier) EX");
constructor(notifier, 0);
}
/**
* Constructs a link datagram socket which is bound to the specific port
* {@code aPort} on the localhost. Valid values for {@code aPort} are
* between 0 and 65535 inclusive. The bind will be delayed until link
* selection.
*
* @param aPort
* the port to bind on the localhost.
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public LinkDatagramSocket(int aPort) throws SocketException {
super(false);
if (DBG) Log.v(TAG, "LinkDatagramSocket(aPort) EX");
constructor(null, aPort);
}
/**
* Constructs a link datagram socket which is bound to the specific port
* {@code aPort} on the localhost. Valid values for {@code aPort} are
* between 0 and 65535 inclusive. The bind will be delayed until link
* selection.
*
* @param aPort
* the port to bind on the localhost.
*
* @param notifier a reference to a class that implements
* {@code LinkSocketNotifier}
*
* @throws SocketException
* if an error occurs while creating or binding the socket.
*/
public LinkDatagramSocket(int aPort, LinkSocketNotifier notifier) throws SocketException {
super(false);
if (DBG) Log.v(TAG, "LinkDatagramSocket(aPort, notifier) EX");
constructor(notifier, aPort);
}
/**
* Creates a new unconnected socket with the same notifier and needs map as
* the source LinkDatagramSocket.
*
* @param source
*/
public LinkDatagramSocket(LinkDatagramSocket source) throws SocketException {
super(false);
if (DBG) Log.v(TAG, "LinkDatagramSocket(source) EX");
constructor(source.mNotifier, 0);
setNeededCapabilities(source.getNeededCapabilities());
}
/**
* Returns properties of the link this socket is bound to.
*
* @return the {@code LinkProperties} for the socket
*/
public LinkProperties getLinkProperties() {
if (DBG) Log.v(TAG, "getLinkProperties() EX");
return new LinkProperties(mProperties);
}
/**
* Set the {@code LinkCapabilities} needed for this socket. If the socket is
* already connected the request is ignored and {@code false} will be
* returned. A needs map can be created via the {@code createNeeds} static
* method.
*
* @param needs the needs of the socket
* @return {@code true} if needs are successfully set, {@code false}
* otherwise
*/
public boolean setNeededCapabilities(LinkCapabilities needs) {
if (DBG) Log.v(TAG, "setNeededCapabilities(needs) EX");
// if mProperties is set, it is too late to set needs
if (mProperties != null) return false;
mNeededCapabilities = needs;
mNeededCapabilities.put(LinkCapabilities.Key.RO_TRANSPORT_PROTO_TYPE,"udp");
if (mNotifier == null) {
// if mNotifier is null, then we cannot send notifications so we
// might as well disable them
mNeededCapabilities.put(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS, "true");
}
return true;
}
/**
* @return the LinkCapabilites set by setNeededCapabilities, empty if none
* has been set
*/
public LinkCapabilities getNeededCapabilities() {
if (DBG) Log.v(TAG, "getNeededCapabilities() EX");
return new LinkCapabilities(mNeededCapabilities);
}
/**
* @return a LinkCapabilities object containing the READ ONLY capabilities
* of the LinkDatagramSocket
*/
public LinkCapabilities getCapabilities() {
if (DBG) Log.v(TAG, "getCapabilities() EX");
LinkCapabilities cap = null;
try {
// place all capabilities in an int array
final int[] keys = new int[] {
LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY,
LinkCapabilities.Key.RO_CURRENT_REV_LATENCY,
LinkCapabilities.Key.RO_BOUND_INTERFACE,
LinkCapabilities.Key.RO_NETWORK_TYPE,
LinkCapabilities.Key.RO_PHYSICAL_INTERFACE,
LinkCapabilities.Key.RO_CARRIER_ROLE,
LinkCapabilities.Key.RO_QOS_STATE,
LinkCapabilities.Key.RO_TRANSPORT_PROTO_TYPE
};
cap = mService.requestCapabilities(mId, keys);
} catch (RemoteException ex) {
Log.d(TAG, "LinkSocket was unable to get capabilities from ConnectivityService");
// should not return a null reference
cap = new LinkCapabilities();
}
return cap;
}
/**
* Returns this LinkDatagramSockets set of capabilities, filtered according to the
* given {@code Set}. Capabilities in the Set but not available from the
* link will not be reported in the results. Capabilities of the link but
* not listed in the Set will also not be reported in the results.
*
* @param capability_keys {@code Set} of capabilities requested
* @return the filtered {@code LinkCapabilities} of this LinkDatagramSocket, may be
* empty
*/
public LinkCapabilities getCapabilities(Set<Integer> capability_keys) {
if (DBG) Log.v(TAG, "getCapabilities(capability_keys) EX");
LinkCapabilities cap = null;
int[] keys = new int[capability_keys.size()];
// convert capability_keys into an int array
Iterator<Integer> it = capability_keys.iterator();
for (int i = 0; it.hasNext(); i++) keys[i] = it.next();
// send the request
try {
cap = mService.requestCapabilities(mId, keys);
} catch (RemoteException ex) {
Log.d(TAG, "LinkDatagramSocket was unable to get capabilities from ConnectivityService");
}
return cap;
}
/**
* Provide the set of capabilities the application is interested in tracking
* for this LinkDatagramSocket.
*
* @param capabilities a {@code Set} of capabilities to track
*/
public void setTrackedCapabilities(Set<Integer> capabilities) {
if (DBG) Log.v(TAG, "setTrackedCapabilities(capabilities) EX");
// This feature is not implemented yet.
}
/**
* This method is not available in this release.
*
* @return the {@code LinkCapabilities} that are tracked, empty if
* none has been set.
*/
public Set<Integer> getTrackedCapabilities() {
if (DBG) Log.v(TAG, "getTrackedCapabilities() EX");
return new HashSet<Integer>();
}
/**
* @deprecated LinkDatagramSocket will automatically pick the optimum interface to
* bind to
* @param localAddr the specific address and port on the local machine to
* bind to
* @throws IOException always as this method is deprecated for LinkDatagramSocket
*/
@Override
@Deprecated
public void bind(SocketAddress localAddr) throws UnsupportedOperationException {
if (DBG) Log.v(TAG, "bind(localAddr) EX");
throw new UnsupportedOperationException(
"LinkDatagamSocket will automatically bind to the optimum interface.");
}
/**
* Connects this socket to the given remote host address and port specified
* by dstName and dstPort. Network selection happens during connect and may
* take 30 seconds.
*
* @param dstName the address of the remote host to connect to
* @param dstPort the port to connect to on the remote host
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting or if required needs are set but not granted
* by the network.
*/
public void connect(String dstName, int dstPort) throws UnknownHostException, IOException {
if (DBG) Log.v(TAG, "connect(dstName, dstPort) EX");
// save the addresses for future use
mHostname = dstName;
performNetworkSelection(mNetSelectTimeout);
super.connect(InetAddress.getByName(dstName), dstPort);
}
/**
* Connects this socket to the same remote host address and port as the
* source LinkDatagramSocket.
*
* @param source the LinkDatagramSocket from which to get the destination
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting
*/
public void connect(LinkDatagramSocket source) throws UnknownHostException, IOException {
if (DBG) Log.v(TAG, "connect(source) EX");
connect(source.getHostname(), source.getPort());
}
/**
* Connects this socket to the given remote host address and port specified
* by the SocketAddress.
*
* @deprecated Use {@link LinkDatagramSocket#connect(String, int)} instead. Using
* this method will result in an IllegalArgumentException.
* @param remoteAddr the address and port of the remote host to connect to.
* @throws IllegalArgumentException always
*/
@Override
@Deprecated
public void connect(SocketAddress remoteAddr) throws IllegalArgumentException {
if (DBG) Log.v(TAG, "connect(remoteAddr) EX");
throw new IllegalArgumentException(
"This method is deprecated, use connect(String, int) instead.");
}
/**
* Receives a packet from this socket and stores it in the argument pack.
* All fields of pack must be set according to the data received. If the
* received data is longer than the packet buffer size it is truncated. This
* method blocks until a packet is received or a timeout has expired. If a
* security manager exists, its checkAccept method determines whether or not
* a packet is discarded. Any packets from unacceptable origins are silently
* discarded.
*
* Note: Network selection happens during send if the socket is
* unconnected and may take up to 30 seconds.
*/
@Override
public synchronized void receive(DatagramPacket pack) throws IOException {
if (DBG) Log.v(TAG, "receive(pack) EX");
if (mProperties == null) performNetworkSelection(mNetSelectTimeout);
super.receive(pack);
}
/**
* Sends a packet over this socket. The packet must satisfy the security
* policy before it may be sent. If a security manager is installed, this
* method checks whether it is allowed to send this packet to the specified
* address.
*
* Note: Network selection happens during send if the socket is
* unconnected and may take up to 30 seconds.
*
* @param pack
* the {@code DatagramPacket} which has to be sent.
* @throws IOException
* if an error occurs while sending the packet or if required
* needs are set but not granted by the network.
*/
@Override
public void send(DatagramPacket pack) throws IOException {
if (DBG) Log.v(TAG, "send(pack) EX");
if (mProperties == null) performNetworkSelection(mNetSelectTimeout);
super.send(pack);
}
/**
* Closes the LinkDatagramSocket and tears down associated QoS reservations
*/
@Override
public void close() {
if (DBG) Log.v(TAG, "close() EX");
mMsgLoop.quit(); // disable callbacks
releaseLink();
super.close();
}
/**
* Sets the socket implementation factory. This may only be invoked once
* over the lifetime of the application. This factory is used to create
* a new link datagram socket implementation. If a security manager is set its
* method {@code checkSetFactory()} is called to check if the operation is
* allowed. An {@code UnsupportedOperationException} is always thrown for this operation
*
* @deprecated PlainDatagramSocketImpl is always used
*
* @param fac
* the socket factory to use.
* @throws UnsupportedOperationException always
*/
@Deprecated
public static synchronized void setDatagramSocketImplFactory(DatagramSocketImplFactory fac)
throws IOException, UnsupportedOperationException {
if (DBG) Log.v(TAG, "setDatagramSocketImplFactory(fac) EX Deprecated");
throw new UnsupportedOperationException("This method is deprecated, "
+ "LinkDatagramSocket will automatically manage which factory to use");
}
/**
* Gets the host name of the target host this socket is connected to.
*
* @return the host name of the connected target host, or {@code null} if
* this socket is not yet connected.
*/
public String getHostname() {
return mHostname;
}
/**
* Gets the port number of the target host this socket is connected to.
*
* @return the port number of the connected target host or 0 if this socket
* is not yet connected.
*/
public int getPort() {
return super.getPort();
}
@Override
public String toString() {
if (this.isConnected() == false) {
if (mId == NOT_SET) {
return "LinkDatagramSocket id:none unconnected";
} else {
return "LinkDatagramSocket id:" + mId + " unconnected";
}
} else {
return "LinkDatagramSocket id:" + mId + " addr:" + super.getInetAddress()
+ " port:" + super.getPort() + " local_port:" + super.getLocalPort();
}
}
protected synchronized void performNetworkSelection(int timeout) throws IOException {
if (DBG) Log.v(TAG, "performNetworkSelection(timeout) EX");
if (mProperties != null) return; // cannot perform network selection twice
/*
* Currently RW_ALLOWED_NETWORKS and RW_PROHIBITED_NETWORKS are not
* implemented. If either of these keys are in use, throw an
* IOException.
*/
if (mNeededCapabilities.containsKey(LinkCapabilities.Key.RW_ALLOWED_NETWORKS)
|| mNeededCapabilities.containsKey(LinkCapabilities.Key.RW_PROHIBITED_NETWORKS)) {
throw new IOException("RW_ALLOWED_NETWORKS and RW_PROHIBITED_NETWORKS" +
" are not supported at this time");
}
// save the current time for timeouts
Calendar start = Calendar.getInstance();
// make sure message processing thread is ready before we try to connect
while (mHandler == null) Thread.yield();
/*
* Steps:
* 1. request a network link
* 2. figure out which address to bind to
* 3. bind to the given address
*/
// request link
synchronized (this) {
if (mId == NOT_SET) {
try {
if (DBG) Log.v(TAG, "sending requestLink()");
isWaitingForResponse = true;
mId = mService.requestLink(mNeededCapabilities, mMsgHandler);
while (isWaitingForResponse) {
if (DBG) Log.v(TAG, "Blocking: waiting for response");
wait(timeout);
/*
* if 'timeout' time passes, wait() will stop
* blocking just as if it was interrupted or
* received a notification, and the while loop will
* begin again. We need track time, and throw and
* exception if appropriate.
*/
timeout -= (int) (Calendar.getInstance().getTimeInMillis()
- start.getTimeInMillis());
if (timeout <= 0) {
releaseLink();
throw new IOException("Socket timed out during link acquisition.");
}
if (DBG) Log.v(TAG, "Blocking: received notification or timeout");
}
if (DBG) Log.v(TAG, "Blocking: done");
} catch (InterruptedException ex) {
Log.d(TAG, "ConnectivityService failed to respond to request.");
releaseLink();
} catch (RemoteException ex) {
Log.w(TAG, "LinkSocket was unable to acquire a new network link. " + ex);
releaseLink();
}
}
}
// if mProperties is still null, we couldn't get a network
if (mProperties == null) {
releaseLink();
throw new IOException("Unable to find a network that meets requirements.");
}
// get first address from mProperties
Collection<InetAddress> addresses = mProperties.getAddresses();
if (addresses == null || addresses.isEmpty()) {
releaseLink();
throw new IOException("No valid address to bind to");
}
InetAddress bindAddress = null;
for (InetAddress address : addresses) {
bindAddress = address;
break;
}
// bind
if (DBG) Log.v(TAG, "attempting to bind: " + bindAddress + " port: " + mBindPort);
try {
super.bind(new InetSocketAddress(bindAddress, mBindPort));
} catch (SocketException se) {
releaseLink();
throw new IOException("Desired source port is already consumed: " + mBindPort);
}
mBindPort = getLocalPort();
if (DBG) Log.v(TAG, "bind successful: " + getLocalSocketAddress() + ":" + mBindPort);
//Do QOS request for all sockets. Check return value to validate if
//socket can get QoS service. Ignoring for now.
try {
mService.requestQoS(mId, mBindPort, bindAddress.getHostAddress());
} catch (RemoteException re) {
if (DBG) Log.w(TAG,"requestQoS experienced remote exception: " + re);
}
}
/*
* Handles callbacks from ConnectivityServices
*/
private class MessageHandler extends ILinkSocketMessageHandler.Stub {
// Flags
private final static int ON_LINK_AVAIL = 0;
private final static int ON_GET_LINK_FAILURE = 1;
private final static int ON_BETTER_LINK_AVAIL = 2;
private final static int ON_LINK_LOST = 3;
private final static int ON_CAPABILITIES_CHANGED = 4;
public void onLinkAvail(LinkProperties properties) {
if (DBG) Log.v(TAG, "CallbackHandler.onLinkAvail(properties) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_LINK_AVAIL, properties));
}
public void onGetLinkFailure(int reason) {
if (DBG) Log.v(TAG, "CallbackHandler.onGetLinkFailure(reason) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_GET_LINK_FAILURE, reason));
}
public void onBetterLinkAvail() {
if (DBG) Log.v(TAG, "CallbackHandler.onBetterLinkAvail(properties) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_BETTER_LINK_AVAIL));
}
public void onLinkLost() {
if (DBG) Log.v(TAG, "CallbackHandler.onLinkLost() EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_LINK_LOST));
}
public void onCapabilitiesChanged(LinkCapabilities changedCapabilities) {
if (DBG) Log.v(TAG, "CallbackHandler.onCapabilitiesChanged(changedCapabilities) EX");
mHandler.sendMessage(
mHandler.obtainMessage(ON_CAPABILITIES_CHANGED, changedCapabilities));
}
}
private void constructor(LinkSocketNotifier notifier, int bindPort) {
if (DBG) Log.v(TAG, "constructor(notifier) EX");
mMsgLoop.start(); // start up message processing thread
mNotifier = notifier;
mBindPort = bindPort;
setNeededCapabilities(LinkCapabilities.createNeeds(Role.DEFAULT));
mNeededCapabilities.put(LinkCapabilities.Key.RO_TRANSPORT_PROTO_TYPE,"udp");
IBinder binder = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
mService = IConnectivityManager.Stub.asInterface(binder);
}
private void releaseLink() {
if (mId == NOT_SET) return; // nothing to release
if (DBG) Log.v(TAG, "releasing link");
try {
mService.releaseLink(mId);
} catch (RemoteException ex) {
Log.w(TAG, "LinkDatagramSocket was unable relinquish the current network link. " + ex);
}
mId = NOT_SET;
}
/*
* Handle messages from CallbackHandler
*/
private class MessageLoop extends Thread {
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
if (DBG) Log.v(TAG, "handleMessage(msg) EX");
switch (msg.what) {
case MessageHandler.ON_LINK_AVAIL:
callbackOnLinkAvail((LinkProperties) msg.obj);
break;
case MessageHandler.ON_GET_LINK_FAILURE:
callbackOnGetLinkFailure((Integer) msg.obj);
break;
case MessageHandler.ON_BETTER_LINK_AVAIL:
callbackOnBetterLinkAvail();
break;
case MessageHandler.ON_LINK_LOST:
callbackOnLinkLost();
break;
case MessageHandler.ON_CAPABILITIES_CHANGED:
callbackOnCapabilitiesChanged((LinkCapabilities) msg.obj);
break;
default:
Log.d(TAG, "LinkDatagramSocket received an unknown message type");
}
}
};
Looper.loop();
}
public void quit() {
if (mHandler != null) mHandler.getLooper().quit();
}
}
private void callbackOnLinkAvail(LinkProperties properties) {
if (DBG) Log.v(TAG, "onLinkAvail(properties) EX");
if (mProperties != null) {
/*
* this is an unexpected onLinkAvail(), it probably should be a
* onBetterLinkAvail() so we're going to call that
*
* remove once CND is updated to the new architecture
*/
callbackOnBetterLinkAvail();
return;
}
// ConnectivityService found a link!
mProperties = properties;
// stop blocking
isWaitingForResponse = false;
synchronized (this) {
notifyAll();
}
}
private void callbackOnGetLinkFailure(int reason) {
if (DBG) Log.v(TAG, "onGetLinkFailure(reason) EX");
if (mProperties != null) {
/*
* this is an unexpected onGetLinkFailure, it probably should just
* be ignored
*
* remove once CND is updated to the new architecture
*/
return;
}
// implied releaseLink()
mId = NOT_SET;
// stop blocking
isWaitingForResponse = false;
synchronized (this) {
notifyAll();
}
}
private void callbackOnBetterLinkAvail() {
if (DBG) Log.v(TAG, "onBetterLinkAvail() EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onBetterLinkAvailable(this);
}
private void callbackOnLinkLost() {
if (DBG) Log.v(TAG, "onLinkLost() EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onLinkLost(this);
}
private void callbackOnCapabilitiesChanged(LinkCapabilities changedCapabilities) {
if (DBG) Log.v(TAG, "onCapabilitiesChanged(changedCapabilities) EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onCapabilitiesChanged(this, changedCapabilities);
}
}
+31
View File
@@ -0,0 +1,31 @@
/* Copyright (c) 2009, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
parcelable LinkInfo;
+202
View File
@@ -0,0 +1,202 @@
/* Copyright (c) 2009,2011 The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
import android.os.Parcelable;
import android.os.Parcel;
import java.net.InetAddress;
/** {@hide}
* This class provides information about a link. User can query this object
* to get supported information about the link.
*/
public class LinkInfo implements Parcelable {
/** IP address of the network link. Currently android supports only v4
*/
private InetAddress ipAddr;
/** Estimated forward-link bandwidth in kbps.
*/
private int availFwLinkBw;
/** Estimated reverse-link bandwidth in kbps.
*/
private int availRevLinkBw;
/** The link identifier..
*/
private int nwId;
public static final int INF_UNSPECIFIED = -1;
public static final int STATUS_FAILURE = 0;
public static final int STATUS_SUCCESS = 1;
/** Default constructor
*/
public LinkInfo() {
ipAddr = null;
availFwLinkBw = INF_UNSPECIFIED;
availRevLinkBw = INF_UNSPECIFIED;
nwId = INF_UNSPECIFIED;
}
/** parametric constructor
*/
public LinkInfo(String ip,
int fwLinkBw,
int revLinkBw,
int netId) {
try {
ipAddr = NetworkUtils.ipAddrStringToInetAddress(ip);
} catch (java.net.UnknownHostException e) {
}
availFwLinkBw = fwLinkBw;
availRevLinkBw = revLinkBw;
nwId = netId;
}
/* needed for Creator */
private LinkInfo(InetAddress ip,
int fwLinkBw,
int revLinkBw,
int netId) {
ipAddr = ip;
availFwLinkBw = fwLinkBw;
availRevLinkBw = revLinkBw;
nwId = netId;
}
/** @hide
* provides the IP address of the interface that this LinkInfo object
* corresponds to.
* @return IP address of the interface
*/
public InetAddress getIpAddr() {
return ipAddr;
}
/** @hide
* provides the estimated forward-link bandwidth in kbps available on
* the interface that this LinkInfo object corresponds to.
* @return estimated forward-link bandwidth in kbps of the interface.
*/
public int getAvailFwLinkBw() {
return availFwLinkBw;
}
/** @hide
* provides the estimated reverse-link bandwidth in kbps available on
* the interface that this LinkInfo object corresponds to.
* @return estimated reverse-link bandwidth in kbps of the interface.
*/
public int getAvailRevLinkBw() {
return availRevLinkBw;
}
/** @hide
* provides the network id of the interface that this LinkInfo object
* corresponds to.
* @return network id of the interface.
*/
public int getNwId() {
return nwId;
}
/**
* Implement the Parcelable interface
* @hide
*/
public int describeContents() {
return 0;
}
/**
* Implement the Parcelable interface.
* @hide
*/
public void writeToParcel(Parcel dest, int flags) {
byte ip[] = ipAddr.getAddress();
dest.writeInt(ip.length);
dest.writeByteArray(ip);
dest.writeInt(availFwLinkBw);
dest.writeInt(availRevLinkBw);
dest.writeInt(nwId);
}
/**
* Implement the Parcelable interface.
* @hide
*/
public void readFromParcel(Parcel in) {
byte ip[] = new byte[in.readInt()];
in.readByteArray(ip);
try {
ipAddr = InetAddress.getByAddress(ip);
} catch (java.net.UnknownHostException e) {
ipAddr = null;
}
availFwLinkBw = in.readInt();
availRevLinkBw = in.readInt();
nwId = in.readInt();
}
/**
* Implement the Parcelable interface.
* @hide
*/
public static final Creator<LinkInfo> CREATOR =
new Creator<LinkInfo>() {
public LinkInfo createFromParcel(Parcel in) {
byte ip[] = new byte[in.readInt()];
in.readByteArray(ip);
InetAddress ipAddr;
try {
ipAddr = InetAddress.getByAddress(ip);
} catch (java.net.UnknownHostException e) {
return null;
}
int availFwLinkBw = in.readInt();
int availRevLinkBw = in.readInt();
int nwId = in.readInt();
LinkInfo info = new LinkInfo(ipAddr,
availFwLinkBw,
availRevLinkBw,
nwId);
return info;
}
public LinkInfo[] newArray(int size) {
return new LinkInfo[size];
}
};
}
@@ -0,0 +1,75 @@
/* Copyright (c) 2009, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package android.net;
/** {@hide}
* This is an interface that the applications need to implement. An app
* This interface will be used by CnE to notify apps of various events
* related to connectivity.
*/
public interface LinkNotifier {
public static int FAILURE_GENERAL = -1;
public static int FAILURE_NO_LINKS = -2;
/** {@hide}
* This function notifies the availability of the Link for use. It can
* get called due to one of the following reasons:
* 1. getLink was called and connection was successful
* 2. reportLinkStatisfaction was called with dissatisfaction
* 3. if a previous connection is lost, after the onConnectionLost call
* 4. switchLink called
*/
public void onLinkAvail(LinkInfo info);
/** {@hide}
* This function will be called if the getLink call by the app has
* failed to provide any link
*/
public void onGetLinkFailure(int reason);
/** {@hide}
* This function notifies the availability of a better link. It can get
* called due to one of the following reasons:
* 1. reportLinkStatisfaction was called with isNotifyBetterCon true
* 2. rejectSwitch was called with isNotifyBetterCon true
* 3. switchLink was called with isNotifyBetterCon true
*/
public void onBetterLinkAvail(LinkInfo info);
/** {@hide}
* This function notifies the connection loss. It can get called due to
* one of the following reasons:
* 1. previously established connection was lost
* expect an onLinkAvail after this for any new connections
* that can be used.
*/
public void onLinkLost(LinkInfo info);
}
@@ -0,0 +1,22 @@
/*
**
** Copyright (C) 2010 The Android Open Source Project
** Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
**
** 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.net;
parcelable LinkProperties;
@@ -0,0 +1,185 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* Describes the properties of a network link.
* TODO - consider adding optional fields like Apn and ApnType
* @hide
*/
public class LinkProperties implements Parcelable {
private NetworkInterface mIface;
private Collection<InetAddress> mAddresses;
private Collection<InetAddress> mDnses;
private InetAddress mGateway;
public LinkProperties() {
clear();
}
// copy constructor instead of clone
public LinkProperties(LinkProperties source) {
if (source != null) {
mIface = source.getInterface();
mAddresses = source.getAddresses();
mDnses = source.getDnses();
mGateway = source.getGateway();
} else {
clear();
}
}
public void setInterface(NetworkInterface iface) {
mIface = iface;
}
public NetworkInterface getInterface() {
return mIface;
}
public String getInterfaceName() {
return (mIface == null ? null : mIface.getName());
}
public void addAddress(InetAddress address) {
mAddresses.add(address);
}
public Collection<InetAddress> getAddresses() {
return Collections.unmodifiableCollection(mAddresses);
}
public void addDns(InetAddress dns) {
mDnses.add(dns);
}
public Collection<InetAddress> getDnses() {
return Collections.unmodifiableCollection(mDnses);
}
public void setGateway(InetAddress gateway) {
mGateway = gateway;
}
public InetAddress getGateway() {
return mGateway;
}
public void clear() {
mIface = null;
mAddresses = new ArrayList<InetAddress>();
mDnses = new ArrayList<InetAddress>();
mGateway = null;
}
/**
* Implement the Parcelable interface
* @hide
*/
public int describeContents() {
return 0;
}
@Override
public String toString() {
String ifaceName = (mIface == null ? "" : "InterfaceName: " + mIface.getName() + " ");
String ip = "IpAddresses: [";
for (InetAddress addr : mAddresses) ip += addr.getHostAddress() + ",";
ip += "] ";
String dns = "DnsAddresses: [";
for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
dns += "] ";
String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.getHostAddress() + " ");
return ifaceName + ip + gateway + dns;
}
/**
* Implement the Parcelable interface.
* @hide
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getInterfaceName());
dest.writeInt(mAddresses.size());
//TODO: explore an easy alternative to preserve hostname
// without doing a lookup
for(InetAddress a : mAddresses) {
dest.writeByteArray(a.getAddress());
}
dest.writeInt(mDnses.size());
for(InetAddress d : mDnses) {
dest.writeByteArray(d.getAddress());
}
if (mGateway != null) {
dest.writeByte((byte)1);
dest.writeByteArray(mGateway.getAddress());
} else {
dest.writeByte((byte)0);
}
}
/**
* Implement the Parcelable interface.
* @hide
*/
public static final Creator<LinkProperties> CREATOR =
new Creator<LinkProperties>() {
public LinkProperties createFromParcel(Parcel in) {
LinkProperties netProp = new LinkProperties();
String iface = in.readString();
if (iface != null) {
try {
netProp.setInterface(NetworkInterface.getByName(iface));
} catch (Exception e) {
return null;
}
}
int addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
try {
netProp.addAddress(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) { }
}
addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
try {
netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) { }
}
if (in.readByte() == 1) {
try {
netProp.setGateway(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) {}
}
return netProp;
}
public LinkProperties[] newArray(int size) {
return new LinkProperties[size];
}
};
}
@@ -0,0 +1,375 @@
/*
* Copyright (c) 2009-2011 The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of The Linux Foundation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package android.net;
import android.net.IConnectivityManager;
import android.os.RemoteException;
import android.net.LinkInfo;
import android.os.ServiceManager;
import android.os.IBinder;
import android.util.Log;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.Looper;
import android.os.SystemProperties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map;
/** {@hide}
* This class provides a means for applications to specify their
* requirements and request for a link. Apps can also report their satisfaction
* with the assigned link and switch links when a new link is available.
*/
public class LinkProvider {
static final String LOG_TAG = "LinkProvider";
static final boolean DBG = false;
/** {@hide}
* Default Role Id, applies to any packet data traffic pattern that
* doesn't have another role defined explicitly.
*/
public static final int ROLE_DEFAULT = 0;
/*
* role that the app wants to register.
*/
private int mRole;
/*
* link requirements of the app for the registered role.
*/
private Map<LinkRequirement, String> mLinkReqs = null;
/*
* Apps process id
*/
private int mPid;
/*
* LinkNotifier object to provide notification to the app.
*/
private LinkNotifier mLinkNotifier;
/*
* handle to connectivity service obj
*/
private IConnectivityManager mService;
private Handler mHandler;
private Looper mLooper;
private static final int ON_LINK_AVAIL = 1;
private static final int ON_BETTER_LINK_AVAIL = 2;
private static final int ON_LINK_LOST = 3;
private static final int ON_GET_LINK_FAILURE = 4;
private Lock mLock;
private Condition mHandlerAvail;
public enum LinkRequirement {
FW_LINK_BW,
REV_LINK_BW,
}
/** {@hide}
* This constructor can be used by apps to specify a role and
* optional requirements and a link notifier object to receive
* notifications.
*
* @param role
* Role that the app wants to register
* @param reqs
* Requirements of the app for that role
* @param notifier
* LinkNotifier object to provide notification to the app
*/
public LinkProvider(int role, Map<LinkRequirement, String> reqs, LinkNotifier notifier)
throws InterruptedException {
mRole = role;
mLinkReqs = reqs;
mLinkNotifier = notifier;
/* get handle to connectivity service */
IBinder b = ServiceManager.getService("connectivity");
mService = IConnectivityManager.Stub.asInterface(b);
/* check for mservice to be null and throw a exception */
if (mService == null) {
throw new IllegalStateException("mService can not be null");
}
mLock = new ReentrantLock();
mHandlerAvail = mLock.newCondition();
}
/** {@hide}
* This function will be used by apps to request a system after they
* have specified their role and/or requirements.
*
* @return {@code true}if the request has been accepted. {@code false}
* otherwise. A return value of true does NOT mean that a link is
* available for the app to use. That will delivered via the
* LinkNotifier.
*/
public boolean getLink() {
if(SystemProperties.getBoolean("persist.cne.fmc.mode", false)) {
Log.w(LOG_TAG,"getLink: FMC is enabled. This API is invalid.");
return false;
}
try {
if (mHandler == null) {
try {
init();
} catch (InterruptedException ex) {
if (DBG) Log.d(LOG_TAG, "Interrupted exception!");
return false;
}
} else {
if (DBG) Log.d(LOG_TAG, "getLink called before release is called!!");
return false;
}
ConSvcEventListener listener = (ConSvcEventListener) IConSvcEventListener.Stub
.asInterface(new ConSvcEventListener());
mPid = Binder.getCallingPid();
if (DBG) Log.d(LOG_TAG, "GetLink called with role=" + mRole + "pid=" + mPid);
return mService.getLink_LP(mRole, mLinkReqs, mPid, listener);
} catch (RemoteException e) {
if (DBG) Log.d(LOG_TAG, "ConSvc throwed remoteExcept'n on startConn call");
return false;
}
}
private void init() throws InterruptedException {
(new NotificationsThread()).start();
/* block until mHandler gets created. */
try {
mLock.lock();
if (mHandler == null) {
mHandlerAvail.await();
}
} finally {
mLock.unlock();
}
}
private void deInit() {
mLooper.quit();
mHandler = null;
}
/** {@hide}
* This function will be used by apps to report to CnE whether they
* are satisfied or dissatisfied with the link that was assigned to them.
*
* @param info
* {@code LinkInfo} about the Link assigned to the app. The app
* needs to pass back the same LinkInfo object it received via
* the {@code LinkNotifier}
* @param isSatisfied
* whether the app is satisfied with the link or not
* @param isNotifyBetterLink
* whether the app wants to be notified when another link is
* available which CnE believes is "better" for the app
* @return {@code true} if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean reportLinkSatisfaction(LinkInfo info, boolean isSatisfied,
boolean isNotifyBetterLink) {
try {
return mService.reportLinkSatisfaction_LP(mRole, mPid, info, isSatisfied,
isNotifyBetterLink);
} catch (RemoteException e) {
if (DBG) Log.d(LOG_TAG, "ConSvc throwed remoteExcept'n on reportConnSatis call");
return false;
}
}
/** {@hide}
* When a "better link available" notification is delivered to the
* app, the app has a choice on whether to switch to the new link or
* continue with the old one. The app needs to call this API if it wants to
* switch to the new link.
*
* @param info
* {@code LinkInfo} about the new link provided to the app
* @param isNotifyBetterLink
* Whether the app wants to be notified if a "better" network
* for its role is available
* @return {@code true}if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean switchLink(LinkInfo info, boolean isNotifyBetterLink) {
try {
return mService.switchLink_LP(mRole, mPid, info, isNotifyBetterLink);
} catch (RemoteException e) {
if (DBG) Log.d(LOG_TAG, "ConSvc throwed remoteExcept'n on reportConnSatis call");
return false;
}
}
/** {@hide}
* When a "better link available" notification is delivered to the
* app, the app has a choice on whether to switch to the new link or
* continue with the old one. The app needs to call this API if it wants to
* stay with the old link.
*
* @param info
* {@code LinkInfo} about the new link provided to the app
* @param isnotifyBetterLink
* whether the app wants to be notified if a "better" network
* for its role is available
* @return {@code true} if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean rejectSwitch(LinkInfo info, boolean isNotifyBetterLink) {
try {
return mService.rejectSwitch_LP(mRole, mPid, info, isNotifyBetterLink);
} catch (RemoteException e) {
if (DBG) Log.d(LOG_TAG, "ConSvc throwed remoteExcept'n on reportConnSatis call");
return false;
}
}
/** {@hide}
* This function will be used by apps to release the network
* assigned to them for a given role.
*
* @return {@code true} if the request has been accepted by Android
* framework, {@code false} otherwise.
*/
public boolean releaseLink() {
try {
boolean retVal = mService.releaseLink_LP(mRole, mPid);
deInit();
return retVal;
} catch (RemoteException e) {
if (DBG) Log.d(LOG_TAG, "ConSvc throwed remoteExcept'n on releaseLink call");
return false;
}
}
/** {@hide} */
/*
* This class has the remoted function call backs that get called when the
* ConSvc has to notify things to the app
*/
private class ConSvcEventListener extends IConSvcEventListener.Stub {
public void onLinkAvail(LinkInfo info) {
if (DBG) Log.v(LOG_TAG, "Sending OnLinkAvail with nwId=" + info.getNwId() + "to App");
Message msg;
msg = mHandler.obtainMessage(ON_LINK_AVAIL, info);
msg.setTarget(mHandler);
msg.sendToTarget();
return;
}
public void onBetterLinkAvail(LinkInfo info) {
if (DBG)
Log.v(LOG_TAG, "Sending onBetterLinkAvail with nwId=" + info.getNwId() + "to App");
Message msg;
msg = mHandler.obtainMessage(ON_BETTER_LINK_AVAIL, info);
msg.setTarget(mHandler);
msg.sendToTarget();
return;
}
public void onLinkLost(LinkInfo info) {
if (DBG) Log.v(LOG_TAG, "Sending onLinkLost with nwId=" + info.getNwId() + "to App");
Message msg;
msg = mHandler.obtainMessage(ON_LINK_LOST, info);
msg.setTarget(mHandler);
msg.sendToTarget();
return;
}
public void onGetLinkFailure(int reason) {
if (DBG) Log.v(LOG_TAG, "Sending onGetLinkFailure with reason=" + reason + "to App");
Message msg;
msg = mHandler.obtainMessage(ON_GET_LINK_FAILURE, reason);
msg.setTarget(mHandler);
msg.sendToTarget();
return;
}
};
private class NotificationsThread extends Thread {
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (DBG) Log.v(LOG_TAG, "handle Message called for msg = " + msg.what);
switch (msg.what) {
case ON_LINK_AVAIL: {
LinkInfo info = (LinkInfo) msg.obj;
if (mLinkNotifier != null) {
mLinkNotifier.onLinkAvail(info);
}
break;
}
case ON_BETTER_LINK_AVAIL: {
LinkInfo info = (LinkInfo) msg.obj;
if (mLinkNotifier != null) {
mLinkNotifier.onBetterLinkAvail(info);
}
break;
}
case ON_LINK_LOST: {
LinkInfo info = (LinkInfo) msg.obj;
if (mLinkNotifier != null) {
mLinkNotifier.onLinkLost(info);
}
break;
}
case ON_GET_LINK_FAILURE: {
int reason = (int) msg.arg1;
if (mLinkNotifier != null) {
mLinkNotifier.onGetLinkFailure(reason);
}
break;
}
default:
if (DBG) Log.d(LOG_TAG, "Unhandled Message msg = " + msg.what);
}
}
};
mLock.lock();
mHandlerAvail.signal();
mLock.unlock();
Looper.loop();
}
};
}
+724
View File
@@ -0,0 +1,724 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import android.content.Context;
import android.net.LinkCapabilities.Role;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/*
* TODO: Capability tracking features are missing.
*/
/**
* An extension of Java sockets. It provides a client-side TCP socket with a way to manage
* wireless connections.
* <p>
* Example Code: <br>
* <code>
* LinkSocket sock = new LinkSocket(instanceOfLinkSocketNotifier); <br>
* LinkCapabilities capabilities = LinkCapabilities.createNeeds(LinkCapabilities.Role.WEB_BROSWER); <br>
* capabilities.put(LinkCapabilities.Key.RW_DESIRED_FWD_BW, "1000"); // set 1Mbps down <br>
* sock.setNeededCapabilities(capabilities); // set the capabilities <br>
* sock.connect("www.google.com", 80); // Google's HTTP server <br>
* sock.close(); <br>
* </code>
*
* @see LinkSocketNotifier
* @hide
*/
public class LinkSocket extends Socket {
// Public error codes
/** Link Failure: Unknown failure */
public static final int ERROR_UNKNOWN = 0;
/** Link Failure: All networks are down */
public static final int ERROR_ALL_NETWORKS_DOWN = 1;
/** Link Failure: Networks are available, but none meet requirements */
public static final int ERROR_NETWORKS_FAIL_REQUIREMENTS = 2;
// Debugging
private final static String TAG = "LinkSocket";
private final static boolean DBG = true;
// Flags
private boolean isWaitingForResponse = false; // waiting for ConnectivityServices?
private final static int NOT_SET = -1;
// Members
private IConnectivityManager mService = null; // handle to ConnectivityService
private LinkCapabilities mNeededCapabilities = null; // requested capabilities
private LinkProperties mProperties = null; // socket properties
private LinkSocketNotifier mNotifier = null; // callback to application
private String mHostname = null; // destination hostname
private int mId = NOT_SET; // unique ID
private MessageHandler mMsgHandler = new MessageHandler(); // messages from ConnectivityServices
private Handler mHandler = null; // message handler
private MessageLoop mMsgLoop = new MessageLoop(); // message loop
/**
* Default constructor
*/
public LinkSocket() {
super();
if (DBG) Log.v(TAG, "LinkSocket() EX");
constructor(null);
}
/**
* Creates a new unconnected socket.
*
* @param notifier a reference to a class that implements
* {@code LinkSocketNotifier}
*/
public LinkSocket(LinkSocketNotifier notifier) {
super();
if (DBG) Log.v(TAG, "LinkSocket(notifier) EX");
constructor(notifier);
}
/**
* Creates a new unconnected socket using the given proxy type.
*
* @param notifier a reference to a class that implements
* {@code LinkSocketNotifier}
* @param proxy the specified proxy for this socket
* @throws IllegalArgumentException if the argument proxy is null or of an
* invalid type.
* @throws SecurityException if a security manager exists and it denies the
* permission to connect to the given proxy.
*/
public LinkSocket(LinkSocketNotifier notifier, Proxy proxy) {
super(proxy);
if (DBG) Log.v(TAG, "LinkSocket(notifier, proxy) EX");
constructor(notifier);
}
/**
* Creates a new unconnected socket with the same notifier and needs map as
* the source LinkSocket.
*
* @param source
*/
public LinkSocket(LinkSocket source) {
super();
if (DBG) Log.v(TAG, "LinkSocket(source) EX");
constructor(source.mNotifier);
setNeededCapabilities(source.getNeededCapabilities());
}
/**
* @return the {@code LinkProperties} for the socket
*/
public LinkProperties getLinkProperties() {
if (DBG) Log.v(TAG, "LinkProperties() EX");
return new LinkProperties(mProperties);
}
/**
* Set the {@code LinkCapabilities} needed for this socket. If the socket is
* already connected the request is ignored and {@code false} will be
* returned. A needs map can be created via the {@code createNeedsMap}
* static method.
*
* @param needs the needs of the socket
* @return {@code true} if needs are successfully set, {@code false}
* otherwise
*/
public boolean setNeededCapabilities(LinkCapabilities needs) {
if (DBG) Log.v(TAG, "setNeeds() EX");
// if mProperties is set, it is too late to set needs
if (mProperties != null) return false;
mNeededCapabilities = needs;
mNeededCapabilities.put(LinkCapabilities.Key.RO_TRANSPORT_PROTO_TYPE,"tcp");
if (mNotifier == null) {
// if mNotifier is null, then we cannot send notifications so we
// might as well disable them
mNeededCapabilities.put(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS, "true");
}
return true;
}
/**
* @return the LinkCapabilites set by setNeededCapabilities, empty if none
* has been set
*/
public LinkCapabilities getNeededCapabilities() {
if (DBG) Log.v(TAG, "getNeeds() EX");
return new LinkCapabilities(mNeededCapabilities);
}
/**
* @return a LinkCapabilities object containing the READ ONLY capabilities
* of the LinkSocket
*/
public LinkCapabilities getCapabilities() {
if (DBG) Log.v(TAG, "getCapabilities() EX");
LinkCapabilities cap = null;
try {
// place all capabilities in an int array
final int[] keys = new int[] {
LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY,
LinkCapabilities.Key.RO_CURRENT_REV_LATENCY,
LinkCapabilities.Key.RO_BOUND_INTERFACE,
LinkCapabilities.Key.RO_NETWORK_TYPE,
LinkCapabilities.Key.RO_PHYSICAL_INTERFACE,
LinkCapabilities.Key.RO_CARRIER_ROLE,
LinkCapabilities.Key.RO_QOS_STATE
};
cap = mService.requestCapabilities(mId, keys);
} catch (RemoteException ex) {
Log.d(TAG, "LinkSocket was unable to get capabilities from ConnectivityService");
// should not return a null reference
cap = new LinkCapabilities();
}
return cap;
}
/**
* Returns this LinkSockets set of capabilities, filtered according to the
* given {@code Set}. Capabilities in the Set but not available from the
* link will not be reported in the results. Capabilities of the link but
* not listed in the Set will also not be reported in the results.
*
* @param capability_keys {@code Set} of capabilities requested
* @return the filtered {@code LinkCapabilities} of this LinkSocket, may be
* empty
*/
public LinkCapabilities getCapabilities(Set<Integer> capability_keys) {
if (DBG) Log.v(TAG, "getCapabilities(capabilities) EX");
LinkCapabilities cap = null;
int[] keys = new int[capability_keys.size()];
// convert capability_keys into an int array
Iterator<Integer> it = capability_keys.iterator();
for (int i = 0; it.hasNext(); i++) keys[i] = it.next();
// send the request
try {
cap = mService.requestCapabilities(mId, keys);
} catch (RemoteException ex) {
Log.d(TAG, "LinkSocket was unable to get capabilities from ConnectivityService");
}
return cap;
}
/**
* Provide the set of capabilities the application is interested in tracking
* for this LinkSocket.
*
* @param capabilities a {@code Set} of capabilities to track
*/
public void setTrackedCapabilities(Set<Integer> capabilities) {
if (DBG) Log.v(TAG, "setTrackedCapabilities(capabilities) EX");
// This feature is not implemented yet.
}
/**
* @return the {@code LinkCapabilities} that are tracked, empty if
* none has been set.
*/
public Set<Integer> getTrackedCapabilities() {
if (DBG) Log.v(TAG, "getTrackedCapabilities() EX");
// This feature is not implemented yet.
return new HashSet<Integer>();
}
/**
* Connects this socket to the given remote host address and port specified
* by dstName and dstPort.
*
* @param dstName the address of the remote host to connect to
* @param dstPort the port to connect to on the remote host
* @param timeout the timeout value in milliseconds or 0 for infinite
* timeout
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting
* @throws SocketTimeoutException if the timeout fires
*/
public void connect(String dstName, int dstPort, int timeout) throws UnknownHostException,
IOException, SocketTimeoutException {
if (DBG) Log.v(TAG, "connect(dstName, dstPort, timeout) EX");
if (dstName == null) {
throw new UnknownHostException("destination address is not set");
}
if (dstPort < 0) {
throw new UnknownHostException("destination port is not set");
}
/*
* Currently RW_ALLOWED_NETWORKS and RW_PROHIBITED_NETWORKS are not
* implemented. If either of these keys are in use, throw an
* IOException.
*
* TODO: implement RW_ALLOWED_NETWORKS and RW_PROHIBITED_NETWORKS in
* next release.
*/
if (mNeededCapabilities.containsKey(LinkCapabilities.Key.RW_ALLOWED_NETWORKS)
|| mNeededCapabilities.containsKey(LinkCapabilities.Key.RW_PROHIBITED_NETWORKS)) {
throw new IOException("RW_ALLOWED_NETWORKS and RW_PROHIBITED_NETWORKS" +
" are not supported at this time");
}
// save the current time for timeouts
Calendar start = Calendar.getInstance();
// make sure message processing thread is ready before we try to connect
while (mHandler == null) Thread.yield();
/*
* Steps:
* 1. save the destination address for future use
* 2. request a network link
* 3. figure out which address to bind to
* 4. bind to the given address
* 5. connect
*/
// save the addresses for future use
mHostname = dstName;
// get need a network connection
synchronized (this) {
if (mId == NOT_SET) {
try {
isWaitingForResponse = true;
if (DBG) Log.v(TAG, "sending requestLink()");
mId = mService.requestLink(mNeededCapabilities, mMsgHandler);
if (DBG) Log.v(TAG, "Blocking: waiting for response");
while (isWaitingForResponse) {
if (timeout == 0) {
wait();
} else {
wait(timeout);
/*
* if 'timeout' time passes, wait() will stop
* blocking just as if it was interrupted or
* received a notification, and the while loop will
* begin again. We need track time, and throw and
* exception if appropriate. This also reduces the
* amount of time socket.connect() will wait based
* on how long it took to acquire the link.
*/
timeout -= (int) (Calendar.getInstance().getTimeInMillis()
- start.getTimeInMillis());
if (timeout <= 0) {
releaseLink();
throw new SocketTimeoutException(
"Socket timed out during link acquisition.");
}
}
if (DBG) Log.v(TAG, "Blocking: received notification or timeout");
}
if (DBG) Log.v(TAG, "Blocking: done");
} catch (InterruptedException ex) {
Log.d(TAG, "ConnectivityService failed to respond to request.");
releaseLink();
} catch (RemoteException ex) {
Log.w(TAG, "LinkSocket was unable to acquire a new network link. " + ex);
releaseLink();
}
}
}
// if mProperties is still null, we couldn't get a network
if (mProperties == null) {
releaseLink();
throw new IOException("Unable to find a network that meets requirements.");
}
// get first address from mProperties
Collection<InetAddress> addresses = mProperties.getAddresses();
if (addresses == null || addresses.isEmpty()) {
releaseLink();
throw new IOException("No valid address to bind to");
}
InetAddress bindAddress = null;
for (InetAddress address : addresses) {
bindAddress = address;
break;
}
// bind and connect
if (DBG) Log.v(TAG, "attempting to bind: " + bindAddress);
super.bind(new InetSocketAddress(bindAddress, 0));
if (DBG) Log.v(TAG, "bind successful: " + getLocalSocketAddress());
if (DBG) Log.v(TAG, "attempting to connect: " + mHostname + ":" + super.getPort());
//request Qos for all sockets regardless of role type, service will
//handle this request appropriately
try {
mService.requestQoS(mId, super.getLocalPort(), bindAddress.getHostAddress());
} catch (RemoteException re) {
if (DBG) Log.v(TAG,"requestQoS experienced remote exception: " + re);
}
super.connect(new InetSocketAddress(dstName, dstPort), timeout);
if (DBG) Log.v(TAG, "connect successful: " + getInetAddress() + ":" + super.getPort());
}
/**
* Connects this socket to the given remote host address and port specified
* by dstName and dstPort.
*
* @param dstName the address of the remote host to connect to
* @param dstPort the port to connect to on the remote host
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting
*/
public void connect(String dstName, int dstPort) throws UnknownHostException, IOException {
if (DBG) Log.v(TAG, "connect(dstName, dstPort) EX");
connect(dstName, dstPort, 0);
}
/**
* Connects this socket to the same remote host address and port as the
* source LinkSocket.
*
* @param source the LinkSocket from which to get the destination
* @param timeout the timeout value in milliseconds or 0 for infinite
* timeout
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting
*/
public void connect(LinkSocket source, int timeout) throws UnknownHostException, IOException {
if (DBG) Log.v(TAG, "connect(source) EX");
connect(source.getHostname(), source.getPort(), timeout);
}
/**
* Connects this socket to the same remote host address and port as the
* source LinkSocket.
*
* @param source the LinkSocket from which to get the destination
* @throws UnknownHostException if the given dstName is invalid
* @throws IOException if the socket is already connected or an error occurs
* while connecting
*/
public void connect(LinkSocket source) throws UnknownHostException, IOException {
if (DBG) Log.v(TAG, "connect(source) EX");
connect(source.getHostname(), source.getPort(), 0);
}
/**
* Connects this socket to the given remote host address and port specified
* by the SocketAddress with the specified timeout.
*
* @deprecated Use {@link LinkSocket#connect(String, int, int)} instead.
* Using this method will result in an IllegalArgumentException.
* @param remoteAddr the address and port of the remote host to connect to
* @param timeout the timeout value in milliseconds or 0 for an infinite timeout
* @throws IllegalArgumentException always
*/
@Override
@Deprecated
public void connect(SocketAddress remoteAddr, int timeout) throws IOException,
SocketTimeoutException, IllegalArgumentException {
if (DBG) Log.v(TAG, "connect(remoteAddr, timeout) EX DEPRECATED");
throw new IllegalArgumentException("connect(remoteAddr, timeout) is deprecated");
}
/**
* Connects this socket to the given remote host address and port specified
* by the SocketAddress. Network selection happens during connect and may
* take 30 seconds.
*
* @deprecated Use {@link LinkSocket#connect(String, int)} instead. Using
* this method will result in an IllegalArgumentException.
* @param remoteAddr the address and port of the remote host to connect to.
* @throws IllegalArgumentException always
*/
@Override
@Deprecated
public void connect(SocketAddress remoteAddr) throws IOException, IllegalArgumentException {
if (DBG) Log.v(TAG, "connect(remoteAddr) EX DEPRECATED");
throw new IllegalArgumentException("connect(remoteAddr) is deprecated");
}
/**
* Closes the socket. It is not possible to reconnect or re-bind to this
* socket thereafter which means a new socket instance has to be created.
*
* @throws IOException if an error occurs while closing the socket
*/
@Override
public void close() throws IOException {
if (DBG) Log.v(TAG, "close() EX");
mMsgLoop.quit(); // disable callbacks
releaseLink();
super.close();
}
/**
* @deprecated LinkSocket will automatically pick the optimum interface to
* bind to
* @param localAddr the specific address and port on the local machine to
* bind to
* @throws IOException always as this method is deprecated for LinkSocket
*/
@Override
@Deprecated
public void bind(SocketAddress localAddr) throws UnsupportedOperationException {
if (DBG) Log.v(TAG, "bind(localAddr) EX throws Exception");
throw new UnsupportedOperationException("bind is deprecated for LinkSocket");
}
/**
* Gets the host name of the target host this socket is connected to.
*
* @return the host name of the connected target host, or {@code null} if
* this socket is not yet connected.
*/
public String getHostname() {
return mHostname;
}
/**
* Gets the port number of the target host this socket is connected to.
*
* @return the port number of the connected target host or 0 if this socket
* is not yet connected.
*/
public int getPort() {
return super.getPort();
}
@Override
public String toString() {
if (this.isConnected() == false) {
if (mId == NOT_SET) {
return "LinkSocket id:none unconnected";
} else {
return "LinkSocket id:" + mId + " unconnected";
}
} else {
return "LinkSocket id:" + mId + " addr:" + super.getInetAddress()
+ " port:" + super.getPort() + " local_port:" + super.getLocalPort();
}
}
/*
* Handles callbacks from ConnectivityServices
*/
private class MessageHandler extends ILinkSocketMessageHandler.Stub {
// Flags
private final static int ON_LINK_AVAIL = 0;
private final static int ON_GET_LINK_FAILURE = 1;
private final static int ON_BETTER_LINK_AVAIL = 2;
private final static int ON_LINK_LOST = 3;
private final static int ON_CAPABILITIES_CHANGED = 4;
public void onLinkAvail(LinkProperties properties) {
if (DBG) Log.v(TAG, "CallbackHandler.onLinkAvail(properties) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_LINK_AVAIL, properties));
}
public void onGetLinkFailure(int reason) {
if (DBG) Log.v(TAG, "CallbackHandler.onGetLinkFailure(reason) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_GET_LINK_FAILURE, reason));
}
public void onBetterLinkAvail() {
if (DBG) Log.v(TAG, "CallbackHandler.onBetterLinkAvail(properties) EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_BETTER_LINK_AVAIL));
}
public void onLinkLost() {
if (DBG) Log.v(TAG, "CallbackHandler.onLinkLost() EX");
mHandler.sendMessage(mHandler.obtainMessage(ON_LINK_LOST));
}
public void onCapabilitiesChanged(LinkCapabilities changedCapabilities) {
if (DBG) Log.v(TAG, "CallbackHandler.onCapabilitiesChanged(changedCapabilities) EX");
mHandler.sendMessage(
mHandler.obtainMessage(ON_CAPABILITIES_CHANGED, changedCapabilities));
}
}
private void constructor(LinkSocketNotifier notifier) {
if (DBG) Log.v(TAG, "constructor(notifier, proxy) EX");
mMsgLoop.start(); // start up message processing thread
mNotifier = notifier;
setNeededCapabilities(LinkCapabilities.createNeeds(Role.DEFAULT));
mNeededCapabilities.put(LinkCapabilities.Key.RO_TRANSPORT_PROTO_TYPE,"tcp");
IBinder binder = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
mService = IConnectivityManager.Stub.asInterface(binder);
}
private void releaseLink() {
if (mId == NOT_SET) return; // nothing to release
if (DBG) Log.v(TAG, "releasing link");
try {
mService.releaseLink(mId);
} catch (RemoteException ex) {
Log.w(TAG, "LinkSocket was unable relinquish the current network link. " + ex);
}
mId = NOT_SET;
}
/*
* Handle messages from CallbackHandler
*/
private class MessageLoop extends Thread {
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
if (DBG) Log.v(TAG, "handleMessage(msg) EX");
switch (msg.what) {
case MessageHandler.ON_LINK_AVAIL:
callbackOnLinkAvail((LinkProperties) msg.obj);
break;
case MessageHandler.ON_GET_LINK_FAILURE:
callbackOnGetLinkFailure((Integer) msg.obj);
break;
case MessageHandler.ON_BETTER_LINK_AVAIL:
callbackOnBetterLinkAvail();
break;
case MessageHandler.ON_LINK_LOST:
callbackOnLinkLost();
break;
case MessageHandler.ON_CAPABILITIES_CHANGED:
callbackOnCapabilitiesChanged((LinkCapabilities) msg.obj);
break;
default:
Log.d(TAG, "LinkSocket received an unknown message type");
}
}
};
Looper.loop();
}
public void quit() {
if (mHandler != null) mHandler.getLooper().quit();
}
}
private void callbackOnLinkAvail(LinkProperties properties) {
if (DBG) Log.v(TAG, "onLinkAvail(properties) EX");
if (mProperties != null) {
/*
* this is an unexpected onLinkAvail(), it probably should be a
* onBetterLinkAvail() so we're going to call that
*
* TODO: remove once CND is updated to the new architecture
*/
callbackOnBetterLinkAvail();
return;
}
// ConnectivityService found a link!
mProperties = properties;
// stop blocking
isWaitingForResponse = false;
synchronized (this) {
notifyAll();
}
}
private void callbackOnGetLinkFailure(int reason) {
if (DBG) Log.v(TAG, "onGetLinkFailure(reason) EX");
if (mProperties != null) {
/*
* this is an unexpected onGetLinkFailure, it probably should just
* be ignored
*
* TODO: remove once CND is updated to the new architecture
*/
return;
}
// implied releaseLink()
mId = NOT_SET;
// stop blocking
isWaitingForResponse = false;
synchronized (this) {
notifyAll();
}
}
private void callbackOnBetterLinkAvail() {
if (DBG) Log.v(TAG, "onBetterLinkAvail() EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onBetterLinkAvailable(this);
}
private void callbackOnLinkLost() {
if (DBG) Log.v(TAG, "onLinkLost() EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onLinkLost(this);
}
private void callbackOnCapabilitiesChanged(LinkCapabilities changedCapabilities) {
if (DBG) Log.v(TAG, "onCapabilitiesChanged(changedCapabilities) EX");
if (mNotifier == null) return;
// are notifications disabled?
String notify = mNeededCapabilities.get(LinkCapabilities.Key.RW_DISABLE_NOTIFICATIONS);
if (notify != null && notify.equalsIgnoreCase("true")) return;
mNotifier.onCapabilitiesChanged(this, changedCapabilities);
}
}
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2010-2011, The Linux Foundation. All rights reserved.
*
* 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.net;
/**
* Interface used to get feedback about a {@link android.net.LinkSocket} or
* {@link android.net.LinkDatagramSocket}. Instance is optionally
* passed when a Link[Datagram]Socket is constructed. Multiple Link[Datagram]Sockets may
* use the same notifier.
* @hide
*/
public interface LinkSocketNotifier {
/**
* This callback function will be called if the system determines
* an application will get a better link if it creates a new
* LinkSocket right now.
*
* If a new LinkSocket is created, it is important to close the
* old LinkSocket once it is no longer in use.
*
* @param socket the original LinkSocket whose connection could be improved
*/
public void onBetterLinkAvailable(LinkSocket socket);
/**
* This callback function will be called if the system determines
* an application will get a better link if it creates a new
* LinkDatagramSocket right now.
*
* Note: This callback is not applicable for QoS needs
*
* If a new LinkDatagramSocket is created, it is important to
* close the old LinkDatagramSocket once it is no longer in use.
*
* @param socket the original LinkDatagramSocket whose connection
* could be improved
*/
public void onBetterLinkAvailable(LinkDatagramSocket socket);
/**
* This callback function will be called when a LinkSocket no longer has
* an active link.
*
* @param socket the LinkSocket that lost its link
*/
public void onLinkLost(LinkSocket socket);
/**
* This callback function will be called when a LinkDatagramSocket no
* longer has an active link.
*
* @param socket the LinkDatagramSocket that lost its link
*/
public void onLinkLost(LinkDatagramSocket socket);
/**
* This callback function will be called when any of the notification-marked
* capabilities of the LinkSocket (e.g. upstream bandwidth) have changed.
*
* @param socket the LinkSocket for which capabilities have changed
* @param changedCapabilities the set of capabilities that the application
* is interested in and have changed (with new values)
*/
public void onCapabilitiesChanged(LinkSocket socket, LinkCapabilities changedCapabilities);
/**
* This callback function will be called when any of the notification-marked
* QoS capabilities of the LinkDatagramSocket (e.g. upstream bandwidth) have
* changed.
*
* @param socket the LinkDatagramSocket for which capabilities have changed
* @param changedCapabilities the set of capabilities that the application
* is interested in and have changed (with new values)
*/
public void onCapabilitiesChanged(LinkDatagramSocket socket, LinkCapabilities changedCapabilities);
}
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2007 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.net;
import java.io.IOException;
import java.io.FileDescriptor;
/**
* non-standard class for creating inbound UNIX-domain socket
* on the Android platform, this is created in the Linux non-filesystem
* namespace.
*
* On simulator platforms, this may be created in a temporary directory on
* the filesystem
*/
public class LocalServerSocket {
private final LocalSocketImpl impl;
private final LocalSocketAddress localAddress;
/** 50 seems a bit much, but it's what was here */
private static final int LISTEN_BACKLOG = 50;
/**
* Crewates a new server socket listening at specified name.
* On the Android platform, the name is created in the Linux
* abstract namespace (instead of on the filesystem).
*
* @param name address for socket
* @throws IOException
*/
public LocalServerSocket(String name) throws IOException
{
impl = new LocalSocketImpl();
impl.create(true);
localAddress = new LocalSocketAddress(name);
impl.bind(localAddress);
impl.listen(LISTEN_BACKLOG);
}
/**
* Create a LocalServerSocket from a file descriptor that's already
* been created and bound. listen() will be called immediately on it.
* Used for cases where file descriptors are passed in via environment
* variables
*
* @param fd bound file descriptor
* @throws IOException
*/
public LocalServerSocket(FileDescriptor fd) throws IOException
{
impl = new LocalSocketImpl(fd);
impl.listen(LISTEN_BACKLOG);
localAddress = impl.getSockAddress();
}
/**
* Obtains the socket's local address
*
* @return local address
*/
public LocalSocketAddress getLocalSocketAddress()
{
return localAddress;
}
/**
* Accepts a new connection to the socket. Blocks until a new
* connection arrives.
*
* @return a socket representing the new connection.
* @throws IOException
*/
public LocalSocket accept() throws IOException
{
LocalSocketImpl acceptedImpl = new LocalSocketImpl();
impl.accept (acceptedImpl);
return new LocalSocket(acceptedImpl);
}
/**
* Returns file descriptor or null if not yet open/already closed
*
* @return fd or null
*/
public FileDescriptor getFileDescriptor() {
return impl.getFileDescriptor();
}
/**
* Closes server socket.
*
* @throws IOException
*/
public void close() throws IOException
{
impl.close();
}
}
+291
View File
@@ -0,0 +1,291 @@
/*
* Copyright (C) 2007 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.net;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketOptions;
/**
* Creates a (non-server) socket in the UNIX-domain namespace. The interface
* here is not entirely unlike that of java.net.Socket
*/
public class LocalSocket {
private LocalSocketImpl impl;
private volatile boolean implCreated;
private LocalSocketAddress localAddress;
private boolean isBound;
private boolean isConnected;
/**
* Creates a AF_LOCAL/UNIX domain stream socket.
*/
public LocalSocket() {
this(new LocalSocketImpl());
isBound = false;
isConnected = false;
}
/**
* for use with AndroidServerSocket
* @param impl a SocketImpl
*/
/*package*/ LocalSocket(LocalSocketImpl impl) {
this.impl = impl;
this.isConnected = false;
this.isBound = false;
}
/** {@inheritDoc} */
@Override
public String toString() {
return super.toString() + " impl:" + impl;
}
/**
* It's difficult to discern from the spec when impl.create() should be
* called, but it seems like a reasonable rule is "as soon as possible,
* but not in a context where IOException cannot be thrown"
*
* @throws IOException from SocketImpl.create()
*/
private void implCreateIfNeeded() throws IOException {
if (!implCreated) {
synchronized (this) {
if (!implCreated) {
try {
impl.create(true);
} finally {
implCreated = true;
}
}
}
}
}
/**
* Connects this socket to an endpoint. May only be called on an instance
* that has not yet been connected.
*
* @param endpoint endpoint address
* @throws IOException if socket is in invalid state or the address does
* not exist.
*/
public void connect(LocalSocketAddress endpoint) throws IOException {
synchronized (this) {
if (isConnected) {
throw new IOException("already connected");
}
implCreateIfNeeded();
impl.connect(endpoint, 0);
isConnected = true;
isBound = true;
}
}
/**
* Binds this socket to an endpoint name. May only be called on an instance
* that has not yet been bound.
*
* @param bindpoint endpoint address
* @throws IOException
*/
public void bind(LocalSocketAddress bindpoint) throws IOException {
implCreateIfNeeded();
synchronized (this) {
if (isBound) {
throw new IOException("already bound");
}
localAddress = bindpoint;
impl.bind(localAddress);
isBound = true;
}
}
/**
* Retrieves the name that this socket is bound to, if any.
*
* @return Local address or null if anonymous
*/
public LocalSocketAddress getLocalSocketAddress() {
return localAddress;
}
/**
* Retrieves the input stream for this instance.
*
* @return input stream
* @throws IOException if socket has been closed or cannot be created.
*/
public InputStream getInputStream() throws IOException {
implCreateIfNeeded();
return impl.getInputStream();
}
/**
* Retrieves the output stream for this instance.
*
* @return output stream
* @throws IOException if socket has been closed or cannot be created.
*/
public OutputStream getOutputStream() throws IOException {
implCreateIfNeeded();
return impl.getOutputStream();
}
/**
* Closes the socket.
*
* @throws IOException
*/
public void close() throws IOException {
implCreateIfNeeded();
impl.close();
}
/**
* Shuts down the input side of the socket.
*
* @throws IOException
*/
public void shutdownInput() throws IOException {
implCreateIfNeeded();
impl.shutdownInput();
}
/**
* Shuts down the output side of the socket.
*
* @throws IOException
*/
public void shutdownOutput() throws IOException {
implCreateIfNeeded();
impl.shutdownOutput();
}
public void setReceiveBufferSize(int size) throws IOException {
impl.setOption(SocketOptions.SO_RCVBUF, Integer.valueOf(size));
}
public int getReceiveBufferSize() throws IOException {
return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue();
}
public void setSoTimeout(int n) throws IOException {
impl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(n));
}
public int getSoTimeout() throws IOException {
return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue();
}
public void setSendBufferSize(int n) throws IOException {
impl.setOption(SocketOptions.SO_SNDBUF, Integer.valueOf(n));
}
public int getSendBufferSize() throws IOException {
return ((Integer) impl.getOption(SocketOptions.SO_SNDBUF)).intValue();
}
//???SEC
public LocalSocketAddress getRemoteSocketAddress() {
throw new UnsupportedOperationException();
}
//???SEC
public synchronized boolean isConnected() {
return isConnected;
}
//???SEC
public boolean isClosed() {
throw new UnsupportedOperationException();
}
//???SEC
public synchronized boolean isBound() {
return isBound;
}
//???SEC
public boolean isOutputShutdown() {
throw new UnsupportedOperationException();
}
//???SEC
public boolean isInputShutdown() {
throw new UnsupportedOperationException();
}
//???SEC
public void connect(LocalSocketAddress endpoint, int timeout)
throws IOException {
throw new UnsupportedOperationException();
}
/**
* Enqueues a set of file descriptors to send to the peer. The queue
* is one deep. The file descriptors will be sent with the next write
* of normal data, and will be delivered in a single ancillary message.
* See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
*
* @param fds non-null; file descriptors to send.
*/
public void setFileDescriptorsForSend(FileDescriptor[] fds) {
impl.setFileDescriptorsForSend(fds);
}
/**
* Retrieves a set of file descriptors that a peer has sent through
* an ancillary message. This method retrieves the most recent set sent,
* and then returns null until a new set arrives.
* File descriptors may only be passed along with regular data, so this
* method can only return a non-null after a read operation.
*
* @return null or file descriptor array
* @throws IOException
*/
public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
return impl.getAncillaryFileDescriptors();
}
/**
* Retrieves the credentials of this socket's peer. Only valid on
* connected sockets.
*
* @return non-null; peer credentials
* @throws IOException
*/
public Credentials getPeerCredentials() throws IOException {
return impl.getPeerCredentials();
}
/**
* Returns file descriptor or null if not yet open/already closed
*
* @return fd or null
*/
public FileDescriptor getFileDescriptor() {
return impl.getFileDescriptor();
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2007 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.net;
/**
* A UNIX-domain (AF_LOCAL) socket address. For use with
* android.net.LocalSocket and android.net.LocalServerSocket.
*
* On the Android system, these names refer to names in the Linux
* abstract (non-filesystem) UNIX domain namespace.
*/
public class LocalSocketAddress
{
/**
* The namespace that this address exists in. See also
* include/cutils/sockets.h ANDROID_SOCKET_NAMESPACE_*
*/
public enum Namespace {
/** A socket in the Linux abstract namespace */
ABSTRACT(0),
/**
* A socket in the Android reserved namespace in /dev/socket.
* Only the init process may create a socket here.
*/
RESERVED(1),
/**
* A socket named with a normal filesystem path.
*/
FILESYSTEM(2);
/** The id matches with a #define in include/cutils/sockets.h */
private int id;
Namespace (int id) {
this.id = id;
}
/**
* @return int constant shared with native code
*/
/*package*/ int getId() {
return id;
}
}
private final String name;
private final Namespace namespace;
/**
* Creates an instance with a given name.
*
* @param name non-null name
* @param namespace namespace the name should be created in.
*/
public LocalSocketAddress(String name, Namespace namespace) {
this.name = name;
this.namespace = namespace;
}
/**
* Creates an instance with a given name in the {@link Namespace#ABSTRACT}
* namespace
*
* @param name non-null name
*/
public LocalSocketAddress(String name) {
this(name,Namespace.ABSTRACT);
}
/**
* Retrieves the string name of this address
* @return string name
*/
public String getName()
{
return name;
}
/**
* Returns the namespace used by this address.
*
* @return non-null a namespace
*/
public Namespace getNamespace() {
return namespace;
}
}
@@ -0,0 +1,490 @@
/*
* Copyright (C) 2007 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.net;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.FileDescriptor;
import java.net.SocketOptions;
/**
* Socket implementation used for android.net.LocalSocket and
* android.net.LocalServerSocket. Supports only AF_LOCAL sockets.
*/
class LocalSocketImpl
{
private SocketInputStream fis;
private SocketOutputStream fos;
private Object readMonitor = new Object();
private Object writeMonitor = new Object();
/** null if closed or not yet created */
private FileDescriptor fd;
// These fields are accessed by native code;
/** file descriptor array received during a previous read */
FileDescriptor[] inboundFileDescriptors;
/** file descriptor array that should be written during next write */
FileDescriptor[] outboundFileDescriptors;
/**
* An input stream for local sockets. Needed because we may
* need to read ancillary data.
*/
class SocketInputStream extends InputStream {
/** {@inheritDoc} */
@Override
public int available() throws IOException {
return available_native(fd);
}
/** {@inheritDoc} */
@Override
public void close() throws IOException {
LocalSocketImpl.this.close();
}
/** {@inheritDoc} */
@Override
public int read() throws IOException {
int ret;
synchronized (readMonitor) {
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
ret = read_native(myFd);
return ret;
}
}
/** {@inheritDoc} */
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
synchronized (readMonitor) {
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
if (off < 0 || len < 0 || (off + len) > b.length ) {
throw new ArrayIndexOutOfBoundsException();
}
int ret = readba_native(b, off, len, myFd);
return ret;
}
}
}
/**
* An output stream for local sockets. Needed because we may
* need to read ancillary data.
*/
class SocketOutputStream extends OutputStream {
/** {@inheritDoc} */
@Override
public void close() throws IOException {
LocalSocketImpl.this.close();
}
/** {@inheritDoc} */
@Override
public void write (byte[] b) throws IOException {
write(b, 0, b.length);
}
/** {@inheritDoc} */
@Override
public void write (byte[] b, int off, int len) throws IOException {
synchronized (writeMonitor) {
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
if (off < 0 || len < 0 || (off + len) > b.length ) {
throw new ArrayIndexOutOfBoundsException();
}
writeba_native(b, off, len, myFd);
}
}
/** {@inheritDoc} */
@Override
public void write (int b) throws IOException {
synchronized (writeMonitor) {
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
write_native(b, myFd);
}
}
}
private native int available_native(FileDescriptor fd) throws IOException;
private native void close_native(FileDescriptor fd) throws IOException;
private native int read_native(FileDescriptor fd) throws IOException;
private native int readba_native(byte[] b, int off, int len,
FileDescriptor fd) throws IOException;
private native void writeba_native(byte[] b, int off, int len,
FileDescriptor fd) throws IOException;
private native void write_native(int b, FileDescriptor fd)
throws IOException;
private native void connectLocal(FileDescriptor fd, String name,
int namespace) throws IOException;
private native void bindLocal(FileDescriptor fd, String name, int namespace)
throws IOException;
private native FileDescriptor create_native(boolean stream)
throws IOException;
private native void listen_native(FileDescriptor fd, int backlog)
throws IOException;
private native void shutdown(FileDescriptor fd, boolean shutdownInput);
private native Credentials getPeerCredentials_native(
FileDescriptor fd) throws IOException;
private native int getOption_native(FileDescriptor fd, int optID)
throws IOException;
private native void setOption_native(FileDescriptor fd, int optID,
int b, int value) throws IOException;
// private native LocalSocketAddress getSockName_native
// (FileDescriptor fd) throws IOException;
/**
* Accepts a connection on a server socket.
*
* @param fd file descriptor of server socket
* @param s socket implementation that will become the new socket
* @return file descriptor of new socket
*/
private native FileDescriptor accept
(FileDescriptor fd, LocalSocketImpl s) throws IOException;
/**
* Create a new instance.
*/
/*package*/ LocalSocketImpl()
{
}
/**
* Create a new instance from a file descriptor representing
* a bound socket. The state of the file descriptor is not checked here
* but the caller can verify socket state by calling listen().
*
* @param fd non-null; bound file descriptor
*/
/*package*/ LocalSocketImpl(FileDescriptor fd) throws IOException
{
this.fd = fd;
}
public String toString() {
return super.toString() + " fd:" + fd;
}
/**
* Creates a socket in the underlying OS.
*
* @param stream true if this should be a stream socket, false for
* datagram.
* @throws IOException
*/
public void create (boolean stream) throws IOException {
// no error if socket already created
// need this for LocalServerSocket.accept()
if (fd == null) {
fd = create_native(stream);
}
}
/**
* Closes the socket.
*
* @throws IOException
*/
public void close() throws IOException {
synchronized (LocalSocketImpl.this) {
if (fd == null) return;
close_native(fd);
fd = null;
}
}
/** note timeout presently ignored */
protected void connect(LocalSocketAddress address, int timeout)
throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
connectLocal(fd, address.getName(), address.getNamespace().getId());
}
/**
* Binds this socket to an endpoint name. May only be called on an instance
* that has not yet been bound.
*
* @param endpoint endpoint address
* @throws IOException
*/
public void bind(LocalSocketAddress endpoint) throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
bindLocal(fd, endpoint.getName(), endpoint.getNamespace().getId());
}
protected void listen(int backlog) throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
listen_native(fd, backlog);
}
/**
* Accepts a new connection to the socket. Blocks until a new
* connection arrives.
*
* @param s a socket that will be used to represent the new connection.
* @throws IOException
*/
protected void accept(LocalSocketImpl s) throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
s.fd = accept(fd, s);
}
/**
* Retrieves the input stream for this instance.
*
* @return input stream
* @throws IOException if socket has been closed or cannot be created.
*/
protected InputStream getInputStream() throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
synchronized (this) {
if (fis == null) {
fis = new SocketInputStream();
}
return fis;
}
}
/**
* Retrieves the output stream for this instance.
*
* @return output stream
* @throws IOException if socket has been closed or cannot be created.
*/
protected OutputStream getOutputStream() throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
synchronized (this) {
if (fos == null) {
fos = new SocketOutputStream();
}
return fos;
}
}
/**
* Returns the number of bytes available for reading without blocking.
*
* @return >= 0 count bytes available
* @throws IOException
*/
protected int available() throws IOException
{
return getInputStream().available();
}
/**
* Shuts down the input side of the socket.
*
* @throws IOException
*/
protected void shutdownInput() throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
shutdown(fd, true);
}
/**
* Shuts down the output side of the socket.
*
* @throws IOException
*/
protected void shutdownOutput() throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
shutdown(fd, false);
}
protected FileDescriptor getFileDescriptor()
{
return fd;
}
protected boolean supportsUrgentData()
{
return false;
}
protected void sendUrgentData(int data) throws IOException
{
throw new RuntimeException ("not impled");
}
public Object getOption(int optID) throws IOException
{
if (fd == null) {
throw new IOException("socket not created");
}
if (optID == SocketOptions.SO_TIMEOUT) {
return 0;
}
int value = getOption_native(fd, optID);
switch (optID)
{
case SocketOptions.SO_RCVBUF:
case SocketOptions.SO_SNDBUF:
return value;
case SocketOptions.SO_REUSEADDR:
default:
return value;
}
}
public void setOption(int optID, Object value)
throws IOException {
/*
* Boolean.FALSE is used to disable some options, so it
* is important to distinguish between FALSE and unset.
* We define it here that -1 is unset, 0 is FALSE, and 1
* is TRUE.
*/
int boolValue = -1;
int intValue = 0;
if (fd == null) {
throw new IOException("socket not created");
}
if (value instanceof Integer) {
intValue = (Integer)value;
} else if (value instanceof Boolean) {
boolValue = ((Boolean) value)? 1 : 0;
} else {
throw new IOException("bad value: " + value);
}
setOption_native(fd, optID, boolValue, intValue);
}
/**
* Enqueues a set of file descriptors to send to the peer. The queue
* is one deep. The file descriptors will be sent with the next write
* of normal data, and will be delivered in a single ancillary message.
* See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
*
* @param fds non-null; file descriptors to send.
* @throws IOException
*/
public void setFileDescriptorsForSend(FileDescriptor[] fds) {
synchronized(writeMonitor) {
outboundFileDescriptors = fds;
}
}
/**
* Retrieves a set of file descriptors that a peer has sent through
* an ancillary message. This method retrieves the most recent set sent,
* and then returns null until a new set arrives.
* File descriptors may only be passed along with regular data, so this
* method can only return a non-null after a read operation.
*
* @return null or file descriptor array
* @throws IOException
*/
public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
synchronized(readMonitor) {
FileDescriptor[] result = inboundFileDescriptors;
inboundFileDescriptors = null;
return result;
}
}
/**
* Retrieves the credentials of this socket's peer. Only valid on
* connected sockets.
*
* @return non-null; peer credentials
* @throws IOException
*/
public Credentials getPeerCredentials() throws IOException
{
return getPeerCredentials_native(fd);
}
/**
* Retrieves the socket name from the OS.
*
* @return non-null; socket name
* @throws IOException on failure
*/
public LocalSocketAddress getSockAddress() throws IOException
{
return null;
//TODO implement this
//return getSockName_native(fd);
}
@Override
protected void finalize() throws IOException {
close();
}
}
+172
View File
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2008 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.net;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
*
* MailTo URL parser
*
* This class parses a mailto scheme URL and then can be queried for
* the parsed parameters. This implements RFC 2368.
*
*/
public class MailTo {
static public final String MAILTO_SCHEME = "mailto:";
// All the parsed content is added to the headers.
private HashMap<String, String> mHeaders;
// Well known headers
static private final String TO = "to";
static private final String BODY = "body";
static private final String CC = "cc";
static private final String SUBJECT = "subject";
/**
* Test to see if the given string is a mailto URL
* @param url string to be tested
* @return true if the string is a mailto URL
*/
public static boolean isMailTo(String url) {
if (url != null && url.startsWith(MAILTO_SCHEME)) {
return true;
}
return false;
}
/**
* Parse and decode a mailto scheme string. This parser implements
* RFC 2368. The returned object can be queried for the parsed parameters.
* @param url String containing a mailto URL
* @return MailTo object
* @exception ParseException if the scheme is not a mailto URL
*/
public static MailTo parse(String url) throws ParseException {
if (url == null) {
throw new NullPointerException();
}
if (!isMailTo(url)) {
throw new ParseException("Not a mailto scheme");
}
// Strip the scheme as the Uri parser can't cope with it.
String noScheme = url.substring(MAILTO_SCHEME.length());
Uri email = Uri.parse(noScheme);
MailTo m = new MailTo();
// Parse out the query parameters
String query = email.getQuery();
if (query != null ) {
String[] queries = query.split("&");
for (String q : queries) {
String[] nameval = q.split("=");
if (nameval.length == 0) {
continue;
}
// insert the headers with the name in lowercase so that
// we can easily find common headers
m.mHeaders.put(Uri.decode(nameval[0]).toLowerCase(),
nameval.length > 1 ? Uri.decode(nameval[1]) : null);
}
}
// Address can be specified in both the headers and just after the
// mailto line. Join the two together.
String address = email.getPath();
if (address != null) {
String addr = m.getTo();
if (addr != null) {
address += ", " + addr;
}
m.mHeaders.put(TO, address);
}
return m;
}
/**
* Retrieve the To address line from the parsed mailto URL. This could be
* several email address that are comma-space delimited.
* If no To line was specified, then null is return
* @return comma delimited email addresses or null
*/
public String getTo() {
return mHeaders.get(TO);
}
/**
* Retrieve the CC address line from the parsed mailto URL. This could be
* several email address that are comma-space delimited.
* If no CC line was specified, then null is return
* @return comma delimited email addresses or null
*/
public String getCc() {
return mHeaders.get(CC);
}
/**
* Retrieve the subject line from the parsed mailto URL.
* If no subject line was specified, then null is return
* @return subject or null
*/
public String getSubject() {
return mHeaders.get(SUBJECT);
}
/**
* Retrieve the body line from the parsed mailto URL.
* If no body line was specified, then null is return
* @return body or null
*/
public String getBody() {
return mHeaders.get(BODY);
}
/**
* Retrieve all the parsed email headers from the mailto URL
* @return map containing all parsed values
*/
public Map<String, String> getHeaders() {
return mHeaders;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(MAILTO_SCHEME);
sb.append('?');
for (Map.Entry<String,String> header : mHeaders.entrySet()) {
sb.append(Uri.encode(header.getKey()));
sb.append('=');
sb.append(Uri.encode(header.getValue()));
sb.append('&');
}
return sb.toString();
}
/**
* Private constructor. The only way to build a Mailto object is through
* the parse() method.
*/
private MailTo() {
mHeaders = new HashMap<String, String>();
}
}
@@ -0,0 +1,775 @@
/*
* Copyright (C) 2008 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.net;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.RemoteException;
import android.os.Handler;
import android.os.ServiceManager;
import android.os.IBinder;
import android.os.INetworkManagementService;
import com.android.internal.net.IPVersion;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.Phone.DataState;
import android.net.NetworkInfo.DetailedState;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.text.TextUtils;
/**
* Track the state of mobile data connectivity. This is done by
* receiving broadcast intents from the Phone process whenever
* the state of data connectivity changes.
*
* {@hide}
*/
public class MobileDataStateTracker extends NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
private static final boolean DBG = false;
private ITelephony mPhoneService;
private String mApnType;
private String mApnTypeToWatchFor;
class MobileInfo {
String mInterfaceName = null;
Phone.DataState mState = DataState.DISCONNECTED;
String mApnName = null;
InetAddress mIpAddress = null;
InetAddress mGateway = null;
public String toString() {
StringBuilder r = new StringBuilder();
r.append("[");
r.append("state=").append(mState).append(", ");
r.append("iface=").append(mInterfaceName).append(", ");
r.append("mApnName=").append(mApnName).append(", ");
r.append("mIpAddress=").append(mIpAddress).append(", ");
r.append("mGateway=").append(mGateway);
r.append("]");
return r.toString();
}
}
HashMap<IPVersion, MobileInfo> mMobileInfo;
private boolean mEnabled;
private BroadcastReceiver mStateReceiver;
// DEFAULT and HIPRI are the same connection. If we're one of these we need to check if
// the other is also disconnected before we reset sockets
private boolean mIsDefaultOrHipri = false;
/**
* Create a new MobileDataStateTracker
* @param context the application context of the caller
* @param target a message handler for getting callbacks about state changes
* @param netType the ConnectivityManager network type
* @param apnType the Phone apnType
* @param tag the name of this network
*/
public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
super(context, target, netType,
TelephonyManager.getDefault().getNetworkType(), tag,
TelephonyManager.getDefault().getNetworkTypeName());
mApnType = networkTypeToApnType(netType);
mApnTypeToWatchFor = mApnType;
if (netType == ConnectivityManager.TYPE_MOBILE ||
netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
mIsDefaultOrHipri = true;
}
mPhoneService = null;
if(netType == ConnectivityManager.TYPE_MOBILE) {
mEnabled = true;
} else {
mEnabled = false;
}
logv("instance created. netType=" + netType + ", mApnType=" + mApnType
+ ", mApnTypeToWatchFor=" + mApnTypeToWatchFor);
mMobileInfo= new HashMap<IPVersion, MobileInfo>();
mMobileInfo.put(IPVersion.INET, new MobileInfo());
mMobileInfo.put(IPVersion.INET6, new MobileInfo());
}
/**
* Begin monitoring mobile data connectivity.
*/
public void startMonitoring() {
/* TODO: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED intent is broadcasted
* once for each <apn_type, ipVersion>. This intent is sticky, so the last
* intent sent out is cached. But this may not be the intent that this instance
* of mobile data state tracker is interested in. One way to fix this would be by
* querying data connection tracker directly at startup - but no such interface exists
* today!
*/
IntentFilter filter =
new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
mStateReceiver = new MobileDataStateReceiver();
Intent intent = mContext.registerReceiver(mStateReceiver, filter);
if (intent != null && intent.getAction().equals(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
IPVersion ipv = getIpVersionFromIntent(intent);
mMobileInfo.get(ipv).mState = getMobileDataState(intent);
}
logv("initial state. v4=" + mMobileInfo.get(IPVersion.INET).mState +
", v6=" + mMobileInfo.get(IPVersion.INET6).mState);
}
private Phone.DataState getMobileDataState(Intent intent) {
String str = intent.getStringExtra(Phone.DATA_APN_TYPE_STATE);
if (str != null) {
String apnTypeList =
intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
if (isApnTypeIncluded(apnTypeList)) {
return Enum.valueOf(Phone.DataState.class, str);
}
}
return Phone.DataState.DISCONNECTED;
}
private IPVersion getIpVersionFromIntent(Intent intent) {
String str = intent.getStringExtra(Phone.DATA_IPVERSION_KEY);
return Enum.valueOf(IPVersion.class, str);
}
private boolean isApnTypeIncluded(String typeList) {
/* comma seperated list - split and check */
if (typeList == null)
return false;
String[] list = typeList.split(",");
for(int i=0; i< list.length; i++) {
if (TextUtils.equals(list[i], mApnTypeToWatchFor) ||
TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
return true;
}
}
return false;
}
private class MobileDataStateReceiver extends BroadcastReceiver {
ConnectivityManager mConnectivityManager;
public void onReceive(Context context, Intent intent) {
synchronized(this) {
// update state and roaming before we set the state - only state changes are
// noticed
TelephonyManager tm = TelephonyManager.getDefault();
int subtype = tm.getNetworkType();
int oldSubtype = mNetworkInfo.getSubtype();
setRoamingStatus(tm.isNetworkRoaming());
if (subtype != oldSubtype) {
logd("subType changed, oldSubtype = " + oldSubtype
+ "new subtype = " + subtype);
mNetworkInfo.setSubtype(subtype, tm.getNetworkTypeName());
}
if (intent.getAction().equals(TelephonyIntents.
ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
// set this regardless of the apnTypeList or IpVersion. It's
// all the same radio/network underneath
mNetworkInfo.setIsAvailable(!unavailable);
// Do not process intents if disabled
if (mEnabled == false)
return;
if (isApnTypeIncluded(apnTypeList) == false)
return; //not what we are looking for.
boolean doReset = true;
if (mIsDefaultOrHipri == true) {
// both default and hipri must go down before we reset
int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
ConnectivityManager.TYPE_MOBILE_HIPRI :
ConnectivityManager.TYPE_MOBILE);
if (mConnectivityManager == null) {
mConnectivityManager =
(ConnectivityManager)context.getSystemService(
Context.CONNECTIVITY_SERVICE);
}
if (mConnectivityManager != null) {
NetworkInfo info = mConnectivityManager.getNetworkInfo(
typeToCheck);
if (info != null && info.isConnected() == true) {
doReset = false;
}
}
//TODO: doReset is not handled!() - FIX THIS!
}
boolean needDetailedStateUpdate = updateMobileInfoFromIntent(intent);
if (needDetailedStateUpdate == false) {
return;
}
String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
/*
* We keep separate states for v4 and v6 in mobile data state tracker, but
* mNetworkinfo needs just one state. So we say CONNECTED if either v4 or v6
* is connected. It doesn't matter which apnName is used though!
*/
DataState state = getMobileDataState(intent);
if (mMobileInfo.get(IPVersion.INET).mState == DataState.CONNECTED
|| mMobileInfo.get(IPVersion.INET6).mState == DataState.CONNECTED) {
state = DataState.CONNECTED;
} else if (mMobileInfo.get(IPVersion.INET).mState == DataState.SUSPENDED
|| mMobileInfo.get(IPVersion.INET6).mState == DataState.SUSPENDED) {
state = DataState.SUSPENDED;
} else if (mMobileInfo.get(IPVersion.INET).mState == DataState.CONNECTING
|| mMobileInfo.get(IPVersion.INET6).mState == DataState.CONNECTING) {
state = DataState.CONNECTING;
}
String extraInfo = null;
String ipv4Apn = null;
String ipv6Apn = null;
if (mMobileInfo.get(IPVersion.INET).mState == DataState.CONNECTED) {
extraInfo = mMobileInfo.get(IPVersion.INET).mApnName;
ipv4Apn = mMobileInfo.get(IPVersion.INET).mApnName;
}
if (mMobileInfo.get(IPVersion.INET6).mState == DataState.CONNECTED) {
ipv6Apn = mMobileInfo.get(IPVersion.INET6).mApnName;
}
if (needDetailedStateUpdate) {
switch (state) {
case DISCONNECTED:
if(isTeardownRequested()) {
//DISCONNECTED as a result of tear down
mEnabled = false;
setTeardownRequested(false);
}
setDetailedState(DetailedState.DISCONNECTED,
false, false, reason, extraInfo, ipv4Apn, ipv6Apn);
break;
case CONNECTING:
setDetailedState(DetailedState.CONNECTING,
false, false, reason, extraInfo, ipv4Apn, ipv6Apn);
break;
case SUSPENDED:
setDetailedState(DetailedState.SUSPENDED,
false, false, reason, extraInfo, ipv4Apn, ipv6Apn);
break;
case CONNECTED:
setDetailedState(
DetailedState.CONNECTED,
mMobileInfo.get(IPVersion.INET).mState == DataState.CONNECTED,
mMobileInfo.get(IPVersion.INET6).mState == DataState.CONNECTED,
reason, extraInfo, ipv4Apn, ipv6Apn);
break;
}
}
} else if (intent.getAction().
equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
mEnabled = false;
String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
logi("Received " + intent.getAction() + " broadcast" +
reason == null ? "" : "(" + reason + ")");
setDetailedState(DetailedState.FAILED,
mMobileInfo.get(IPVersion.INET).mState == DataState.CONNECTED,
mMobileInfo.get(IPVersion.INET6).mState == DataState.CONNECTED,
reason, apnName, null, null);
}
if (subtype != oldSubtype) {
notifySubtypeChanged(subtype, oldSubtype);
}
}
}
}
private boolean updateMobileInfoFromIntent(Intent intent) {
DataState newState = getMobileDataState(intent);
IPVersion ipv = getIpVersionFromIntent(intent);
logi("dc state change intent received for " + mApnType + "/" + ipv
+ " with state " + newState + ". enabled=" + mEnabled);
if (mMobileInfo.get(ipv).mState == newState) {
// no change - nothing needs to be done.
return false;
}
MobileInfo newInfo = mMobileInfo.get(ipv);
newInfo.mState = newState;
if (newInfo.mState == DataState.CONNECTED) {
newInfo.mApnName = intent.getStringExtra(Phone.DATA_APN_KEY);
newInfo.mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
try {
newInfo.mIpAddress = InetAddress.getByName(intent
.getStringExtra(Phone.DATA_IP_ADDRESS_KEY));
newInfo.mGateway = InetAddress.getByName(intent
.getStringExtra(Phone.DATA_GW_ADDRESS_KEY));
} catch (UnknownHostException e) {
loge("interface connected with invalid parameters : ip=" + newInfo.mIpAddress
+ ", gw=" + newInfo.mGateway);
}
} else {
if (newInfo.mState == DataState.DISCONNECTED) {
if (newInfo.mInterfaceName != null) {
NetworkUtils.resetConnections(mMobileInfo.get(ipv).mInterfaceName);
}
/*
* When network disconnects the data call, the routing table
* entries corresponding to this interface are removed
* automatically - update our flags to reflect this. Ideally
* connectivity service should do this, but it may not if the
* other IP version is active.
*/
if (!(mApnType.equals(Phone.APN_TYPE_DEFAULT))) {
removePrivateDnsRoutes(ipv);
}
}
}
logv("updated mobile state info for " + ipv + " : " + mMobileInfo.get(ipv));
return true;
}
private void getPhoneService(boolean forceRefresh) {
if ((mPhoneService == null) || forceRefresh) {
mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
}
}
/**
* Report whether data connectivity is possible.
*/
public boolean isAvailable() {
getPhoneService(false);
/*
* If the phone process has crashed in the past, we'll get a
* RemoteException and need to re-reference the service.
*/
for (int retry = 0; retry < 2; retry++) {
if (mPhoneService == null) break;
try {
return mPhoneService.isDataConnectivityPossible();
} catch (RemoteException e) {
// First-time failed, get the phone service again
if (retry == 0) getPhoneService(true);
}
}
return false;
}
/**
* {@inheritDoc}
* The mobile data network subtype indicates what generation network technology is in effect,
* e.g., GPRS, EDGE, UMTS, etc.
*/
public int getNetworkSubtype() {
return TelephonyManager.getDefault().getNetworkType();
}
/**
* Return the system properties name associated with the tcp buffer sizes
* for this network.
*/
public String getTcpBufferSizesPropName() {
String networkTypeStr = "unknown";
TelephonyManager tm = new TelephonyManager(mContext);
//TODO We have to edit the parameter for getNetworkType regarding CDMA
switch(tm.getNetworkType()) {
case TelephonyManager.NETWORK_TYPE_GPRS:
networkTypeStr = "gprs";
break;
case TelephonyManager.NETWORK_TYPE_EDGE:
networkTypeStr = "edge";
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
networkTypeStr = "umts";
break;
case TelephonyManager.NETWORK_TYPE_HSDPA:
networkTypeStr = "hsdpa";
break;
case TelephonyManager.NETWORK_TYPE_HSUPA:
networkTypeStr = "hsupa";
break;
case TelephonyManager.NETWORK_TYPE_HSPA:
networkTypeStr = "hspa";
break;
case TelephonyManager.NETWORK_TYPE_CDMA:
networkTypeStr = "cdma";
break;
case TelephonyManager.NETWORK_TYPE_1xRTT:
networkTypeStr = "1xrtt";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_0:
networkTypeStr = "evdo";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_A:
networkTypeStr = "evdo";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_B:
networkTypeStr = "evdo_b";
break;
case TelephonyManager.NETWORK_TYPE_EHRPD:
networkTypeStr = "ehrpd";
break;
case TelephonyManager.NETWORK_TYPE_LTE:
networkTypeStr = "lte";
}
return "net.tcp.buffersize." + networkTypeStr;
}
/**
* Tear down mobile data connectivity, i.e., disable the ability to create
* mobile data connections.
*/
@Override
public boolean teardown() {
// since we won't get a notification currently (TODO - per APN notifications)
// we won't get a disconnect message until all APN's on the current connection's
// APN list are disabled. That means privateRoutes for DNS and such will remain on -
// not a problem since that's all shared with whatever other APN is still on, but
// ugly.
setTeardownRequested(true);
return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
}
public void resetTornDownbyConnMgr() {
}
/**
* Re-enable mobile data connectivity after a {@link #teardown()}.
*/
public boolean reconnect() {
setTeardownRequested(false);
/*
* enable first, so that intents are processed, as soon as
* setEnableApn() is called
*/
mEnabled = true;
/*
* Following will force a connectivity action event to be send, even if
* state change has not occurred.
*/
mMobileInfo.get(IPVersion.INET).mState = DataState.CONNECTING;
mMobileInfo.get(IPVersion.INET6).mState = DataState.CONNECTING;
switch (setEnableApn(mApnType, true)) {
case Phone.APN_ALREADY_ACTIVE:
logv("dct reports apn already active. " + this);
//we will be sent intents again.
break;
case Phone.APN_REQUEST_STARTED:
logv("dct reports apn request started " + this);
// no need to do anything - we're already due some status update
// intents
break;
case Phone.APN_REQUEST_FAILED:
logv("dct reports apn request failed " + this);
if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
// on startup we may try to talk to the phone before it's ready
// since the phone will come up enabled, go with that.
// TODO - this also comes up on telephony crash: if we think mobile data is
// off and the telephony stuff crashes and has to restart it will come up
// enabled (making a data connection). We will then be out of sync.
// A possible solution is a broadcast when telephony restarts.
mEnabled = true;
return false;
}
// else fall through
case Phone.APN_TYPE_NOT_AVAILABLE:
// Default is always available, but may be off due to
// AirplaneMode or E-Call or whatever..
logv("dct reports apn type not available " + this);
if (mApnType != Phone.APN_TYPE_DEFAULT) {
mEnabled = false;
}
break;
default:
Log.e(TAG, "Error in reconnect - unexpected response.");
mEnabled = false;
break;
}
return mEnabled;
}
/**
* Turn on or off the mobile radio. No connectivity will be possible while the
* radio is off. The operation is a no-op if the radio is already in the desired state.
* @param turnOn {@code true} if the radio should be turned on, {@code false} if
*/
public boolean setRadio(boolean turnOn) {
getPhoneService(false);
/*
* If the phone process has crashed in the past, we'll get a
* RemoteException and need to re-reference the service.
*/
for (int retry = 0; retry < 2; retry++) {
if (mPhoneService == null) {
Log.w(TAG,
"Ignoring mobile radio request because could not acquire PhoneService");
break;
}
try {
return mPhoneService.setRadio(turnOn);
} catch (RemoteException e) {
if (retry == 0) getPhoneService(true);
}
}
Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
return false;
}
/**
* Tells the phone sub-system that the caller wants to
* begin using the named feature. The only supported features at
* this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
* to specify that it wants to send and/or receive MMS data, and
* {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
* @param feature the name of the feature to be used
* @param callingPid the process ID of the process that is issuing this request
* @param callingUid the user ID of the process that is issuing this request
* @return an integer value representing the outcome of the request.
* The interpretation of this value is feature-specific.
* specific, except that the value {@code -1}
* always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
* the other possible return values are
* <ul>
* <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
* <li>{@code Phone.APN_REQUEST_STARTED}</li>
* <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
* <li>{@code Phone.APN_REQUEST_FAILED}</li>
* </ul>
*/
public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
return -1;
}
/**
* Tells the phone sub-system that the caller is finished
* using the named feature. The only supported feature at
* this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
* to specify that it wants to send and/or receive MMS data.
* @param feature the name of the feature that is no longer needed
* @param callingPid the process ID of the process that is issuing this request
* @param callingUid the user ID of the process that is issuing this request
* @return an integer value representing the outcome of the request.
* The interpretation of this value is feature-specific, except that
* the value {@code -1} always indicates failure.
*/
public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
return -1;
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the mobile data network.
* @param hostAddress the IP address of the host to which the route is desired.
* @return {@code true} on success, {@code false} on failure
*/
@Override
public boolean requestRouteToHost(InetAddress hostAddress) {
String interfaceName = null;
if (hostAddress instanceof Inet4Address) {
interfaceName = getInterfaceName(IPVersion.INET);
} else if (hostAddress instanceof Inet6Address) {
interfaceName = getInterfaceName(IPVersion.INET6);
}
logv("Requested host route to " + hostAddress.getHostAddress() +
" for " + mApnType + "(" + interfaceName + ")");
boolean result = false;
if (interfaceName != null) {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return false;
} else {
try {
result = nms.addDstRoute(interfaceName, hostAddress.getHostAddress(), null);
} catch (RemoteException e) {
Log.w(TAG, "MobileDataStateTracker failed to request host route. Exception: " + e);
}
}
}
return result;
}
/**
* Return the IP addresses of the DNS servers available for the current
* network interface.
* @return a list of DNS addresses, with no holes.
*/
@Override
public String[] getNameServers() {
//null interfaces are fine - taken care of by getNameServerList()
String[] dnsPropNames = new String[] {
/* static list - emulator etc.. */
"net.eth0.dns1",
"net.eth0.dns2",
"net.eth0.dns3",
"net.eth0.dns4",
"net.gprs.dns1",
"net.gprs.dns2",
"net.ppp0.dns1",
"net.ppp0.dns2",
/* dynamic */
"net." + getInterfaceName(IPVersion.INET) + ".dns1",
"net." + getInterfaceName(IPVersion.INET) + ".dns2",
"net." + getInterfaceName(IPVersion.INET6) + ".dns1",
"net." + getInterfaceName(IPVersion.INET6) + ".dns2"
};
return getNameServerList(dnsPropNames);
}
@Override
public String getInterfaceName(IPVersion ipv) {
return mMobileInfo.get(ipv).mInterfaceName;
}
@Override
public InetAddress getGateway(IPVersion ipv) {
return mMobileInfo.get(ipv).mGateway;
}
@Override
public InetAddress getIpAdress(IPVersion ipv) {
return mMobileInfo.get(ipv).mIpAddress;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("Mobile data state: IPV4=");
sb.append(mMobileInfo.get(IPVersion.INET));
sb.append(", IPV6=");
sb.append(mMobileInfo.get(IPVersion.INET6));
return sb.toString();
}
/**
* Internal method supporting the ENABLE_MMS feature.
* @param apnType the type of APN to be enabled or disabled (e.g., mms)
* @param enable {@code true} to enable the specified APN type,
* {@code false} to disable it.
* @return an integer value representing the outcome of the request.
*/
private int setEnableApn(String apnType, boolean enable) {
getPhoneService(false);
/*
* If the phone process has crashed in the past, we'll get a
* RemoteException and need to re-reference the service.
*/
for (int retry = 0; retry < 2; retry++) {
if (mPhoneService == null) {
Log.w(TAG,
"Ignoring feature request because could not acquire PhoneService");
break;
}
try {
if (enable) {
return mPhoneService.enableApnType(apnType);
} else {
return mPhoneService.disableApnType(apnType);
}
} catch (RemoteException e) {
if (retry == 0) getPhoneService(true);
}
}
Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
+ " APN type \"" + apnType + "\"");
return Phone.APN_REQUEST_FAILED;
}
public static String networkTypeToApnType(int netType) {
switch(netType) {
case ConnectivityManager.TYPE_MOBILE:
return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these
case ConnectivityManager.TYPE_MOBILE_MMS:
return Phone.APN_TYPE_MMS;
case ConnectivityManager.TYPE_MOBILE_SUPL:
return Phone.APN_TYPE_SUPL;
case ConnectivityManager.TYPE_MOBILE_DUN:
return Phone.APN_TYPE_DUN;
case ConnectivityManager.TYPE_MOBILE_HIPRI:
return Phone.APN_TYPE_HIPRI;
default:
Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
return null;
}
}
void loge(String string) {
Log.e(TAG, "[" + mApnType + "] " + string);
}
void logw(String string) {
Log.w(TAG, "[" + mApnType + "] " + string);
}
void logd(String string) {
Log.d(TAG, "[" + mApnType + "] " + string);
}
void logv(String string) {
if (DBG)
Log.v(TAG, "[" + mApnType + "] " + string);
}
void logi(String string) {
if (DBG)
Log.i(TAG, "[" + mApnType + "] " + string);
}
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2007, 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.net;
parcelable NetworkInfo;
+481
View File
@@ -0,0 +1,481 @@
/*
* Copyright (C) 2008 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.net;
import android.os.Parcelable;
import android.os.Parcel;
import java.util.EnumMap;
/**
* Describes the status of a network interface of a given type
* (currently either Mobile or Wifi).
*/
public class NetworkInfo implements Parcelable {
/**
* Coarse-grained network state. This is probably what most applications should
* use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}.
* The mapping between the two is as follows:
* <br/><br/>
* <table>
* <tr><td><b>Detailed state</b></td><td><b>Coarse-grained state</b></td></tr>
* <tr><td><code>IDLE</code></td><td><code>DISCONNECTED</code></td></tr>
* <tr><td><code>SCANNING</code></td><td><code>CONNECTING</code></td></tr>
* <tr><td><code>CONNECTING</code></td><td><code>CONNECTING</code></td></tr>
* <tr><td><code>AUTHENTICATING</code></td><td><code>CONNECTING</code></td></tr>
* <tr><td><code>CONNECTED</code></td><td<code>CONNECTED</code></td></tr>
* <tr><td><code>DISCONNECTING</code></td><td><code>DISCONNECTING</code></td></tr>
* <tr><td><code>DISCONNECTED</code></td><td><code>DISCONNECTED</code></td></tr>
* <tr><td><code>UNAVAILABLE</code></td><td><code>DISCONNECTED</code></td></tr>
* <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
* </table>
*/
public enum State {
CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
}
/**
* The fine-grained state of a network connection. This level of detail
* is probably of interest to few applications. Most should use
* {@link android.net.NetworkInfo.State State} instead.
*/
public enum DetailedState {
/** Ready to start data connection setup. */
IDLE,
/** Searching for an available access point. */
SCANNING,
/** Currently setting up data connection. */
CONNECTING,
/** Network link established, performing authentication. */
AUTHENTICATING,
/** Awaiting response from DHCP server in order to assign IP address information. */
OBTAINING_IPADDR,
/** IP traffic should be available. */
CONNECTED,
/** IP traffic is suspended */
SUSPENDED,
/** Currently tearing down data connection. */
DISCONNECTING,
/** IP traffic not available. */
DISCONNECTED,
/** Attempt to connect failed. */
FAILED
}
/**
* This is the map described in the Javadoc comment above. The positions
* of the elements of the array must correspond to the ordinal values
* of <code>DetailedState</code>.
*/
private static final EnumMap<DetailedState, State> stateMap =
new EnumMap<DetailedState, State>(DetailedState.class);
static {
stateMap.put(DetailedState.IDLE, State.DISCONNECTED);
stateMap.put(DetailedState.SCANNING, State.DISCONNECTED);
stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
}
private int mNetworkType;
private int mSubtype;
private String mTypeName;
private String mSubtypeName;
private State mState;
private DetailedState mDetailedState;
private String mReason;
private String mExtraInfo;
private boolean mIsFailover;
private boolean mIsRoaming;
private boolean mIsIpV4Connected;
private String mIpv4Interface;
private boolean mIsIpV6Connected;
private String mIpv6Interface;
private String mIpv4Apn;
private String mIpv6Apn;
/**
* Indicates whether network connectivity is possible:
*/
private boolean mIsAvailable;
/**
* @param type network type
* @deprecated
* @hide because this constructor was only meant for internal use (and
* has now been superseded by the package-private constructor below).
*/
public NetworkInfo(int type) {}
NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
if (!ConnectivityManager.isNetworkTypeValid(type)) {
throw new IllegalArgumentException("Invalid network type: " + type);
}
mNetworkType = type;
mSubtype = subtype;
mTypeName = typeName;
mSubtypeName = subtypeName;
setDetailedState(DetailedState.IDLE, false, false, null, null, null, null, null, null);
mState = State.UNKNOWN;
mIsAvailable = false; // until we're told otherwise, assume unavailable
mIsRoaming = false;
}
/**
* Reports the type of network (currently mobile or Wi-Fi) to which the
* info in this object pertains.
* @return the network type
*/
public int getType() {
return mNetworkType;
}
/**
* Return a network-type-specific integer describing the subtype
* of the network.
* @return the network subtype
*/
public int getSubtype() {
return mSubtype;
}
void setSubtype(int subtype, String subtypeName) {
mSubtype = subtype;
mSubtypeName = subtypeName;
}
/**
* Return a human-readable name describe the type of the network,
* for example "WIFI" or "MOBILE".
* @return the name of the network type
*/
public String getTypeName() {
return mTypeName;
}
/**
* Return a human-readable name describing the subtype of the network.
* @return the name of the network subtype
*/
public String getSubtypeName() {
return mSubtypeName;
}
/**
* Indicates whether network connectivity exists or is in the process
* of being established. This is good for applications that need to
* do anything related to the network other than read or write data.
* For the latter, call {@link #isConnected()} instead, which guarantees
* that the network is fully usable.
* @return {@code true} if network connectivity exists or is in the process
* of being established, {@code false} otherwise.
*/
public boolean isConnectedOrConnecting() {
return mState == State.CONNECTED || mState == State.CONNECTING;
}
/**
* Indicates whether network connectivity exists and it is possible to establish
* connections and pass data.
* @return {@code true} if network connectivity exists, {@code false} otherwise.
*/
public boolean isConnected() {
return mState == State.CONNECTED;
}
/**
* Indicates whether ipv4 network connectivity exists and it is possible to establish
* connections and pass ipv4 data.
* @return {@code true} if network connectivity through ipv4 exists, {@code false} otherwise.
*
* @hide
*/
public boolean isIpv4Connected() {
if (isConnected()) {
return mIsIpV4Connected;
}
return false;
}
/**
* Provides the name of the interface on which ipv4 connectivity for this network is enabled
* @return Name of the interface. Null if IPv4 not active for this network
*/
public String getIpv4Interface() {
return mIpv4Interface;
}
/**
* Indicates whether ipv6 network connectivity exists and it is possible to establish
* connections and pass ipv6 data.
* @return {@code true} if network connectivity through ipv6 exists, {@code false} otherwise.
*
* @hide
*/
public boolean isIpv6Connected() {
if (isConnected()) {
return mIsIpV6Connected;
}
return false;
}
/**
* Provides the name of the interface on which IPv6 connectivity for this network is enabled
* @return Name of the interface. Null if IPv6 not active for this network
*/
public String getIpv6Interface() {
return mIpv6Interface;
}
/**
* Indicates whether network connectivity is possible. A network is unavailable
* when a persistent or semi-persistent condition prevents the possibility
* of connecting to that network. Examples include
* <ul>
* <li>The device is out of the coverage area for any network of this type.</li>
* <li>The device is on a network other than the home network (i.e., roaming), and
* data roaming has been disabled.</li>
* <li>The device's radio is turned off, e.g., because airplane mode is enabled.</li>
* </ul>
* @return {@code true} if the network is available, {@code false} otherwise
*/
public boolean isAvailable() {
return mIsAvailable;
}
/**
* Sets if the network is available, ie, if the connectivity is possible.
* @param isAvailable the new availability value.
*
* @hide
*/
public void setIsAvailable(boolean isAvailable) {
mIsAvailable = isAvailable;
}
/**
* Indicates whether the current attempt to connect to the network
* resulted from the ConnectivityManager trying to fail over to this
* network following a disconnect from another network.
* @return {@code true} if this is a failover attempt, {@code false}
* otherwise.
*/
public boolean isFailover() {
return mIsFailover;
}
/**
* Set the failover boolean.
* @param isFailover {@code true} to mark the current connection attempt
* as a failover.
* @hide
*/
public void setFailover(boolean isFailover) {
mIsFailover = isFailover;
}
/**
* Indicates whether the device is currently roaming on this network.
* When {@code true}, it suggests that use of data on this network
* may incur extra costs.
* @return {@code true} if roaming is in effect, {@code false} otherwise.
*/
public boolean isRoaming() {
return mIsRoaming;
}
void setRoaming(boolean isRoaming) {
mIsRoaming = isRoaming;
}
/**
* Reports the current coarse-grained state of the network.
* @return the coarse-grained state
*/
public State getState() {
return mState;
}
/**
* Reports the current fine-grained state of the network.
* @return the fine-grained state
*/
public DetailedState getDetailedState() {
return mDetailedState;
}
/**
* Sets the fine-grained state of the network.
* @param detailedState the {@link DetailedState}.
* @param reason a {@code String} indicating the reason for the state change,
* if one was supplied. May be {@code null}.
* @param extraInfo an optional {@code String} providing additional network state
* information passed up from the lower networking layers.
*/
void setDetailedState(DetailedState detailedState, boolean isIpv4Connected,
boolean isIpv6Connected, String reason, String extraInfo,
String ipv4Apn, String ipv6Apn,
String ipv4Interface, String ipv6Interface) {
this.mDetailedState = detailedState;
this.mState = stateMap.get(detailedState);
this.mReason = reason;
this.mExtraInfo = extraInfo;
this.mIpv4Apn = ipv4Apn;
this.mIpv6Apn = ipv6Apn;
if (this.mState != State.CONNECTED) {
this.mIsIpV4Connected = false;
this.mIsIpV6Connected = false;
this.mIpv4Interface = null;
this.mIpv6Interface = null;
} else {
this.mIsIpV4Connected = isIpv4Connected;
this.mIpv4Interface = ipv4Interface;
this.mIsIpV6Connected = isIpv6Connected;
this.mIpv6Interface = ipv6Interface;
}
}
/**
* Report the reason an attempt to establish connectivity failed,
* if one is available.
* @return the reason for failure, or null if not available
*/
public String getReason() {
return mReason;
}
/**
* Report the extra information about the network state, if any was
* provided by the lower networking layers.,
* if one is available.
* @return the extra information, or null if not available
*/
public String getExtraInfo() {
return mExtraInfo;
}
/**
* Report the ipv4 apn used to establish the data call
* if one is available.
* @return the ipv4 apn, or null if not available
* @hide
*/
public String getIpv4Apn() {
return mIpv4Apn;
}
/**
* Report the ipv6 apn used to establish the data call
* if one is available.
* @return the ipv6 apn, or null if not available
* @hide
*/
public String getIpv6Apn() {
return mIpv6Apn;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("NetworkInfo: ");
builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
append("], state: ").append(mState).append("/").append(mDetailedState).
append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
append(", roaming: ").append(mIsRoaming).
append(", failover: ").append(mIsFailover).
append(", isAvailable: ").append(mIsAvailable).
append(", isIpv4Connected: ").append(mIsIpV4Connected).
append(", isIpv6Connected: ").append(mIsIpV6Connected).
append(", ipv4Name: ").append(mIpv4Apn).
append(", ipv6Name: ").append(mIpv6Apn);
return builder.toString();
}
/**
* Implement the Parcelable interface
* @hide
*/
public int describeContents() {
return 0;
}
/**
* Implement the Parcelable interface.
* @hide
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mNetworkType);
dest.writeInt(mSubtype);
dest.writeString(mTypeName);
dest.writeString(mSubtypeName);
dest.writeString(mState.name());
dest.writeString(mDetailedState.name());
dest.writeInt(mIsFailover ? 1 : 0);
dest.writeInt(mIsAvailable ? 1 : 0);
dest.writeInt(mIsRoaming ? 1 : 0);
dest.writeString(mReason);
dest.writeString(mExtraInfo);
dest.writeInt(mIsIpV4Connected? 1 : 0);
dest.writeInt(mIsIpV6Connected? 1 : 0);
dest.writeString(mIpv4Apn);
dest.writeString(mIpv6Apn);
dest.writeString(mIpv4Interface);
dest.writeString(mIpv6Interface);
}
/**
* Implement the Parcelable interface.
* @hide
*/
public static final Creator<NetworkInfo> CREATOR =
new Creator<NetworkInfo>() {
public NetworkInfo createFromParcel(Parcel in) {
int netType = in.readInt();
int subtype = in.readInt();
String typeName = in.readString();
String subtypeName = in.readString();
NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
netInfo.mState = State.valueOf(in.readString());
netInfo.mDetailedState = DetailedState.valueOf(in.readString());
netInfo.mIsFailover = in.readInt() != 0;
netInfo.mIsAvailable = in.readInt() != 0;
netInfo.mIsRoaming = in.readInt() != 0;
netInfo.mReason = in.readString();
netInfo.mExtraInfo = in.readString();
netInfo.mIsIpV4Connected = in.readInt() != 0;
netInfo.mIsIpV6Connected = in.readInt() != 0;
netInfo.mIpv4Apn = in.readString();
netInfo.mIpv6Apn = in.readString();
netInfo.mIpv4Interface = in.readString();
netInfo.mIpv6Interface = in.readString();
return netInfo;
}
public NetworkInfo[] newArray(int size) {
return new NetworkInfo[size];
}
};
}
@@ -0,0 +1,689 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (c) 2010-2011 The Linux Foundation. All rights reserved.
*
* 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.net;
import java.io.FileWriter;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import com.android.internal.net.IPVersion;
import android.net.NetworkInfo.DetailedState;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
/**
* Each subclass of this class keeps track of the state of connectivity
* of a network interface. All state information for a network should
* be kept in a Tracker class. This superclass manages the
* network-type-independent aspects of network state.
*
* {@hide}
*/
public abstract class NetworkStateTracker extends Handler {
protected NetworkInfo mNetworkInfo;
protected Context mContext;
protected Handler mTarget;
private boolean mTeardownRequested;
private static boolean DBG = false;
private static final String TAG = "NetworkStateTracker";
// Ok to start V6 routeId from 127 as V4 Id will never exceed 126 (number of NST
// instances)
private static int v4RouteIdCtr = 1;
private static int v6RouteIdCtr = 127;
private int V4RouteId = 0;
private int V6RouteId = 0;
// Share the event space with ConnectivityService (which we can't see, but
// must send events to). If you change these, change ConnectivityService
// too.
private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
public static final int EVENT_STATE_CHANGED = 1;
public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2;
/**
* arg1: 1 to show, 0 to hide
* arg2: ID of the notification
* obj: Notification (if showing)
*/
public static final int EVENT_NOTIFICATION_CHANGED = 3;
public static final int EVENT_CONFIGURATION_CHANGED = 4;
public static final int EVENT_ROAMING_CHANGED = 5;
public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
public NetworkStateTracker(Context context,
Handler target,
int networkType,
int subType,
String typeName,
String subtypeName) {
super();
mContext = context;
mTarget = target;
mTeardownRequested = false;
V4RouteId = acquireV4RId();
V6RouteId = acquireV6RId();
this.mNetworkInfo = new NetworkInfo(networkType, subType, typeName, subtypeName);
}
public NetworkInfo getNetworkInfo() {
return mNetworkInfo;
}
/**
* Creates unique route Id (custom routing table Id) for INET family per NST
* instance
*/
private synchronized int acquireV4RId() {
return ++v4RouteIdCtr;
}
/**
* Creates unique route Id (custom routing table Id) for INET6 family per NST
* instance
*/
private synchronized int acquireV6RId() {
return ++v6RouteIdCtr;
}
/**
* Return the system properties name associated with the tcp buffer sizes
* for this network.
*/
public abstract String getTcpBufferSizesPropName();
/**
* Return the IP addresses of the DNS servers available for the mobile data
* network interface.
* @return a list of DNS addresses, with no holes.
*/
abstract public String[] getNameServers();
/*
* Return the interface name that supports the specified IP Version.
*/
abstract public String getInterfaceName(IPVersion ipv);
/*
* return the gateway associated with this interface.
*/
abstract public InetAddress getGateway(IPVersion ipv);
/*
* TODO: This should come from native space, rather than relying on
* telephony.
*/
abstract public InetAddress getIpAdress(IPVersion ipv);
/**
* Return the IP addresses of the DNS servers available for this
* network interface.
* @param propertyNames the names of the system properties whose values
* give the IP addresses. Properties with no values are skipped.
* @return an array of {@code String}s containing the IP addresses
* of the DNS servers, in dot-notation. This may have fewer
* non-null entries than the list of names passed in, since
* some of the passed-in names may have empty values.
*/
static protected String[] getNameServerList(String[] propertyNames) {
String[] dnsAddresses = new String[propertyNames.length];
int i, j;
for (i = 0, j = 0; i < propertyNames.length; i++) {
String value = SystemProperties.get(propertyNames[i]);
// The GSM layer sometimes sets a bogus DNS server address of
// 0.0.0.0
if (!TextUtils.isEmpty(value) && !TextUtils.equals(value, "0.0.0.0")) {
dnsAddresses[j++] = value;
}
}
return dnsAddresses;
}
protected INetworkManagementService getNetworkManagementService () {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
return INetworkManagementService.Stub.asInterface(b);
}
boolean mPrivateDnsRouteSet[] = new boolean[] {false, false};
public void addPrivateDnsRoutes() {
addPrivateDnsRoutes(IPVersion.INET);
addPrivateDnsRoutes(IPVersion.INET6);
}
public void addPrivateDnsRoutes(IPVersion ipv) {
String interfaceName = getInterfaceName(ipv);
int index = ipv == IPVersion.INET ? 0 : 1;
if (interfaceName != null && mPrivateDnsRouteSet[index] == false) {
for (String addrString : getNameServers()) {
if (addrString != null) {
try {
InetAddress inetAddress = InetAddress.getByName(addrString);
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
if (ipv == IPVersion.INET && inetAddress instanceof Inet4Address) {
Log.v(TAG, "adding ipv4 dns " + addrString + " through "
+ interfaceName);
nms.addDstRoute(interfaceName, inetAddress.getHostAddress(), null);
} else if (ipv == IPVersion.INET6 && inetAddress instanceof Inet6Address) {
Log.v(TAG, "adding ipv6 dns " + addrString + " through "
+ interfaceName);
nms.addDstRoute(interfaceName, inetAddress.getHostAddress(), null);
}
}
} catch (UnknownHostException e) {
Log.w(TAG, " DNS address " + addrString + " : Exception " + e);
} catch (RemoteException e) {
Log.w(TAG, " DNS address " + addrString + " : Exception " + e);
}
}
}
mPrivateDnsRouteSet[index] = true;
}
}
public void removePrivateDnsRoutes() {
removePrivateDnsRoutes(IPVersion.INET);
removePrivateDnsRoutes(IPVersion.INET6);
}
public void removePrivateDnsRoutes(IPVersion ipv) {
// TODO - we should do this explicitly but the NetUtils api doesnt
// support this yet - must remove all. No worse than before
String interfaceName = getInterfaceName(ipv);
int index = ipv == IPVersion.INET ? 0 : 1;
if (interfaceName != null && mPrivateDnsRouteSet[index]) {
Log.v(TAG, "remove " + ipv + " dns routes for " + mNetworkInfo.getTypeName() + " ("
+ interfaceName + ")");
NetworkUtils.removeHostRoutes(interfaceName);
}
mPrivateDnsRouteSet[index] = false;
}
public void addDefaultRoute() {
addDefaultRoute(IPVersion.INET);
addDefaultRoute(IPVersion.INET6);
}
public void addDefaultRoute(IPVersion ipv) {
String interfaceName = getInterfaceName(ipv);
InetAddress gateway = getGateway(ipv);
int index = ipv == IPVersion.INET ? 0 : 1;
String gwString = (gateway == null) ? "0" : gateway.getHostAddress();
if (interfaceName != null) {
Log.i(TAG, "addDefaultRoute (" + ipv + ") for " + mNetworkInfo.getTypeName() +
" ("+ interfaceName + "), GatewayAddr=" + gwString);
boolean result = false;
try {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
if (index == 0) {
result = nms.replaceV4DefaultRoute(interfaceName, gwString);
} else {
result = nms.replaceV6DefaultRoute(interfaceName, gwString);
}
}
} catch (RemoteException e) {
Log.w(TAG, " NetworkManagementService was unable to add default route. Exception: " + e);
}
if (!result) {
Log.e(TAG, " Unable to add default route.");
}
}
}
public void removeDefaultRoute() {
// do nothing since add is essentially replacing
}
/**
* Reads the network specific TCP buffer sizes from SystemProperties
* net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
* wide use
*/
public void updateNetworkSettings() {
String key = getTcpBufferSizesPropName();
String bufferSizes = SystemProperties.get(key);
if (bufferSizes.length() == 0) {
Log.w(TAG, key + " not found in system properties. Using defaults");
// Setting to default values so we won't be stuck to previous values
key = "net.tcp.buffersize.default";
bufferSizes = SystemProperties.get(key);
}
// Set values in kernel
if (bufferSizes.length() != 0) {
if (DBG) {
Log.v(TAG, "Setting TCP values: [" + bufferSizes
+ "] which comes from [" + key + "]");
}
setBufferSize(bufferSizes);
}
}
/**
* Release the wakelock, if any, that may be held while handling a
* disconnect operation.
*/
public void releaseWakeLock() {
}
/**
* Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
* which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
*
* @param bufferSizes in the format of "readMin, readInitial, readMax,
* writeMin, writeInitial, writeMax"
*/
private void setBufferSize(String bufferSizes) {
try {
String[] values = bufferSizes.split(",");
if (values.length == 6) {
final String prefix = "/sys/kernel/ipv4/tcp_";
stringToFile(prefix + "rmem_min", values[0]);
stringToFile(prefix + "rmem_def", values[1]);
stringToFile(prefix + "rmem_max", values[2]);
stringToFile(prefix + "wmem_min", values[3]);
stringToFile(prefix + "wmem_def", values[4]);
stringToFile(prefix + "wmem_max", values[5]);
} else {
Log.w(TAG, "Invalid buffersize string: " + bufferSizes);
}
} catch (IOException e) {
Log.w(TAG, "Can't set tcp buffer sizes:" + e);
}
}
/**
* Writes string to file. Basically same as "echo -n $string > $filename"
*
* @param filename
* @param string
* @throws IOException
*/
private void stringToFile(String filename, String string) throws IOException {
FileWriter out = new FileWriter(filename);
try {
out.write(string);
} finally {
out.close();
}
}
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
* any listeners.
* @param state the new @{code DetailedState}
*/
public void setDetailedState(NetworkInfo.DetailedState state) {
if (state == DetailedState.CONNECTED) {
/*
* TODO: this function is called by wifi. We assume that if wifi
* says CONNECTED, both v4 and v6 is connected. This may not be true
* always but no other way of knowing this now.
*/
setDetailedState(state, true, true, null, null, null, null);
} else {
setDetailedState(state, false, false, null, null, null, null);
}
}
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
* any listeners.
* @param state the new @{code DetailedState}
* @param reason a {@code String} indicating a reason for the state change,
* if one was supplied. May be {@code null}.
* @param extraInfo optional {@code String} providing extra information about the state change
*/
public void setDetailedState(NetworkInfo.DetailedState state, boolean isIpv4Connected,
boolean isIpv6Connected, String reason, String extraInfo,
String ipv4Apn, String ipv6Apn) {
if (DBG)
Log.d(TAG, "setDetailed state, old =" + mNetworkInfo.getDetailedState()
+ " and new state=" + state);
boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
String lastReason = mNetworkInfo.getReason();
/*
* If a reason was supplied when the CONNECTING state was entered, and no
* reason was supplied for entering the CONNECTED state, then retain the
* reason that was supplied when going to CONNECTING.
*/
if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
&& lastReason != null)
reason = lastReason;
String v4int = getInterfaceName(IPVersion.INET);
String v6int = getInterfaceName(IPVersion.INET6);
mNetworkInfo.setDetailedState(state, isIpv4Connected, isIpv6Connected, reason, extraInfo,
ipv4Apn, ipv6Apn, v4int, v6int);
Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
msg.sendToTarget();
}
protected void setDetailedStateInternal(NetworkInfo.DetailedState state) {
if (state == DetailedState.CONNECTED) {
/*
* TODO: this function is called by wifi. We assume that if wifi
* says CONNECTED, both v4 and v6 is connected. This may not be true
* always but no other way of knowing this now.
*/
String v4int = getInterfaceName(IPVersion.INET);
String v6int = getInterfaceName(IPVersion.INET6);
mNetworkInfo.setDetailedState(state, true, true, null, null, null, null, v4int, v6int);
} else {
mNetworkInfo.setDetailedState(state, false, false, null, null, null, null, null, null);
}
}
public void setTeardownRequested(boolean isRequested) {
mTeardownRequested = isRequested;
}
public boolean isTeardownRequested() {
return mTeardownRequested;
}
/**
* Send a notification that the results of a scan for network access
* points has completed, and results are available.
*/
protected void sendScanResultsAvailable() {
Message msg = mTarget.obtainMessage(EVENT_SCAN_RESULTS_AVAILABLE, mNetworkInfo);
msg.sendToTarget();
}
/**
* Record the roaming status of the device, and if it is a change from the previous
* status, send a notification to any listeners.
* @param isRoaming {@code true} if the device is now roaming, {@code false}
* if it is no longer roaming.
*/
protected void setRoamingStatus(boolean isRoaming) {
if (isRoaming != mNetworkInfo.isRoaming()) {
mNetworkInfo.setRoaming(isRoaming);
Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
msg.sendToTarget();
}
}
protected void setSubtype(int subtype, String subtypeName) {
int oldSubtype = mNetworkInfo.getSubtype();
if (subtype != oldSubtype) {
mNetworkInfo.setSubtype(subtype, subtypeName);
if (mNetworkInfo.isConnected()) {
Message msg = mTarget.obtainMessage(
EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
msg.sendToTarget();
}
}
}
protected void notifySubtypeChanged(int subtype, int oldSubtype) {
if (mNetworkInfo.isConnected() && (subtype != oldSubtype)) {
Message msg = mTarget.obtainMessage(
EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
msg.sendToTarget();
}
}
public abstract void startMonitoring();
/**
* Disable connectivity to a network
* @return {@code true} if a teardown occurred, {@code false} if the
* teardown did not occur.
*/
public abstract boolean teardown();
/**
* Reenable connectivity to a network after a {@link #teardown()}.
*/
public abstract boolean reconnect();
/**
* Reset torn-down by Connection Manager flag.
*/
public abstract void resetTornDownbyConnMgr();
/**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
public abstract boolean setRadio(boolean turnOn);
/**
* Returns an indication of whether this network is available for
* connections. A value of {@code false} means that some quasi-permanent
* condition prevents connectivity to this network.
*/
public abstract boolean isAvailable();
/**
* Tells the underlying networking system that the caller wants to
* begin using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
* @param feature the name of the feature to be used
* @param callingPid the process ID of the process that is issuing this request
* @param callingUid the user ID of the process that is issuing this request
* @return an integer value representing the outcome of the request.
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*/
public abstract int startUsingNetworkFeature(String feature, int callingPid, int callingUid);
/**
* Tells the underlying networking system that the caller is finished
* using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
* @param feature the name of the feature that is no longer needed.
* @param callingPid the process ID of the process that is issuing this request
* @param callingUid the user ID of the process that is issuing this request
* @return an integer value representing the outcome of the request.
* The interpretation of this value is specific to each networking
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*/
public abstract int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via this network interface.
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
*/
public boolean requestRouteToHost(InetAddress hostAddress) {
return false;
}
/**
* Interprets scan results. This will be called at a safe time for
* processing, and from a safe thread.
*/
public void interpretScanResultsAvailable() {
}
/**
* Adds a source policy based routes and corresponding rules for INET and INET6 addresses of an
* interface. If the route already exists, then it will replace the previous route-rule entry
* in rpdb.
*/
public void addSrcRoutes() {
replaceV4SrcRoute();
replaceV6SrcRoute();
}
public void addSrcRoute(IPVersion ipv) {
if (ipv == null) {
Log.w(TAG, "ipversion is null; cannot add src route");
} else if (ipv == IPVersion.INET6) {
replaceV6SrcRoute();
} else if (ipv == IPVersion.INET) {
replaceV4SrcRoute();
} else {
Log.w(TAG,"ipversion not recognized; cannot add src route");
}
}
public void replaceV4SrcRoute() {
InetAddress ipAddr = getIpAdress(IPVersion.INET);
InetAddress gatewayAddr = getGateway(IPVersion.INET);
String interfaceName = getInterfaceName(IPVersion.INET);
String gwString = (gatewayAddr == null) ? "0" : gatewayAddr.getHostAddress();
if ((interfaceName != null) && (ipAddr != null)) {
Log.i(TAG, "replaceV4SrcRoute for " + mNetworkInfo.getTypeName() +
" ("+ interfaceName + "), GatewayAddr=" + gwString + ", IPAddr =" + ipAddr.getHostAddress() +
"V4RouteId =" + V4RouteId);
try {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
nms.replaceV4SrcRoute(interfaceName, ipAddr.getHostAddress(), gwString, V4RouteId);
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to replace V4 srouce route. Exception: " + e);
}
}
}
public void replaceV6SrcRoute() {
InetAddress ipAddr = getIpAdress(IPVersion.INET6);
InetAddress gatewayAddr = getGateway(IPVersion.INET6);
String interfaceName = getInterfaceName(IPVersion.INET6);
String gwString = (gatewayAddr == null) ? "0" : gatewayAddr.getHostAddress();
if ((interfaceName != null) && (ipAddr != null)) {
Log.i(TAG, "replaceV6SrcRoute for " + mNetworkInfo.getTypeName() +
" ("+ interfaceName + "), GatewayAddr=" + gwString + ", IPAddr =" + ipAddr.getHostAddress() +
"V6RouteId =" + V6RouteId);
try {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
nms.replaceV6SrcRoute(interfaceName, ipAddr.getHostAddress(), gwString, V6RouteId);
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to replace V6 srouce route. Exception: " + e);
}
}
}
public void delSrcRoutes() {
delV4SrcRoute();
delV6SrcRoute();
}
public void delSrcRoute(IPVersion ipv) {
if (ipv == null) {
Log.w(TAG,"ipversion is null; cannot delete src route");
} else if (ipv == IPVersion.INET6) {
delV6SrcRoute();
} else if (ipv == IPVersion.INET) {
delV4SrcRoute();
} else {
Log.w(TAG,"ipversion is not recognized; cannot delete src route");
}
}
public void delV4SrcRoute() {
InetAddress ipAddr = getIpAdress(IPVersion.INET);
String interfaceName = getInterfaceName(IPVersion.INET);
if ((interfaceName != null) && (ipAddr != null)) {
Log.i(TAG, "delV4SrcRoute for " + mNetworkInfo.getTypeName() +
",InterfaceName=" + interfaceName + ",IPAddr =" + ipAddr.getHostAddress() +
",V4RouteId =" + V4RouteId);
}
try {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
nms.delV4SrcRoute(V4RouteId);
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to delete v4 source route. Exception: " + e);
}
}
public void delV6SrcRoute() {
InetAddress ipAddr = getIpAdress(IPVersion.INET6);
String interfaceName = getInterfaceName(IPVersion.INET6);
if ((interfaceName != null) && (ipAddr != null)) {
Log.i(TAG, "delV6SrcRoute for " + mNetworkInfo.getTypeName() +
",InterfaceName=" + interfaceName + ",IPAddr =" + ipAddr.getHostAddress() +
",V6RouteId =" + V6RouteId);
}
try {
INetworkManagementService nms = getNetworkManagementService();
if (nms == null) {
Log.w(TAG, "could not acquire NetworkManagementService.");
return;
} else {
nms.delV6SrcRoute(V6RouteId);
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to delete v6 source route. Exception: " + e);
}
}
}
@@ -0,0 +1,263 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (c) 2011 The Linux Foundation. All rights reserved.
*
* 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.net;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import android.util.Log;
import android.text.TextUtils;
/**
* Native methods for managing network interfaces.
*
* {@hide}
*/
public class NetworkUtils {
private static final String TAG = "NetworkUtils";
/** Bring the named network interface up. */
public native static int enableInterface(String interfaceName);
/** Bring the named network interface down. */
public native static int disableInterface(String interfaceName);
/** Add a route to the specified host or gateway via the named interface. */
public native static int addRoute(String interfaceName, String hostAddr, int prefixLength);
/** Return the gateway address for the default route for the named interface. */
public native static int getDefaultRoute(String interfaceName);
/** Remove host routes that uses the named interface. */
public native static int removeHostRoutes(String interfaceName);
/** Remove the default route for the named interface. */
public native static int removeDefaultRoute(String interfaceName);
/** Reset any sockets that are connected via the named interface. */
public native static int resetConnections(String interfaceName);
/**
* Start the DHCP client daemon, in order to have it request addresses
* for the named interface, and then configure the interface with those
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
* @param interfaceName the name of the interface to configure
* @param ipInfo if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
public native static boolean runDhcp(String interfaceName, DhcpInfo ipInfo);
/**
* Shut down the DHCP client daemon.
* @param interfaceName the name of the interface for which the daemon
* should be stopped
* @return {@code true} for success, {@code false} for failure
*/
public native static boolean stopDhcp(String interfaceName);
/**
* Release the current DHCP lease.
* @param interfaceName the name of the interface for which the lease should
* be released
* @return {@code true} for success, {@code false} for failure
*/
public native static boolean releaseDhcpLease(String interfaceName);
/**
* Return the last DHCP-related error message that was recorded.
* <p/>NOTE: This string is not localized, but currently it is only
* used in logging.
* @return the most recent error message, if any
*/
public native static String getDhcpError();
/**
* When static IP configuration has been specified, configure the network
* interface according to the values supplied.
* @param interfaceName the name of the interface to configure
* @param ipInfo the IP address, default gateway, and DNS server addresses
* with which to configure the interface.
* @return {@code true} for success, {@code false} for failure
*/
public static boolean configureInterface(String interfaceName, DhcpInfo ipInfo) {
return configureNative(interfaceName,
ipInfo.ipAddress,
ipInfo.netmask,
ipInfo.gateway,
ipInfo.dns1,
ipInfo.dns2);
}
private native static boolean configureNative(
String interfaceName, int ipAddress, int netmask, int gateway, int dns1, int dns2);
/**
* Look up a host name and return the result as an int. Works if the argument
* is an IP address in dot notation. Obviously, this can only be used for IPv4
* addresses.
* @param hostname the name of the host (or the IP address)
* @return the IP address as an {@code int} in network byte order
*/
public static int lookupHost(String hostname) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
return -1;
}
byte[] addrBytes;
int addr;
addrBytes = inetAddress.getAddress();
addr = ((addrBytes[3] & 0xff) << 24)
| ((addrBytes[2] & 0xff) << 16)
| ((addrBytes[1] & 0xff) << 8)
| (addrBytes[0] & 0xff);
return addr;
}
public static int v4StringToInt(String str) {
int result = 0;
String[] array = str.split("\\.");
if (array.length != 4) return 0;
try {
result = Integer.parseInt(array[3]);
result = (result << 8) + Integer.parseInt(array[2]);
result = (result << 8) + Integer.parseInt(array[1]);
result = (result << 8) + Integer.parseInt(array[0]);
} catch (NumberFormatException e) {
return 0;
}
return result;
}
public static byte[] ipStringToByteArray(String str) {
byte []result = null;
try {
InetAddress addr = InetAddress.getByName(str);
result = addr.getAddress();
} catch (UnknownHostException e) {
return null;
}
return result;
}
public static InetAddress byteArrayToInetAddress(byte []address) {
InetAddress result = null;
try {
result = InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
return null;
}
return result;
}
/**
* Look up a host name and return the result as an InetAddress.
* This can only be used for IPv4 addresses.
* @param hostAddr is an Int corresponding to the IPv4 address in network
* byte order
* @return the IP address as an {@code InetAddress}, returns null if
* unable to lookup host address.
*/
public static InetAddress intToInetAddress(int hostAddress) {
InetAddress inetAddress;
String hostName;
hostName = (0xff & hostAddress) + "." + (0xff & (hostAddress >> 8)) + "." +
(0xff & (hostAddress >> 16)) + "." + (0xff & (hostAddress >> 24));
try {
inetAddress = InetAddress.getByName(hostName);
} catch (UnknownHostException e) {
return null;
}
return inetAddress;
}
/**
* Add a route to the specified host/gateway.
* @param interfaceName interface on which the route should be added
* @param hostAddress the IP address to which the route is desired,
* in network byte order.
* @param prefixLength specifies default or host route, value=32/128 for IPv4/IPv6
* Host route respectively and value=0 for Default IPv4/IPv6 route.
* @return {@code true} on success, {@code false} on failure
*/
public static boolean addRoute(String interfaceName, InetAddress hostAddress, int prefixLength) {
String address = hostAddress.getHostAddress();
return addRoute(interfaceName, address, prefixLength) == 0;
}
/**
* Add a route to the specified host via the named interface.
* @param interfaceName interface on which the route should be added
* @param hostAddress the IP address to which the route is desired,
* @return {@code true} on success, {@code false} on failure
*/
public static boolean addHostRoute(String interfaceName, InetAddress hostAddress) {
int prefixLength;
String address = hostAddress.getHostAddress();
if (hostAddress instanceof Inet4Address) {
prefixLength = 32;
} else if (hostAddress instanceof Inet6Address) {
prefixLength = 128;
} else {
Log.w(TAG, "addHostRoute failure: address is neither IPv4 nor IPv6" +
"(" + address + ")");
return false;
}
return addRoute(interfaceName, address, prefixLength) == 0;
}
/**
* Start the DHCP renew service for wimax,
* This call blocks until it obtains a result (either success
* or failure) from the daemon.
* @param interfaceName the name of the interface to configure
* @param ipInfo if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
* {@hide}
*/
public native static boolean runDhcpRenew(String interfaceName, DhcpInfo ipInfo);
private native static byte[] ipAddrStringToByteArray(String ipAddr)
throws UnknownHostException;
/**{
* Helper method to convert the ip addresss in presentation format (string) to InetAddress.
* @param ipAddr ip address string in presentation format
* @return {@code InetAddress} InetAddress instance
* {@hide}
*/
public static InetAddress ipAddrStringToInetAddress(String ipAddr)
throws UnknownHostException {
if (TextUtils.isEmpty(ipAddr) || ipAddr.equals("0")) {
return InetAddress.getByName(ipAddr);
}
return InetAddress.getByAddress(ipAddrStringToByteArray(ipAddr));
}
}
@@ -0,0 +1,30 @@
/*
* 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.net;
/**
*
*
* When WebAddress Parser Fails, this exception is thrown
*/
public class ParseException extends RuntimeException {
public String response;
ParseException(String response) {
this.response = response;
}
}
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2007 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.net;
import android.content.ContentResolver;
import android.content.Context;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import junit.framework.Assert;
/**
* A convenience class for accessing the user and default proxy
* settings.
*/
final public class Proxy {
// Set to true to enable extra debugging.
static final private boolean DEBUG = false;
static final public String PROXY_CHANGE_ACTION =
"android.intent.action.PROXY_CHANGE";
/**
* Return the proxy host set by the user.
* @param ctx A Context used to get the settings for the proxy host.
* @return String containing the host name. If the user did not set a host
* name it returns the default host. A null value means that no
* host is to be used.
*/
static final public String getHost(Context ctx) {
ContentResolver contentResolver = ctx.getContentResolver();
Assert.assertNotNull(contentResolver);
String host = Settings.Secure.getString(
contentResolver,
Settings.Secure.HTTP_PROXY);
if (host != null) {
int i = host.indexOf(':');
if (i == -1) {
if (DEBUG) {
Assert.assertTrue(host.length() == 0);
}
return null;
}
return host.substring(0, i);
}
return getDefaultHost();
}
/**
* Return the proxy port set by the user.
* @param ctx A Context used to get the settings for the proxy port.
* @return The port number to use or -1 if no proxy is to be used.
*/
static final public int getPort(Context ctx) {
ContentResolver contentResolver = ctx.getContentResolver();
Assert.assertNotNull(contentResolver);
String host = Settings.Secure.getString(
contentResolver,
Settings.Secure.HTTP_PROXY);
if (host != null) {
int i = host.indexOf(':');
if (i == -1) {
if (DEBUG) {
Assert.assertTrue(host.length() == 0);
}
return -1;
}
if (DEBUG) {
Assert.assertTrue(i < host.length());
}
return Integer.parseInt(host.substring(i+1));
}
return getDefaultPort();
}
/**
* Return the default proxy host specified by the carrier.
* @return String containing the host name or null if there is no proxy for
* this carrier.
*/
static final public String getDefaultHost() {
String host = SystemProperties.get("net.gprs.http-proxy");
if (host != null) {
Uri u = Uri.parse(host);
host = u.getHost();
return host;
} else {
return null;
}
}
/**
* Return the default proxy port specified by the carrier.
* @return The port number to be used with the proxy host or -1 if there is
* no proxy for this carrier.
*/
static final public int getDefaultPort() {
String host = SystemProperties.get("net.gprs.http-proxy");
if (host != null) {
Uri u = Uri.parse(host);
return u.getPort();
} else {
return -1;
}
}
};
+498
View File
@@ -0,0 +1,498 @@
/*
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.net;
import com.android.internal.net.IPVersion;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.QosSpec;
import android.net.ILinkSocketMessageHandler;
import android.net.LinkCapabilities;
import android.net.ExtraLinkCapabilities;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
/** @hide */
public class QoSTracker {
private static final String LOG_TAG = "QoSTracker";
private static final String LOCAL_TAG = "QoSTracker_DEBUG";
private final boolean DBG = true;
private int mId;
private int mQosId;
private ExtraLinkCapabilities myCap = null;
private QosSpec mQosSpec;
private ILinkSocketMessageHandler mNotifier;
private boolean mSetupRequested;
private boolean mTeardownRequested;
private int mDetailedState; //Detail QoS State obtained from lower layers
private String mState; //QoS State notified to the APP.
private String lastState; //last state notified to the app.
private boolean notifyQosToSocket; //flag to track if the socket needs to be notified
private boolean isWaitingForSpecUpdate; //flag to track qosspec request/response
private final String QOS_STATE_FAILED = "failed";
private final String QOS_STATE_ACTIVE = "active";
private final String QOS_STATE_INACTIVE = "inactive";
private final String QOS_STATE_SUSPENDED = "suspended";
private final int[] capRoKeys = new int[] {
LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW,
LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW,
LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY,
LinkCapabilities.Key.RO_CURRENT_REV_LATENCY,
LinkCapabilities.Key.RO_BOUND_INTERFACE,
LinkCapabilities.Key.RO_NETWORK_TYPE,
LinkCapabilities.Key.RO_PHYSICAL_INTERFACE,
LinkCapabilities.Key.RO_CARRIER_ROLE,
LinkCapabilities.Key.RO_QOS_STATE
};
//Constructor
public QoSTracker (int id, ILinkSocketMessageHandler lsmh, ExtraLinkCapabilities eCap) {
dlogd("socket id: " + id + " QoSTracker EX");
mId = id;
mNotifier = lsmh;
myCap = eCap;
mQosId = -1;
mDetailedState = -1;
mState = QOS_STATE_INACTIVE;
lastState = QOS_STATE_INACTIVE;
mSetupRequested = false;
mTeardownRequested = false;
myCap.put(LinkCapabilities.Key.RO_QOS_STATE, mState);
isWaitingForSpecUpdate = false;
}
public int getSocketId() {
return mId;
}
public int getQosId() {
return mQosId;
}
public LinkCapabilities getQosCapabilities () {
return myCap;
}
public void startQosTransaction(QosSpec spec) {
if (spec == null) return;
mQosSpec = spec;
mQosSpec.setUserData(mId);
dlogi("startQosTransaction got called for socket: " + mId);
if (!mSetupRequested) {
//TODO Have CNE/App decide on the APN type for QoS.
//FIXME Hardcoding APN type for now.
if (enableQoS(mQosSpec, Phone.APN_TYPE_DEFAULT)) {
mSetupRequested = true;
} else {
//TODO implement error handlers for calls to telephony
mSetupRequested = false;
}
} else {
//TODO do modify qos
}
}
public void stopQosTransaction() {
dlogd("stopQosTransaction got called for sid: " + mId);
if (!mTeardownRequested) {
disableQos(mQosId);
mTeardownRequested = true;
}
}
public void handleQosEvent(int qosId, int qosIndState, int qosState, QosSpec spec) {
mQosId = qosId;
if (myCap == null) {
dlogw("handleQosEvent failed due to null capabilities... aborting");
return;
}
if (qosState == -1) { // event is an unsolicited indication
handleQosIndEvent(qosIndState);
//do not go further if a spec update is needed.
if (isWaitingForSpecUpdate) return;
} else { //event is a response to getQosStatus
if (spec == null) {
//remove current flow spec entries from the capabilities if spec is null
myCap.remove(LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW);
myCap.remove(LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW);
myCap.remove(LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW);
myCap.remove(LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW);
myCap.remove(LinkCapabilities.Key.RO_CURRENT_REV_LATENCY);
myCap.remove(LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY);
} else {
updateCapabilitiesFromSpec(spec);
}
isWaitingForSpecUpdate = false; //received spec update so reset the flag
}
if (notifyQosToSocket) {
ExtraLinkCapabilities sendCap = new ExtraLinkCapabilities();
for (int roKey : capRoKeys) {
if (myCap.containsKey(roKey))
sendCap.put(roKey, myCap.get(roKey));
}
try {
dlogi("notifying socket of updated capabilities: " + sendCap);
mNotifier.onCapabilitiesChanged(sendCap);
notifyQosToSocket = false;
} catch (RemoteException re) {
dlogd(" oncapabilitieschanged failed for sid: "
+ mId + " with exception: " + re);
} catch (NullPointerException npe) {
dlogd(" onCapabilitiesChgd got null notifier " + npe);
}
}
}
private void handleQosIndEvent(int qosIndState) {
mDetailedState = qosIndState;
/*
* Convert detailed state to coarse state that will be conveyed to
* the socket per the following table
* ==================================================
* Detailed State | State
* --------------------------------------------------
* REQUEST_FAILED | QOS_STATE_FAILED
* --------------------------------------------------
* INITIATED, RELEASED, |
* RELEASED_NETWORK, | QOS_STATE_INACTIVE
* NONE |
* -------------------------|------------------------
* ACTIVATED, |
* MODIFYING, MODIFIED, |
* MODIFIED_NETWORK, | QOS_STATE_ACTIVE
* RELEASING, |
* RESUMED_NETWORK, |
* SUSPENDING |
* -------------------------|------------------------
* SUSPENDED, | QOS_STATE_SUSPENDED
* --------------------------------------------------
*/
switch (mDetailedState) {
case QosSpec.QosIndStates.REQUEST_FAILED:
mSetupRequested = false;
notifyQosToSocket = true;
mState = QOS_STATE_FAILED;
break;
case QosSpec.QosIndStates.RELEASED_NETWORK:
case QosSpec.QosIndStates.RELEASED:
case QosSpec.QosIndStates.NONE:
mSetupRequested = false;
mState = QOS_STATE_INACTIVE;
//sometimes we need to update even if the coarse QOS_STATE does not change
notifyQosToSocket = true;
break;
case QosSpec.QosIndStates.INITIATED:
mSetupRequested = true;
mState = QOS_STATE_INACTIVE;
break;
case QosSpec.QosIndStates.ACTIVATED:
case QosSpec.QosIndStates.MODIFIED:
case QosSpec.QosIndStates.MODIFIED_NETWORK:
case QosSpec.QosIndStates.RESUMED_NETWORK:
//sometimes we need to update even if the coarse QOS_STATE does not change
notifyQosToSocket = true;
case QosSpec.QosIndStates.MODIFYING:
case QosSpec.QosIndStates.SUSPENDING:
case QosSpec.QosIndStates.RELEASING:
mState = QOS_STATE_ACTIVE;
break;
case QosSpec.QosIndStates.SUSPENDED:
mState = QOS_STATE_SUSPENDED;
break;
default:
dlogd("CnE got invalid qos indication: " + mDetailedState);
}
myCap.put(LinkCapabilities.Key.RO_QOS_STATE, mState);
//FIXME Querying for a spec for every indication for now.
//TODO find out for which indications the spec is expected to change
//and query the spec for those indications only.
isWaitingForSpecUpdate = getQos(mQosId);
if (!mState.equals(lastState)) {
notifyQosToSocket = true;
lastState = mState;
}
}
private void updateCapabilitiesFromSpec (QosSpec spec) {
//Only extract flow information for bandiwdth and latency
//FIXME hardcoding to two flows per spec, viz one fwd and reverse.
//TODO extract flows as per qos role definition in the config file
if (spec == null) return;
dlogi("updateCapabilities got spec: " + spec);
String temp = null;
QosSpec.QosPipe txPipe = null;
QosSpec.QosPipe rxPipe = null;
for (QosSpec.QosPipe p : spec.getQosPipes()) {
if (p.get(QosSpec.QosSpecKey.FLOW_DIRECTION).equals(
Integer.toString(QosSpec.QosDirection.QOS_TX))) txPipe = p;
if (p.get(QosSpec.QosSpecKey.FLOW_DIRECTION).equals(
Integer.toString(QosSpec.QosDirection.QOS_TX))) rxPipe = p;
}
if (txPipe == null && rxPipe == null) {
dlogw("updateCapabilities expected tx and rx pipes but did not find them");
return;
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MIN)) != null) {
myCap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW, temp);
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MAX)) != null) {
myCap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MIN)) != null) {
myCap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_DATA_RATE_MAX)) != null) {
myCap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW, temp);
}
if ((temp = txPipe.get(QosSpec.QosSpecKey.FLOW_LATENCY)) != null) {
myCap.put(LinkCapabilities.Key.RO_CURRENT_REV_LATENCY, temp);
}
if ((temp = rxPipe.get(QosSpec.QosSpecKey.FLOW_LATENCY)) != null) {
myCap.put(LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY, temp);
}
dlogi("updated capabilities to: " + myCap);
}
//update Active Capabilities
//TODO Move this out of here and do this when requestLink is received
private boolean enableQoS (QosSpec spec, String apnType) {
boolean res = false;
if (spec.getUserData() < 1 || apnType == null) return res;
if (spec == null) {
dlogw( "qos spec is null");
return res;
}
dlogi("requesting qos with spec: " + spec.toString()
+ " for txId: " + spec.getUserData()
+ " on apn: " + apnType);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
// Currently only IPv4 is supported
res = (mPhone.enableQos(spec, apnType, IPVersion.INET.toString()) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean disableQos (int qosId) {
boolean res = false;
dlogi( "disabling qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.disableQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean getQos (int qosId) {
boolean res = false;
dlogi( "requesting qos spec for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.getQosStatus(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
dlogi("getQoS returned: " + res);
return res;
}
private boolean modifyQos (int qosId, QosSpec spec) {
boolean res = false;
if (spec == null) {
dlogw( "qos spec is null");
return res;
}
dlogi( "modifying qos spec for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.modifyQos(qosId, spec) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean suspendQos (int qosId) {
boolean res = false;
dlogi( "suspending qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.suspendQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private boolean resumeQos (int qosId) {
boolean res = false;
dlogi( "resuming qos for id: " + qosId);
ITelephony mPhone = getPhone();
if (mPhone == null) {
logw("telephony service is unavailable");
return res;
}
try {
res = (mPhone.resumeQos(qosId) == Phone.QOS_REQUEST_SUCCESS);
} catch (RemoteException re) {
logw("remote exception while using telephony service: " + re);
} catch (Exception e) {
logw("exception while using telephony service: " + e);
}
return res;
}
private ITelephony getPhone() {
return ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
}
/**
* Convert a data rate string such as "2Mbps" to an integer
* representing the data rate in bps. If no data rate is given
* then bps will be assumed.
* @param rate a data rate string, such as "100kbps" or "2Mbps"
* @return the data rate in bps as an integer
*/
private static int parseBwString(String rate) {
if (rate == null) return 0;
int rateMultiple = 1; // defaults to bps
if (rate.toLowerCase().endsWith("kbps") || rate.endsWith("kbit/s") || rate.endsWith("kb/s")) {
rateMultiple = 1000; // 1,000 bps per 1 Mbps
} else if (rate.toLowerCase().endsWith("mbps") || rate.endsWith("Mbit/s") || rate.endsWith("Mb/s")) {
rateMultiple = 1000000; // 1,000,000 bps per 1 Mbps
} else if (rate.toLowerCase().endsWith("gbps") || rate.endsWith("Gbit/s") || rate.endsWith("Gb/s")) {
rateMultiple = 1000000000; // 1,000,000,000 bps per 1 Gbps
}
// find first non-numeric character, and trim
int trimPosition = rate.length();
for (int i = 0; i < rate.length(); i++) {
if (rate.charAt(i) <= '0' || rate.charAt(i) >= '9') {
trimPosition = i;
break;
}
}
rate = rate.substring(0, trimPosition);
if (rate.length() == 0) rate = "0";
return (Integer.parseInt(rate) * rateMultiple);
}
/* logging macros */
private void logd (String s) {
Log.d(LOG_TAG,s);
}
private void loge (String s) {
Log.e(LOG_TAG,s);
}
private void logw (String s) {
Log.w(LOG_TAG,s);
}
private void logv (String s) {
Log.v(LOG_TAG,s);
}
private void logi (String s) {
Log.i(LOG_TAG,s);
}
private void dlogd (String s) {
if (DBG) Log.d(LOCAL_TAG,s);
}
private void dloge (String s) {
if (DBG) Log.e(LOCAL_TAG,s);
}
private void dlogw (String s) {
if (DBG) Log.w(LOCAL_TAG,s);
}
private void dlogv (String s) {
if (DBG) Log.v(LOCAL_TAG,s);
}
private void dlogi (String s) {
if (DBG) Log.i(LOCAL_TAG,s);
}
}
@@ -0,0 +1,352 @@
/*
* Copyright (C) 2008 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.net;
import com.android.internal.net.DomainNameValidator;
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
/**
* SSLSocketFactory implementation with several extra features:
*
* <ul>
* <li>Timeout specification for SSL handshake operations
* <li>Hostname verification in most cases (see WARNINGs below)
* <li>Optional SSL session caching with {@link SSLSessionCache}
* <li>Optionally bypass all SSL certificate checks
* </ul>
*
* The handshake timeout does not apply to actual TCP socket connection.
* If you want a connection timeout as well, use {@link #createSocket()}
* and {@link Socket#connect(SocketAddress, int)}, after which you
* must verify the identity of the server you are connected to.
*
* <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not
* verify the server's identity, allowing man-in-the-middle attacks.</b>
* This implementation does check the server's certificate hostname, but only
* for createSocket variants that specify a hostname. When using methods that
* use {@link InetAddress} or which return an unconnected socket, you MUST
* verify the server's identity yourself to ensure a secure connection.</p>
*
* <p>One way to verify the server's identity is to use
* {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
* {@link HostnameVerifier} to verify the certificate hostname.
*
* <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
* SSL certificate and hostname checks for testing purposes. This setting
* requires root access.
*/
public class SSLCertificateSocketFactory extends SSLSocketFactory {
private static final String TAG = "SSLCertificateSocketFactory";
private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) { }
public void checkServerTrusted(X509Certificate[] certs, String authType) { }
}
};
private static final HostnameVerifier HOSTNAME_VERIFIER =
HttpsURLConnection.getDefaultHostnameVerifier();
private SSLSocketFactory mInsecureFactory = null;
private SSLSocketFactory mSecureFactory = null;
private final int mHandshakeTimeoutMillis;
private final SSLClientSessionCache mSessionCache;
private final boolean mSecure;
/** @deprecated Use {@link #getDefault(int)} instead. */
@Deprecated
public SSLCertificateSocketFactory(int handshakeTimeoutMillis) {
this(handshakeTimeoutMillis, null, true);
}
private SSLCertificateSocketFactory(
int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) {
mHandshakeTimeoutMillis = handshakeTimeoutMillis;
mSessionCache = cache == null ? null : cache.mSessionCache;
mSecure = secure;
}
/**
* Returns a new socket factory instance with an optional handshake timeout.
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
* @return a new SSLSocketFactory with the specified parameters
*/
public static SocketFactory getDefault(int handshakeTimeoutMillis) {
return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true);
}
/**
* Returns a new socket factory instance with an optional handshake timeout
* and SSL session cache.
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
* @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
* @return a new SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true);
}
/**
* Returns a new instance of a socket factory with all SSL security checks
* disabled, using an optional handshake timeout and SSL session cache.
*
* <p class="caution"><b>Warning:</b> Sockets created using this factory
* are vulnerable to man-in-the-middle attacks!</p>
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
* @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
* @return an insecure SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false);
}
/**
* Returns a socket factory (also named SSLSocketFactory, but in a different
* namespace) for use with the Apache HTTP stack.
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
* @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
* @return a new SocketFactory with the specified parameters
*/
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
int handshakeTimeoutMillis,
SSLSessionCache cache) {
return new org.apache.http.conn.ssl.SSLSocketFactory(
new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
}
/**
* Verify the hostname of the certificate used by the other end of a
* connected socket. You MUST call this if you did not supply a hostname
* to {@link #createSocket()}. It is harmless to call this method
* redundantly if the hostname has already been verified.
*
* <p>Wildcard certificates are allowed to verify any matching hostname,
* so "foo.bar.example.com" is verified if the peer has a certificate
* for "*.example.com".
*
* @param socket An SSL socket which has been connected to a server
* @param hostname The expected hostname of the remote server
* @throws IOException if something goes wrong handshaking with the server
* @throws SSLPeerUnverifiedException if the server cannot prove its identity
*
* @hide
*/
public static void verifyHostname(Socket socket, String hostname) throws IOException {
if (!(socket instanceof SSLSocket)) {
throw new IllegalArgumentException("Attempt to verify non-SSL socket");
}
if (!isSslCheckRelaxed()) {
// The code at the start of OpenSSLSocketImpl.startHandshake()
// ensures that the call is idempotent, so we can safely call it.
SSLSocket ssl = (SSLSocket) socket;
ssl.startHandshake();
SSLSession session = ssl.getSession();
if (session == null) {
throw new SSLException("Cannot verify SSL socket without session");
}
if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
}
}
}
private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) {
try {
OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
sslContext.engineInit(null, trustManagers, null);
sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
return sslContext.engineGetSocketFactory();
} catch (KeyManagementException e) {
Log.wtf(TAG, e);
return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback
}
}
private static boolean isSslCheckRelaxed() {
return "1".equals(SystemProperties.get("ro.debuggable")) &&
"yes".equals(SystemProperties.get("socket.relaxsslcheck"));
}
private synchronized SSLSocketFactory getDelegate() {
// Relax the SSL check if instructed (for this factory, or systemwide)
if (!mSecure || isSslCheckRelaxed()) {
if (mInsecureFactory == null) {
if (mSecure) {
Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
} else {
Log.w(TAG, "Bypassing SSL security checks at caller's request");
}
mInsecureFactory = makeSocketFactory(INSECURE_TRUST_MANAGER);
}
return mInsecureFactory;
} else {
if (mSecureFactory == null) {
mSecureFactory = makeSocketFactory(null);
}
return mSecureFactory;
}
}
/**
* {@inheritDoc}
*
* <p>This method verifies the peer's certificate hostname after connecting
* (unless created with {@link #getInsecure(int, SSLSessionCache)}).
*/
@Override
public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
if (mSecure) {
verifyHostname(s, host);
}
return s;
}
/**
* Creates a new socket which is not connected to any remote host.
* You must use {@link Socket#connect} to connect the socket.
*
* <p class="caution"><b>Warning:</b> Hostname verification is not performed
* with this method. You MUST verify the server's identity after connecting
* the socket to avoid man-in-the-middle attacks.</p>
*/
@Override
public Socket createSocket() throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
return s;
}
/**
* {@inheritDoc}
*
* <p class="caution"><b>Warning:</b> Hostname verification is not performed
* with this method. You MUST verify the server's identity after connecting
* the socket to avoid man-in-the-middle attacks.</p>
*/
@Override
public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort)
throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
addr, port, localAddr, localPort);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
return s;
}
/**
* {@inheritDoc}
*
* <p class="caution"><b>Warning:</b> Hostname verification is not performed
* with this method. You MUST verify the server's identity after connecting
* the socket to avoid man-in-the-middle attacks.</p>
*/
@Override
public Socket createSocket(InetAddress addr, int port) throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
return s;
}
/**
* {@inheritDoc}
*
* <p>This method verifies the peer's certificate hostname after connecting
* (unless created with {@link #getInsecure(int, SSLSessionCache)}).
*/
@Override
public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
host, port, localAddr, localPort);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
if (mSecure) {
verifyHostname(s, host);
}
return s;
}
/**
* {@inheritDoc}
*
* <p>This method verifies the peer's certificate hostname after connecting
* (unless created with {@link #getInsecure(int, SSLSessionCache)}).
*/
@Override
public Socket createSocket(String host, int port) throws IOException {
OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
if (mSecure) {
verifyHostname(s, host);
}
return s;
}
@Override
public String[] getDefaultCipherSuites() {
return getDelegate().getSupportedCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return getDelegate().getSupportedCipherSuites();
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2010 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.net;
import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.IOException;
/**
* File-based cache of established SSL sessions. When re-establishing a
* connection to the same server, using an SSL session cache can save some time,
* power, and bandwidth by skipping directly to an encrypted stream.
* This is a persistent cache which can span executions of the application.
*
* @see SSLCertificateSocketFactory
*/
public final class SSLSessionCache {
private static final String TAG = "SSLSessionCache";
/* package */ final SSLClientSessionCache mSessionCache;
/**
* Create a session cache using the specified directory.
* Individual session entries will be files within the directory.
* Multiple instances for the same directory share data internally.
*
* @param dir to store session files in (created if necessary)
* @throws IOException if the cache can't be opened
*/
public SSLSessionCache(File dir) throws IOException {
mSessionCache = FileClientSessionCache.usingDirectory(dir);
}
/**
* Create a session cache at the default location for this app.
* Multiple instances share data internally.
*
* @param context for the application
*/
public SSLSessionCache(Context context) {
File dir = context.getDir("sslcache", Context.MODE_PRIVATE);
SSLClientSessionCache cache = null;
try {
cache = FileClientSessionCache.usingDirectory(dir);
} catch (IOException e) {
Log.w(TAG, "Unable to create SSL session cache in " + dir, e);
}
mSessionCache = cache;
}
}
+214
View File
@@ -0,0 +1,214 @@
/*
* Copyright (C) 2008 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.net;
import android.os.SystemClock;
import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* {@hide}
*
* Simple SNTP client class for retrieving network time.
*
* Sample usage:
* <pre>SntpClient client = new SntpClient();
* if (client.requestTime("time.foo.com")) {
* long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
* }
* </pre>
*/
public class SntpClient
{
private static final String TAG = "SntpClient";
private static final int REFERENCE_TIME_OFFSET = 16;
private static final int ORIGINATE_TIME_OFFSET = 24;
private static final int RECEIVE_TIME_OFFSET = 32;
private static final int TRANSMIT_TIME_OFFSET = 40;
private static final int NTP_PACKET_SIZE = 48;
private static final int NTP_PORT = 123;
private static final int NTP_MODE_CLIENT = 3;
private static final int NTP_VERSION = 3;
// Number of seconds between Jan 1, 1900 and Jan 1, 1970
// 70 years plus 17 leap days
private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
// system time computed from NTP server response
private long mNtpTime;
// value of SystemClock.elapsedRealtime() corresponding to mNtpTime
private long mNtpTimeReference;
// round trip time in milliseconds
private long mRoundTripTime;
/**
* Sends an SNTP request to the given host and processes the response.
*
* @param host host name of the server.
* @param timeout network timeout in milliseconds.
* @return true if the transaction was successful.
*/
public boolean requestTime(String host, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
InetAddress address = InetAddress.getByName(host);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
// set mode = 3 (client) and version = 3
// mode is in low 3 bits of first byte
// version is in bits 3-5 of first byte
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
// get current time and write it to the request packet
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request);
// read the response
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
long responseTicks = SystemClock.elapsedRealtime();
long responseTime = requestTime + (responseTicks - requestTicks);
// extract the results
long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
// receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
// if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
// if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");
// save our results - use the times on this side of the network latency
// (response rather than request time)
mNtpTime = responseTime + clockOffset;
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (Config.LOGD) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
/**
* Returns the time computed from the NTP transaction.
*
* @return time value computed from NTP server response.
*/
public long getNtpTime() {
return mNtpTime;
}
/**
* Returns the reference clock value (value of SystemClock.elapsedRealtime())
* corresponding to the NTP time.
*
* @return reference clock corresponding to the NTP time.
*/
public long getNtpTimeReference() {
return mNtpTimeReference;
}
/**
* Returns the round trip time of the NTP transaction
*
* @return round trip time in milliseconds.
*/
public long getRoundTripTime() {
return mRoundTripTime;
}
/**
* Reads an unsigned 32 bit big endian number from the given offset in the buffer.
*/
private long read32(byte[] buffer, int offset) {
byte b0 = buffer[offset];
byte b1 = buffer[offset+1];
byte b2 = buffer[offset+2];
byte b3 = buffer[offset+3];
// convert signed bytes to unsigned values
int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
return ((long)i0 << 24) + ((long)i1 << 16) + ((long)i2 << 8) + (long)i3;
}
/**
* Reads the NTP time stamp at the given offset in the buffer and returns
* it as a system time (milliseconds since January 1, 1970).
*/
private long readTimeStamp(byte[] buffer, int offset) {
long seconds = read32(buffer, offset);
long fraction = read32(buffer, offset + 4);
return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
}
/**
* Writes system time (milliseconds since January 1, 1970) as an NTP time stamp
* at the given offset in the buffer.
*/
private void writeTimeStamp(byte[] buffer, int offset, long time) {
long seconds = time / 1000L;
long milliseconds = time - seconds * 1000L;
seconds += OFFSET_1900_TO_1970;
// write seconds in big endian format
buffer[offset++] = (byte)(seconds >> 24);
buffer[offset++] = (byte)(seconds >> 16);
buffer[offset++] = (byte)(seconds >> 8);
buffer[offset++] = (byte)(seconds >> 0);
long fraction = milliseconds * 0x100000000L / 1000L;
// write fraction in big endian format
buffer[offset++] = (byte)(fraction >> 24);
buffer[offset++] = (byte)(fraction >> 16);
buffer[offset++] = (byte)(fraction >> 8);
// low order bits should be random data
buffer[offset++] = (byte)(Math.random() * 255.0);
}
}
@@ -0,0 +1,214 @@
/*
* Copyright (C) 2008 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.net;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.os.Binder;
import android.os.RemoteException;
/**
* Class that handles throttling. It provides read/write numbers per interface
* and methods to apply throttled rates.
* {@hide}
*/
public class ThrottleManager
{
/**
* Broadcast each polling period to indicate new data counts.
*
* Includes four extras:
* EXTRA_CYCLE_READ - a long of the read bytecount for the current cycle
* EXTRA_CYCLE_WRITE -a long of the write bytecount for the current cycle
* EXTRA_CYLCE_START -a long of MS for the cycle start time
* EXTRA_CYCLE_END -a long of MS for the cycle stop time
* {@hide}
*/
public static final String THROTTLE_POLL_ACTION = "android.net.thrott.POLL_ACTION";
/**
* The lookup key for a long for the read bytecount for this period. Retrieve with
* {@link android.content.Intent#getLongExtra(String)}.
* {@hide}
*/
public static final String EXTRA_CYCLE_READ = "cycleRead";
/**
* contains a long of the number of bytes written in the cycle
* {@hide}
*/
public static final String EXTRA_CYCLE_WRITE = "cycleWrite";
/**
* contains a long of the number of bytes read in the cycle
* {@hide}
*/
public static final String EXTRA_CYCLE_START = "cycleStart";
/**
* contains a long of the ms since 1970 used to init a calendar, etc for the end
* of the cycle
* {@hide}
*/
public static final String EXTRA_CYCLE_END = "cycleEnd";
/**
* Broadcast when the thottle level changes.
* {@hide}
*/
public static final String THROTTLE_ACTION = "android.net.thrott.THROTTLE_ACTION";
/**
* int of the current bandwidth in TODO
* {@hide}
*/
public static final String EXTRA_THROTTLE_LEVEL = "level";
/**
* Broadcast on boot and whenever the settings change.
* {@hide}
*/
public static final String POLICY_CHANGED_ACTION = "android.net.thrott.POLICY_CHANGED_ACTION";
// {@hide}
public static final int DIRECTION_TX = 0;
// {@hide}
public static final int DIRECTION_RX = 1;
// {@hide}
public static final int PERIOD_CYCLE = 0;
// {@hide}
public static final int PERIOD_YEAR = 1;
// {@hide}
public static final int PERIOD_MONTH = 2;
// {@hide}
public static final int PERIOD_WEEK = 3;
// @hide
public static final int PERIOD_7DAY = 4;
// @hide
public static final int PERIOD_DAY = 5;
// @hide
public static final int PERIOD_24HOUR = 6;
// @hide
public static final int PERIOD_HOUR = 7;
// @hide
public static final int PERIOD_60MIN = 8;
// @hide
public static final int PERIOD_MINUTE = 9;
// @hide
public static final int PERIOD_60SEC = 10;
// @hide
public static final int PERIOD_SECOND = 11;
/**
* returns a long of the ms from the epoch to the time the current cycle ends for the
* named interface
* {@hide}
*/
public long getResetTime(String iface) {
try {
return mService.getResetTime(iface);
} catch (RemoteException e) {
return -1;
}
}
/**
* returns a long of the ms from the epoch to the time the current cycle started for the
* named interface
* {@hide}
*/
public long getPeriodStartTime(String iface) {
try {
return mService.getPeriodStartTime(iface);
} catch (RemoteException e) {
return -1;
}
}
/**
* returns a long of the byte count either read or written on the named interface
* for the period described. Direction is either DIRECTION_RX or DIRECTION_TX and
* period may only be PERIOD_CYCLE for the current cycle (other periods may be supported
* in the future). Ago indicates the number of periods in the past to lookup - 0 means
* the current period, 1 is the last one, 2 was two periods ago..
* {@hide}
*/
public long getByteCount(String iface, int direction, int period, int ago) {
try {
return mService.getByteCount(iface, direction, period, ago);
} catch (RemoteException e) {
return -1;
}
}
/**
* returns the number of bytes read+written after which a particular cliff
* takes effect on the named iface. Currently only cliff #1 is supported (1 step)
* {@hide}
*/
public long getCliffThreshold(String iface, int cliff) {
try {
return mService.getCliffThreshold(iface, cliff);
} catch (RemoteException e) {
return -1;
}
}
/**
* returns the thottling bandwidth (bps) for a given cliff # on the named iface.
* only cliff #1 is currently supported.
* {@hide}
*/
public int getCliffLevel(String iface, int cliff) {
try {
return mService.getCliffLevel(iface, cliff);
} catch (RemoteException e) {
return -1;
}
}
/**
* returns the help URI for throttling
* {@hide}
*/
public String getHelpUri() {
try {
return mService.getHelpUri();
} catch (RemoteException e) {
return null;
}
}
private IThrottleManager mService;
/**
* Don't allow use of default constructor.
*/
@SuppressWarnings({"UnusedDeclaration"})
private ThrottleManager() {
}
/**
* {@hide}
*/
public ThrottleManager(IThrottleManager service) {
if (service == null) {
throw new IllegalArgumentException(
"ThrottleManager() cannot be constructed with null service");
}
mService = service;
}
}
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2007 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.net;
import android.util.Log;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
/**
* Class that provides network traffic statistics. These statistics include
* bytes transmitted and received and network packets transmitted and received,
* over all interfaces, over the mobile interface, and on a per-UID basis.
* <p>
* These statistics may not be available on all platforms. If the statistics
* are not supported by this device, {@link #UNSUPPORTED} will be returned.
*/
public class TrafficStats {
/**
* The return value to indicate that the device does not support the statistic.
*/
public final static int UNSUPPORTED = -1;
/**
* Get the total number of packets transmitted through the mobile interface.
*
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getMobileTxPackets();
/**
* Get the total number of packets received through the mobile interface.
*
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getMobileRxPackets();
/**
* Get the total number of bytes transmitted through the mobile interface.
*
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getMobileTxBytes();
/**
* Get the total number of bytes received through the mobile interface.
*
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getMobileRxBytes();
/**
* Get the total number of packets sent through all network interfaces.
*
* @return the number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getTotalTxPackets();
/**
* Get the total number of packets received through all network interfaces.
*
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getTotalRxPackets();
/**
* Get the total number of bytes sent through all network interfaces.
*
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getTotalTxBytes();
/**
* Get the total number of bytes received through all network interfaces.
*
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getTotalRxBytes();
/**
* Get the number of bytes sent through the network for this UID.
* The statistics are across all interfaces.
*
* {@see android.os.Process#myUid()}.
*
* @param uid The UID of the process to examine.
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
public static native long getUidTxBytes(int uid);
/**
* Get the number of bytes received through the network for this UID.
* The statistics are across all interfaces.
*
* {@see android.os.Process#myUid()}.
*
* @param uid The UID of the process to examine.
* @return number of bytes
*/
public static native long getUidRxBytes(int uid);
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2007, 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.net;
parcelable Uri;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,913 @@
/*
* Copyright (C) 2007 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.net;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
/**
*
* Sanitizes the Query portion of a URL. Simple example:
* <code>
* UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
* sanitizer.setAllowUnregisteredParamaters(true);
* sanitizer.parseUrl("http://example.com/?name=Joe+User");
* String name = sanitizer.getValue("name"));
* // name now contains "Joe_User"
* </code>
*
* Register ValueSanitizers to customize the way individual
* parameters are sanitized:
* <code>
* UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
* sanitizer.registerParamater("name", UrlQuerySanitizer.createSpaceLegal());
* sanitizer.parseUrl("http://example.com/?name=Joe+User");
* String name = sanitizer.getValue("name"));
* // name now contains "Joe User". (The string is first decoded, which
* // converts the '+' to a ' '. Then the string is sanitized, which
* // converts the ' ' to an '_'. (The ' ' is converted because the default
* unregistered parameter sanitizer does not allow any special characters,
* and ' ' is a special character.)
* </code>
*
* There are several ways to create ValueSanitizers. In order of increasing
* sophistication:
* <ol>
* <li>Call one of the UrlQuerySanitizer.createXXX() methods.
* <li>Construct your own instance of
* UrlQuerySanitizer.IllegalCharacterValueSanitizer.
* <li>Subclass UrlQuerySanitizer.ValueSanitizer to define your own value
* sanitizer.
* </ol>
*
*/
public class UrlQuerySanitizer {
/**
* A simple tuple that holds parameter-value pairs.
*
*/
public class ParameterValuePair {
/**
* Construct a parameter-value tuple.
* @param parameter an unencoded parameter
* @param value an unencoded value
*/
public ParameterValuePair(String parameter,
String value) {
mParameter = parameter;
mValue = value;
}
/**
* The unencoded parameter
*/
public String mParameter;
/**
* The unencoded value
*/
public String mValue;
}
final private HashMap<String, ValueSanitizer> mSanitizers =
new HashMap<String, ValueSanitizer>();
final private HashMap<String, String> mEntries =
new HashMap<String, String>();
final private ArrayList<ParameterValuePair> mEntriesList =
new ArrayList<ParameterValuePair>();
private boolean mAllowUnregisteredParamaters;
private boolean mPreferFirstRepeatedParameter;
private ValueSanitizer mUnregisteredParameterValueSanitizer =
getAllIllegal();
/**
* A functor used to sanitize a single query value.
*
*/
public static interface ValueSanitizer {
/**
* Sanitize an unencoded value.
* @param value
* @return the sanitized unencoded value
*/
public String sanitize(String value);
}
/**
* Sanitize values based on which characters they contain. Illegal
* characters are replaced with either space or '_', depending upon
* whether space is a legal character or not.
*/
public static class IllegalCharacterValueSanitizer implements
ValueSanitizer {
private int mFlags;
/**
* Allow space (' ') characters.
*/
public final static int SPACE_OK = 1 << 0;
/**
* Allow whitespace characters other than space. The
* other whitespace characters are
* '\t' '\f' '\n' '\r' and '\0x000b' (vertical tab)
*/
public final static int OTHER_WHITESPACE_OK = 1 << 1;
/**
* Allow characters with character codes 128 to 255.
*/
public final static int NON_7_BIT_ASCII_OK = 1 << 2;
/**
* Allow double quote characters. ('"')
*/
public final static int DQUOTE_OK = 1 << 3;
/**
* Allow single quote characters. ('\'')
*/
public final static int SQUOTE_OK = 1 << 4;
/**
* Allow less-than characters. ('<')
*/
public final static int LT_OK = 1 << 5;
/**
* Allow greater-than characters. ('>')
*/
public final static int GT_OK = 1 << 6;
/**
* Allow ampersand characters ('&')
*/
public final static int AMP_OK = 1 << 7;
/**
* Allow percent-sign characters ('%')
*/
public final static int PCT_OK = 1 << 8;
/**
* Allow nul characters ('\0')
*/
public final static int NUL_OK = 1 << 9;
/**
* Allow text to start with a script URL
* such as "javascript:" or "vbscript:"
*/
public final static int SCRIPT_URL_OK = 1 << 10;
/**
* Mask with all fields set to OK
*/
public final static int ALL_OK = 0x7ff;
/**
* Mask with both regular space and other whitespace OK
*/
public final static int ALL_WHITESPACE_OK =
SPACE_OK | OTHER_WHITESPACE_OK;
// Common flag combinations:
/**
* <ul>
* <li>Deny all special characters.
* <li>Deny script URLs.
* </ul>
*/
public final static int ALL_ILLEGAL =
0;
/**
* <ul>
* <li>Allow all special characters except Nul. ('\0').
* <li>Allow script URLs.
* </ul>
*/
public final static int ALL_BUT_NUL_LEGAL =
ALL_OK & ~NUL_OK;
/**
* <ul>
* <li>Allow all special characters except for:
* <ul>
* <li>whitespace characters
* <li>Nul ('\0')
* </ul>
* <li>Allow script URLs.
* </ul>
*/
public final static int ALL_BUT_WHITESPACE_LEGAL =
ALL_OK & ~(ALL_WHITESPACE_OK | NUL_OK);
/**
* <ul>
* <li>Allow characters used by encoded URLs.
* <li>Deny script URLs.
* </ul>
*/
public final static int URL_LEGAL =
NON_7_BIT_ASCII_OK | SQUOTE_OK | AMP_OK | PCT_OK;
/**
* <ul>
* <li>Allow characters used by encoded URLs.
* <li>Allow spaces.
* <li>Deny script URLs.
* </ul>
*/
public final static int URL_AND_SPACE_LEGAL =
URL_LEGAL | SPACE_OK;
/**
* <ul>
* <li>Allow ampersand.
* <li>Deny script URLs.
* </ul>
*/
public final static int AMP_LEGAL =
AMP_OK;
/**
* <ul>
* <li>Allow ampersand.
* <li>Allow space.
* <li>Deny script URLs.
* </ul>
*/
public final static int AMP_AND_SPACE_LEGAL =
AMP_OK | SPACE_OK;
/**
* <ul>
* <li>Allow space.
* <li>Deny script URLs.
* </ul>
*/
public final static int SPACE_LEGAL =
SPACE_OK;
/**
* <ul>
* <li>Allow all but.
* <ul>
* <li>Nul ('\0')
* <li>Angle brackets ('<', '>')
* </ul>
* <li>Deny script URLs.
* </ul>
*/
public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL =
ALL_OK & ~(NUL_OK | LT_OK | GT_OK);
/**
* Script URL definitions
*/
private final static String JAVASCRIPT_PREFIX = "javascript:";
private final static String VBSCRIPT_PREFIX = "vbscript:";
private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min(
JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length());
/**
* Construct a sanitizer. The parameters set the behavior of the
* sanitizer.
* @param flags some combination of the XXX_OK flags.
*/
public IllegalCharacterValueSanitizer(
int flags) {
mFlags = flags;
}
/**
* Sanitize a value.
* <ol>
* <li>If script URLs are not OK, the will be removed.
* <li>If neither spaces nor other white space is OK, then
* white space will be trimmed from the beginning and end of
* the URL. (Just the actual white space characters are trimmed, not
* other control codes.)
* <li> Illegal characters will be replaced with
* either ' ' or '_', depending on whether a space is itself a
* legal character.
* </ol>
* @param value
* @return the sanitized value
*/
public String sanitize(String value) {
if (value == null) {
return null;
}
int length = value.length();
if ((mFlags & SCRIPT_URL_OK) != 0) {
if (length >= MIN_SCRIPT_PREFIX_LENGTH) {
String asLower = value.toLowerCase();
if (asLower.startsWith(JAVASCRIPT_PREFIX) ||
asLower.startsWith(VBSCRIPT_PREFIX)) {
return "";
}
}
}
// If whitespace isn't OK, get rid of whitespace at beginning
// and end of value.
if ( (mFlags & ALL_WHITESPACE_OK) == 0) {
value = trimWhitespace(value);
// The length could have changed, so we need to correct
// the length variable.
length = value.length();
}
StringBuilder stringBuilder = new StringBuilder(length);
for(int i = 0; i < length; i++) {
char c = value.charAt(i);
if (!characterIsLegal(c)) {
if ((mFlags & SPACE_OK) != 0) {
c = ' ';
}
else {
c = '_';
}
}
stringBuilder.append(c);
}
return stringBuilder.toString();
}
/**
* Trim whitespace from the beginning and end of a string.
* <p>
* Note: can't use {@link String#trim} because {@link String#trim} has a
* different definition of whitespace than we want.
* @param value the string to trim
* @return the trimmed string
*/
private String trimWhitespace(String value) {
int start = 0;
int last = value.length() - 1;
int end = last;
while (start <= end && isWhitespace(value.charAt(start))) {
start++;
}
while (end >= start && isWhitespace(value.charAt(end))) {
end--;
}
if (start == 0 && end == last) {
return value;
}
return value.substring(start, end + 1);
}
/**
* Check if c is whitespace.
* @param c character to test
* @return true if c is a whitespace character
*/
private boolean isWhitespace(char c) {
switch(c) {
case ' ':
case '\t':
case '\f':
case '\n':
case '\r':
case 11: /* VT */
return true;
default:
return false;
}
}
/**
* Check whether an individual character is legal. Uses the
* flag bit-set passed into the constructor.
* @param c
* @return true if c is a legal character
*/
private boolean characterIsLegal(char c) {
switch(c) {
case ' ' : return (mFlags & SPACE_OK) != 0;
case '\t': case '\f': case '\n': case '\r': case 11: /* VT */
return (mFlags & OTHER_WHITESPACE_OK) != 0;
case '\"': return (mFlags & DQUOTE_OK) != 0;
case '\'': return (mFlags & SQUOTE_OK) != 0;
case '<' : return (mFlags & LT_OK) != 0;
case '>' : return (mFlags & GT_OK) != 0;
case '&' : return (mFlags & AMP_OK) != 0;
case '%' : return (mFlags & PCT_OK) != 0;
case '\0': return (mFlags & NUL_OK) != 0;
default : return (c >= 32 && c < 127) ||
((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0));
}
}
}
/**
* Get the current value sanitizer used when processing
* unregistered parameter values.
* <p>
* <b>Note:</b> The default unregistered parameter value sanitizer is
* one that doesn't allow any special characters, similar to what
* is returned by calling createAllIllegal.
*
* @return the current ValueSanitizer used to sanitize unregistered
* parameter values.
*/
public ValueSanitizer getUnregisteredParameterValueSanitizer() {
return mUnregisteredParameterValueSanitizer;
}
/**
* Set the value sanitizer used when processing unregistered
* parameter values.
* @param sanitizer set the ValueSanitizer used to sanitize unregistered
* parameter values.
*/
public void setUnregisteredParameterValueSanitizer(
ValueSanitizer sanitizer) {
mUnregisteredParameterValueSanitizer = sanitizer;
}
// Private fields for singleton sanitizers:
private static final ValueSanitizer sAllIllegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_ILLEGAL);
private static final ValueSanitizer sAllButNulLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL);
private static final ValueSanitizer sAllButWhitespaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL);
private static final ValueSanitizer sURLLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.URL_LEGAL);
private static final ValueSanitizer sUrlAndSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL);
private static final ValueSanitizer sAmpLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.AMP_LEGAL);
private static final ValueSanitizer sAmpAndSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL);
private static final ValueSanitizer sSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.SPACE_LEGAL);
private static final ValueSanitizer sAllButNulAndAngleBracketsLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL);
/**
* Return a value sanitizer that does not allow any special characters,
* and also does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getAllIllegal() {
return sAllIllegal;
}
/**
* Return a value sanitizer that allows everything except Nul ('\0')
* characters. Script URLs are allowed.
* @return a value sanitizer
*/
public static final ValueSanitizer getAllButNulLegal() {
return sAllButNulLegal;
}
/**
* Return a value sanitizer that allows everything except Nul ('\0')
* characters, space (' '), and other whitespace characters.
* Script URLs are allowed.
* @return a value sanitizer
*/
public static final ValueSanitizer getAllButWhitespaceLegal() {
return sAllButWhitespaceLegal;
}
/**
* Return a value sanitizer that allows all the characters used by
* encoded URLs. Does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getUrlLegal() {
return sURLLegal;
}
/**
* Return a value sanitizer that allows all the characters used by
* encoded URLs and allows spaces, which are not technically legal
* in encoded URLs, but commonly appear anyway.
* Does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getUrlAndSpaceLegal() {
return sUrlAndSpaceLegal;
}
/**
* Return a value sanitizer that does not allow any special characters
* except ampersand ('&'). Does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getAmpLegal() {
return sAmpLegal;
}
/**
* Return a value sanitizer that does not allow any special characters
* except ampersand ('&') and space (' '). Does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getAmpAndSpaceLegal() {
return sAmpAndSpaceLegal;
}
/**
* Return a value sanitizer that does not allow any special characters
* except space (' '). Does not allow script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getSpaceLegal() {
return sSpaceLegal;
}
/**
* Return a value sanitizer that allows any special characters
* except angle brackets ('<' and '>') and Nul ('\0').
* Allows script URLs.
* @return a value sanitizer
*/
public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() {
return sAllButNulAndAngleBracketsLegal;
}
/**
* Constructs a UrlQuerySanitizer.
* <p>
* Defaults:
* <ul>
* <li>unregistered parameters are not allowed.
* <li>the last instance of a repeated parameter is preferred.
* <li>The default value sanitizer is an AllIllegal value sanitizer.
* <ul>
*/
public UrlQuerySanitizer() {
}
/**
* Constructs a UrlQuerySanitizer and parse a URL.
* This constructor is provided for convenience when the
* default parsing behavior is acceptable.
* <p>
* Because the URL is parsed before the constructor returns, there isn't
* a chance to configure the sanitizer to change the parsing behavior.
* <p>
* <code>
* UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(myUrl);
* String name = sanitizer.getValue("name");
* </code>
* <p>
* Defaults:
* <ul>
* <li>unregistered parameters <em>are</em> allowed.
* <li>the last instance of a repeated parameter is preferred.
* <li>The default value sanitizer is an AllIllegal value sanitizer.
* <ul>
*/
public UrlQuerySanitizer(String url) {
setAllowUnregisteredParamaters(true);
parseUrl(url);
}
/**
* Parse the query parameters out of an encoded URL.
* Works by extracting the query portion from the URL and then
* calling parseQuery(). If there is no query portion it is
* treated as if the query portion is an empty string.
* @param url the encoded URL to parse.
*/
public void parseUrl(String url) {
int queryIndex = url.indexOf('?');
String query;
if (queryIndex >= 0) {
query = url.substring(queryIndex + 1);
}
else {
query = "";
}
parseQuery(query);
}
/**
* Parse a query. A query string is any number of parameter-value clauses
* separated by any non-zero number of ampersands. A parameter-value clause
* is a parameter followed by an equal sign, followed by a value. If the
* equal sign is missing, the value is assumed to be the empty string.
* @param query the query to parse.
*/
public void parseQuery(String query) {
clear();
// Split by '&'
StringTokenizer tokenizer = new StringTokenizer(query, "&");
while(tokenizer.hasMoreElements()) {
String attributeValuePair = tokenizer.nextToken();
if (attributeValuePair.length() > 0) {
int assignmentIndex = attributeValuePair.indexOf('=');
if (assignmentIndex < 0) {
// No assignment found, treat as if empty value
parseEntry(attributeValuePair, "");
}
else {
parseEntry(attributeValuePair.substring(0, assignmentIndex),
attributeValuePair.substring(assignmentIndex + 1));
}
}
}
}
/**
* Get a set of all of the parameters found in the sanitized query.
* <p>
* Note: Do not modify this set. Treat it as a read-only set.
* @return all the parameters found in the current query.
*/
public Set<String> getParameterSet() {
return mEntries.keySet();
}
/**
* An array list of all of the parameter value pairs in the sanitized
* query, in the order they appeared in the query. May contain duplicate
* parameters.
* <p class="note"><b>Note:</b> Do not modify this list. Treat it as a read-only list.</p>
*/
public List<ParameterValuePair> getParameterList() {
return mEntriesList;
}
/**
* Check if a parameter exists in the current sanitized query.
* @param parameter the unencoded name of a parameter.
* @return true if the paramater exists in the current sanitized queary.
*/
public boolean hasParameter(String parameter) {
return mEntries.containsKey(parameter);
}
/**
* Get the value for a parameter in the current sanitized query.
* Returns null if the parameter does not
* exit.
* @param parameter the unencoded name of a parameter.
* @return the sanitized unencoded value of the parameter,
* or null if the parameter does not exist.
*/
public String getValue(String parameter) {
return mEntries.get(parameter);
}
/**
* Register a value sanitizer for a particular parameter. Can also be used
* to replace or remove an already-set value sanitizer.
* <p>
* Registering a non-null value sanitizer for a particular parameter
* makes that parameter a registered parameter.
* @param parameter an unencoded parameter name
* @param valueSanitizer the value sanitizer to use for a particular
* parameter. May be null in order to unregister that parameter.
* @see #getAllowUnregisteredParamaters()
*/
public void registerParameter(String parameter,
ValueSanitizer valueSanitizer) {
if (valueSanitizer == null) {
mSanitizers.remove(parameter);
}
mSanitizers.put(parameter, valueSanitizer);
}
/**
* Register a value sanitizer for an array of parameters.
* @param parameters An array of unencoded parameter names.
* @param valueSanitizer
* @see #registerParameter
*/
public void registerParameters(String[] parameters,
ValueSanitizer valueSanitizer) {
int length = parameters.length;
for(int i = 0; i < length; i++) {
mSanitizers.put(parameters[i], valueSanitizer);
}
}
/**
* Set whether or not unregistered parameters are allowed. If they
* are not allowed, then they will be dropped when a query is sanitized.
* <p>
* Defaults to false.
* @param allowUnregisteredParamaters true to allow unregistered parameters.
* @see #getAllowUnregisteredParamaters()
*/
public void setAllowUnregisteredParamaters(
boolean allowUnregisteredParamaters) {
mAllowUnregisteredParamaters = allowUnregisteredParamaters;
}
/**
* Get whether or not unregistered parameters are allowed. If not
* allowed, they will be dropped when a query is parsed.
* @return true if unregistered parameters are allowed.
* @see #setAllowUnregisteredParamaters(boolean)
*/
public boolean getAllowUnregisteredParamaters() {
return mAllowUnregisteredParamaters;
}
/**
* Set whether or not the first occurrence of a repeated parameter is
* preferred. True means the first repeated parameter is preferred.
* False means that the last repeated parameter is preferred.
* <p>
* The preferred parameter is the one that is returned when getParameter
* is called.
* <p>
* defaults to false.
* @param preferFirstRepeatedParameter True if the first repeated
* parameter is preferred.
* @see #getPreferFirstRepeatedParameter()
*/
public void setPreferFirstRepeatedParameter(
boolean preferFirstRepeatedParameter) {
mPreferFirstRepeatedParameter = preferFirstRepeatedParameter;
}
/**
* Get whether or not the first occurrence of a repeated parameter is
* preferred.
* @return true if the first occurrence of a repeated parameter is
* preferred.
* @see #setPreferFirstRepeatedParameter(boolean)
*/
public boolean getPreferFirstRepeatedParameter() {
return mPreferFirstRepeatedParameter;
}
/**
* Parse an escaped parameter-value pair. The default implementation
* unescapes both the parameter and the value, then looks up the
* effective value sanitizer for the parameter and uses it to sanitize
* the value. If all goes well then addSanitizedValue is called with
* the unescaped parameter and the sanitized unescaped value.
* @param parameter an escaped parameter
* @param value an unsanitzied escaped value
*/
protected void parseEntry(String parameter, String value) {
String unescapedParameter = unescape(parameter);
ValueSanitizer valueSanitizer =
getEffectiveValueSanitizer(unescapedParameter);
if (valueSanitizer == null) {
return;
}
String unescapedValue = unescape(value);
String sanitizedValue = valueSanitizer.sanitize(unescapedValue);
addSanitizedEntry(unescapedParameter, sanitizedValue);
}
/**
* Record a sanitized parameter-value pair. Override if you want to
* do additional filtering or validation.
* @param parameter an unescaped parameter
* @param value a sanitized unescaped value
*/
protected void addSanitizedEntry(String parameter, String value) {
mEntriesList.add(
new ParameterValuePair(parameter, value));
if (mPreferFirstRepeatedParameter) {
if (mEntries.containsKey(parameter)) {
return;
}
}
mEntries.put(parameter, value);
}
/**
* Get the value sanitizer for a parameter. Returns null if there
* is no value sanitizer registered for the parameter.
* @param parameter the unescaped parameter
* @return the currently registered value sanitizer for this parameter.
* @see #registerParameter(String, android.net.UrlQuerySanitizer.ValueSanitizer)
*/
public ValueSanitizer getValueSanitizer(String parameter) {
return mSanitizers.get(parameter);
}
/**
* Get the effective value sanitizer for a parameter. Like getValueSanitizer,
* except if there is no value sanitizer registered for a parameter, and
* unregistered paramaters are allowed, then the default value sanitizer is
* returned.
* @param parameter an unescaped parameter
* @return the effective value sanitizer for a parameter.
*/
public ValueSanitizer getEffectiveValueSanitizer(String parameter) {
ValueSanitizer sanitizer = getValueSanitizer(parameter);
if (sanitizer == null && mAllowUnregisteredParamaters) {
sanitizer = getUnregisteredParameterValueSanitizer();
}
return sanitizer;
}
/**
* Unescape an escaped string.
* <ul>
* <li>'+' characters are replaced by
* ' ' characters.
* <li>Valid "%xx" escape sequences are replaced by the
* corresponding unescaped character.
* <li>Invalid escape sequences such as %1z", are passed through unchanged.
* <ol>
* @param string the escaped string
* @return the unescaped string.
*/
public String unescape(String string) {
// Early exit if no escaped characters.
int firstEscape = string.indexOf('%');
if ( firstEscape < 0) {
firstEscape = string.indexOf('+');
if (firstEscape < 0) {
return string;
}
}
int length = string.length();
StringBuilder stringBuilder = new StringBuilder(length);
stringBuilder.append(string.substring(0, firstEscape));
for (int i = firstEscape; i < length; i++) {
char c = string.charAt(i);
if (c == '+') {
c = ' ';
}
else if ( c == '%' && i + 2 < length) {
char c1 = string.charAt(i + 1);
char c2 = string.charAt(i + 2);
if (isHexDigit(c1) && isHexDigit(c2)) {
c = (char) (decodeHexDigit(c1) * 16 + decodeHexDigit(c2));
i += 2;
}
}
stringBuilder.append(c);
}
return stringBuilder.toString();
}
/**
* Test if a character is a hexidecimal digit. Both upper case and lower
* case hex digits are allowed.
* @param c the character to test
* @return true if c is a hex digit.
*/
protected boolean isHexDigit(char c) {
return decodeHexDigit(c) >= 0;
}
/**
* Convert a character that represents a hexidecimal digit into an integer.
* If the character is not a hexidecimal digit, then -1 is returned.
* Both upper case and lower case hex digits are allowed.
* @param c the hexidecimal digit.
* @return the integer value of the hexidecimal digit.
*/
protected int decodeHexDigit(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
else if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
else if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
else {
return -1;
}
}
/**
* Clear the existing entries. Called to get ready to parse a new
* query string.
*/
protected void clear() {
mEntries.clear();
mEntriesList.clear();
}
}
+138
View File
@@ -0,0 +1,138 @@
/*
* 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.net;
import static android.util.Patterns.GOOD_IRI_CHAR;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* {@hide}
*
* Web Address Parser
*
* This is called WebAddress, rather than URL or URI, because it
* attempts to parse the stuff that a user will actually type into a
* browser address widget.
*
* Unlike java.net.uri, this parser will not choke on URIs missing
* schemes. It will only throw a ParseException if the input is
* really hosed.
*
* If given an https scheme but no port, fills in port
*
*/
public class WebAddress {
private final static String LOGTAG = "http";
public String mScheme;
public String mHost;
public int mPort;
public String mPath;
public String mAuthInfo;
static final int MATCH_GROUP_SCHEME = 1;
static final int MATCH_GROUP_AUTHORITY = 2;
static final int MATCH_GROUP_HOST = 3;
static final int MATCH_GROUP_PORT = 4;
static final int MATCH_GROUP_PATH = 5;
static Pattern sAddressPattern = Pattern.compile(
/* scheme */ "(?:(http|https|file)\\:\\/\\/)?" +
/* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
/* host */ "([-" + GOOD_IRI_CHAR + "%_]+(?:\\.[-" + GOOD_IRI_CHAR + "%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
/* port */ "(?:\\:([0-9]*))?" +
/* path */ "(\\/?[^#]*)?" +
/* anchor */ ".*", Pattern.CASE_INSENSITIVE);
/** parses given uriString. */
public WebAddress(String address) throws ParseException {
if (address == null) {
throw new NullPointerException();
}
// android.util.Log.d(LOGTAG, "WebAddress: " + address);
mScheme = "";
mHost = "";
mPort = -1;
mPath = "/";
mAuthInfo = "";
Matcher m = sAddressPattern.matcher(address);
String t;
if (m.matches()) {
t = m.group(MATCH_GROUP_SCHEME);
if (t != null) mScheme = t.toLowerCase();
t = m.group(MATCH_GROUP_AUTHORITY);
if (t != null) mAuthInfo = t;
t = m.group(MATCH_GROUP_HOST);
if (t != null) mHost = t;
t = m.group(MATCH_GROUP_PORT);
if (t != null && t.length() > 0) {
// The ':' character is not returned by the regex.
try {
mPort = Integer.parseInt(t);
} catch (NumberFormatException ex) {
throw new ParseException("Bad port");
}
}
t = m.group(MATCH_GROUP_PATH);
if (t != null && t.length() > 0) {
/* handle busted myspace frontpage redirect with
missing initial "/" */
if (t.charAt(0) == '/') {
mPath = t;
} else {
mPath = "/" + t;
}
}
} else {
// nothing found... outa here
throw new ParseException("Bad address");
}
/* Get port from scheme or scheme from port, if necessary and
possible */
if (mPort == 443 && mScheme.equals("")) {
mScheme = "https";
} else if (mPort == -1) {
if (mScheme.equals("https"))
mPort = 443;
else
mPort = 80; // default
}
if (mScheme.equals("")) mScheme = "http";
}
public String toString() {
String port = "";
if ((mPort != 443 && mScheme.equals("https")) ||
(mPort != 80 && mScheme.equals("http"))) {
port = ":" + Integer.toString(mPort);
}
String authInfo = "";
if (mAuthInfo.length() > 0) {
authInfo = mAuthInfo + "@";
}
return mScheme + "://" + authInfo + mHost + port + mPath;
}
}
@@ -0,0 +1,466 @@
/*
* Copyright (C) 2007 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.net.http;
import com.android.internal.http.HttpDateTime;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.net.URI;
import android.content.Context;
import android.content.ContentResolver;
import android.net.SSLCertificateSocketFactory;
import android.net.SSLSessionCache;
import android.os.Looper;
import android.util.Log;
/**
* Subclass of the Apache {@link DefaultHttpClient} that is configured with
* reasonable default settings and registered schemes for Android, and
* also lets the user add {@link HttpRequestInterceptor} classes.
* Don't create this directly, use the {@link #newInstance} factory method.
*
* <p>This client processes cookies but does not retain them by default.
* To retain cookies, simply add a cookie store to the HttpContext:</p>
*
* <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
*/
public final class AndroidHttpClient implements HttpClient {
// Gzip of data shorter than this probably won't be worthwhile
public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
private static final String TAG = "AndroidHttpClient";
/** Interceptor throws an exception if the executing thread is blocked */
private static final HttpRequestInterceptor sThreadCheckInterceptor =
new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) {
// Prevent the HttpRequest from being sent on the main thread
if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
throw new RuntimeException("This thread forbids HTTP requests");
}
}
};
/**
* Create a new HttpClient with reasonable defaults (which you can update).
*
* @param userAgent to report in your HTTP requests
* @param context to use for caching SSL sessions (may be null for no caching)
* @return AndroidHttpClient for you to use for all your requests.
*/
public static AndroidHttpClient newInstance(String userAgent, Context context) {
HttpParams params = new BasicHttpParams();
// Turn off stale checking. Our connections break all the time anyway,
// and it's not worth it to pay the penalty of checking every time.
HttpConnectionParams.setStaleCheckingEnabled(params, false);
// Default connection and socket timeout of 20 seconds. Tweak to taste.
HttpConnectionParams.setConnectionTimeout(params, 60 * 1000);
HttpConnectionParams.setSoTimeout(params, 60 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 8192);
// Don't handle redirects -- return them to the caller. Our code
// often wants to re-POST after a redirect, which we must do ourselves.
HttpClientParams.setRedirecting(params, false);
// Use a session cache for SSL sockets
SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
// Set the specified user agent and register standard protocols.
HttpProtocolParams.setUserAgent(params, userAgent);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https",
SSLCertificateSocketFactory.getHttpSocketFactory(30 * 1000, sessionCache), 443));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
// We use a factory method to modify superclass initialization
// parameters without the funny call-a-static-method dance.
return new AndroidHttpClient(manager, params);
}
/**
* Create a new HttpClient with reasonable defaults (which you can update).
* @param userAgent to report in your HTTP requests.
* @return AndroidHttpClient for you to use for all your requests.
*/
public static AndroidHttpClient newInstance(String userAgent) {
return newInstance(userAgent, null /* session cache */);
}
private final HttpClient delegate;
private RuntimeException mLeakedException = new IllegalStateException(
"AndroidHttpClient created and never closed");
private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
this.delegate = new DefaultHttpClient(ccm, params) {
@Override
protected BasicHttpProcessor createHttpProcessor() {
// Add interceptor to prevent making requests from main thread.
BasicHttpProcessor processor = super.createHttpProcessor();
processor.addRequestInterceptor(sThreadCheckInterceptor);
processor.addRequestInterceptor(new CurlLogger());
return processor;
}
@Override
protected HttpContext createHttpContext() {
// Same as DefaultHttpClient.createHttpContext() minus the
// cookie store.
HttpContext context = new BasicHttpContext();
context.setAttribute(
ClientContext.AUTHSCHEME_REGISTRY,
getAuthSchemes());
context.setAttribute(
ClientContext.COOKIESPEC_REGISTRY,
getCookieSpecs());
context.setAttribute(
ClientContext.CREDS_PROVIDER,
getCredentialsProvider());
return context;
}
};
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (mLeakedException != null) {
Log.e(TAG, "Leak found", mLeakedException);
mLeakedException = null;
}
}
/**
* Modifies a request to indicate to the server that we would like a
* gzipped response. (Uses the "Accept-Encoding" HTTP header.)
* @param request the request to modify
* @see #getUngzippedContent
*/
public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
request.addHeader("Accept-Encoding", "gzip");
}
/**
* Gets the input stream from a response entity. If the entity is gzipped
* then this will get a stream over the uncompressed data.
*
* @param entity the entity whose content should be read
* @return the input stream to read from
* @throws IOException
*/
public static InputStream getUngzippedContent(HttpEntity entity)
throws IOException {
InputStream responseStream = entity.getContent();
if (responseStream == null) return responseStream;
Header header = entity.getContentEncoding();
if (header == null) return responseStream;
String contentEncoding = header.getValue();
if (contentEncoding == null) return responseStream;
if (contentEncoding.contains("gzip")) responseStream
= new GZIPInputStream(responseStream);
return responseStream;
}
/**
* Release resources associated with this client. You must call this,
* or significant resources (sockets and memory) may be leaked.
*/
public void close() {
if (mLeakedException != null) {
getConnectionManager().shutdown();
mLeakedException = null;
}
}
public HttpParams getParams() {
return delegate.getParams();
}
public ClientConnectionManager getConnectionManager() {
return delegate.getConnectionManager();
}
public HttpResponse execute(HttpUriRequest request) throws IOException {
return delegate.execute(request);
}
public HttpResponse execute(HttpUriRequest request, HttpContext context)
throws IOException {
return delegate.execute(request, context);
}
public HttpResponse execute(HttpHost target, HttpRequest request)
throws IOException {
return delegate.execute(target, request);
}
public HttpResponse execute(HttpHost target, HttpRequest request,
HttpContext context) throws IOException {
return delegate.execute(target, request, context);
}
public <T> T execute(HttpUriRequest request,
ResponseHandler<? extends T> responseHandler)
throws IOException, ClientProtocolException {
return delegate.execute(request, responseHandler);
}
public <T> T execute(HttpUriRequest request,
ResponseHandler<? extends T> responseHandler, HttpContext context)
throws IOException, ClientProtocolException {
return delegate.execute(request, responseHandler, context);
}
public <T> T execute(HttpHost target, HttpRequest request,
ResponseHandler<? extends T> responseHandler) throws IOException,
ClientProtocolException {
return delegate.execute(target, request, responseHandler);
}
public <T> T execute(HttpHost target, HttpRequest request,
ResponseHandler<? extends T> responseHandler, HttpContext context)
throws IOException, ClientProtocolException {
return delegate.execute(target, request, responseHandler, context);
}
/**
* Compress data to send to server.
* Creates a Http Entity holding the gzipped data.
* The data will not be compressed if it is too short.
* @param data The bytes to compress
* @return Entity holding the data
*/
public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
throws IOException {
AbstractHttpEntity entity;
if (data.length < getMinGzipSize(resolver)) {
entity = new ByteArrayEntity(data);
} else {
ByteArrayOutputStream arr = new ByteArrayOutputStream();
OutputStream zipper = new GZIPOutputStream(arr);
zipper.write(data);
zipper.close();
entity = new ByteArrayEntity(arr.toByteArray());
entity.setContentEncoding("gzip");
}
return entity;
}
/**
* Retrieves the minimum size for compressing data.
* Shorter data will not be compressed.
*/
public static long getMinGzipSize(ContentResolver resolver) {
return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant.
}
/* cURL logging support. */
/**
* Logging tag and level.
*/
private static class LoggingConfiguration {
private final String tag;
private final int level;
private LoggingConfiguration(String tag, int level) {
this.tag = tag;
this.level = level;
}
/**
* Returns true if logging is turned on for this configuration.
*/
private boolean isLoggable() {
return Log.isLoggable(tag, level);
}
/**
* Prints a message using this configuration.
*/
private void println(String message) {
Log.println(level, tag, message);
}
}
/** cURL logging configuration. */
private volatile LoggingConfiguration curlConfiguration;
/**
* Enables cURL request logging for this client.
*
* @param name to log messages with
* @param level at which to log messages (see {@link android.util.Log})
*/
public void enableCurlLogging(String name, int level) {
if (name == null) {
throw new NullPointerException("name");
}
if (level < Log.VERBOSE || level > Log.ASSERT) {
throw new IllegalArgumentException("Level is out of range ["
+ Log.VERBOSE + ".." + Log.ASSERT + "]");
}
curlConfiguration = new LoggingConfiguration(name, level);
}
/**
* Disables cURL logging for this client.
*/
public void disableCurlLogging() {
curlConfiguration = null;
}
/**
* Logs cURL commands equivalent to requests.
*/
private class CurlLogger implements HttpRequestInterceptor {
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException {
LoggingConfiguration configuration = curlConfiguration;
if (configuration != null
&& configuration.isLoggable()
&& request instanceof HttpUriRequest) {
// Never print auth token -- we used to check ro.secure=0 to
// enable that, but can't do that in unbundled code.
configuration.println(toCurl((HttpUriRequest) request, false));
}
}
}
/**
* Generates a cURL command equivalent to the given request.
*/
private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
StringBuilder builder = new StringBuilder();
builder.append("curl ");
for (Header header: request.getAllHeaders()) {
if (!logAuthToken
&& (header.getName().equals("Authorization") ||
header.getName().equals("Cookie"))) {
continue;
}
builder.append("--header \"");
builder.append(header.toString().trim());
builder.append("\" ");
}
URI uri = request.getURI();
// If this is a wrapped request, use the URI from the original
// request instead. getURI() on the wrapper seems to return a
// relative URI. We want an absolute URI.
if (request instanceof RequestWrapper) {
HttpRequest original = ((RequestWrapper) request).getOriginal();
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
}
}
builder.append("\"");
builder.append(uri);
builder.append("\"");
if (request instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest entityRequest =
(HttpEntityEnclosingRequest) request;
HttpEntity entity = entityRequest.getEntity();
if (entity != null && entity.isRepeatable()) {
if (entity.getContentLength() < 1024) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
entity.writeTo(stream);
String entityString = stream.toString();
// TODO: Check the content type, too.
builder.append(" --data-ascii \"")
.append(entityString)
.append("\"");
} else {
builder.append(" [TOO MUCH DATA TO INCLUDE]");
}
}
}
return builder.toString();
}
/**
* Returns the date of the given HTTP date string. This method can identify
* and parse the date formats emitted by common HTTP servers, such as
* <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
* <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
* <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
* <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
* <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
* C's asctime()</a>.
*
* @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
* @throws IllegalArgumentException if {@code dateString} is not a date or
* of an unsupported format.
*/
public static long parseDate(String dateString) {
return HttpDateTime.parse(dateString);
}
}
@@ -0,0 +1,464 @@
/*
* Copyright (C) 2008 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.net.http;
import org.apache.http.Header;
import org.apache.http.HttpConnection;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpConnectionMetrics;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpInetConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseFactory;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ContentLengthStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.HttpConnectionMetricsImpl;
import org.apache.http.impl.entity.EntitySerializer;
import org.apache.http.impl.entity.StrictContentLengthStrategy;
import org.apache.http.impl.io.ChunkedInputStream;
import org.apache.http.impl.io.ContentLengthInputStream;
import org.apache.http.impl.io.HttpRequestWriter;
import org.apache.http.impl.io.IdentityInputStream;
import org.apache.http.impl.io.SocketInputBuffer;
import org.apache.http.impl.io.SocketOutputBuffer;
import org.apache.http.io.HttpMessageWriter;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.io.SessionOutputBuffer;
import org.apache.http.message.BasicLineParser;
import org.apache.http.message.ParserCursor;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.ParseException;
import org.apache.http.util.CharArrayBuffer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
/**
* A alternate class for (@link DefaultHttpClientConnection).
* It has better performance than DefaultHttpClientConnection
*
* {@hide}
*/
public class AndroidHttpClientConnection
implements HttpInetConnection, HttpConnection {
private SessionInputBuffer inbuffer = null;
private SessionOutputBuffer outbuffer = null;
private int maxHeaderCount;
// store CoreConnectionPNames.MAX_LINE_LENGTH for performance
private int maxLineLength;
private final EntitySerializer entityserializer;
private HttpMessageWriter requestWriter = null;
private HttpConnectionMetricsImpl metrics = null;
private volatile boolean open;
private Socket socket = null;
public AndroidHttpClientConnection() {
this.entityserializer = new EntitySerializer(
new StrictContentLengthStrategy());
}
/**
* Bind socket and set HttpParams to AndroidHttpClientConnection
* @param socket outgoing socket
* @param params HttpParams
* @throws IOException
*/
public void bind(
final Socket socket,
final HttpParams params) throws IOException {
if (socket == null) {
throw new IllegalArgumentException("Socket may not be null");
}
if (params == null) {
throw new IllegalArgumentException("HTTP parameters may not be null");
}
assertNotOpen();
socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
int linger = HttpConnectionParams.getLinger(params);
if (linger >= 0) {
socket.setSoLinger(linger > 0, linger);
}
this.socket = socket;
int buffersize = HttpConnectionParams.getSocketBufferSize(params);
this.inbuffer = new SocketInputBuffer(socket, buffersize, params);
this.outbuffer = new SocketOutputBuffer(socket, buffersize, params);
maxHeaderCount = params.getIntParameter(
CoreConnectionPNames.MAX_HEADER_COUNT, -1);
maxLineLength = params.getIntParameter(
CoreConnectionPNames.MAX_LINE_LENGTH, -1);
this.requestWriter = new HttpRequestWriter(outbuffer, null, params);
this.metrics = new HttpConnectionMetricsImpl(
inbuffer.getMetrics(),
outbuffer.getMetrics());
this.open = true;
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(getClass().getSimpleName()).append("[");
if (isOpen()) {
buffer.append(getRemotePort());
} else {
buffer.append("closed");
}
buffer.append("]");
return buffer.toString();
}
private void assertNotOpen() {
if (this.open) {
throw new IllegalStateException("Connection is already open");
}
}
private void assertOpen() {
if (!this.open) {
throw new IllegalStateException("Connection is not open");
}
}
public boolean isOpen() {
// to make this method useful, we want to check if the socket is connected
return (this.open && this.socket != null && this.socket.isConnected());
}
public InetAddress getLocalAddress() {
if (this.socket != null) {
return this.socket.getLocalAddress();
} else {
return null;
}
}
public int getLocalPort() {
if (this.socket != null) {
return this.socket.getLocalPort();
} else {
return -1;
}
}
public InetAddress getRemoteAddress() {
if (this.socket != null) {
return this.socket.getInetAddress();
} else {
return null;
}
}
public int getRemotePort() {
if (this.socket != null) {
return this.socket.getPort();
} else {
return -1;
}
}
public void setSocketTimeout(int timeout) {
assertOpen();
if (this.socket != null) {
try {
this.socket.setSoTimeout(timeout);
} catch (SocketException ignore) {
// It is not quite clear from the original documentation if there are any
// other legitimate cases for a socket exception to be thrown when setting
// SO_TIMEOUT besides the socket being already closed
}
}
}
public int getSocketTimeout() {
if (this.socket != null) {
try {
return this.socket.getSoTimeout();
} catch (SocketException ignore) {
return -1;
}
} else {
return -1;
}
}
public void shutdown() throws IOException {
this.open = false;
Socket tmpsocket = this.socket;
if (tmpsocket != null) {
tmpsocket.close();
}
}
public void close() throws IOException {
if (!this.open) {
return;
}
this.open = false;
doFlush();
try {
try {
this.socket.shutdownOutput();
} catch (IOException ignore) {
}
try {
this.socket.shutdownInput();
} catch (IOException ignore) {
}
} catch (UnsupportedOperationException ignore) {
// if one isn't supported, the other one isn't either
}
this.socket.close();
}
/**
* Sends the request line and all headers over the connection.
* @param request the request whose headers to send.
* @throws HttpException
* @throws IOException
*/
public void sendRequestHeader(final HttpRequest request)
throws HttpException, IOException {
if (request == null) {
throw new IllegalArgumentException("HTTP request may not be null");
}
assertOpen();
this.requestWriter.write(request);
this.metrics.incrementRequestCount();
}
/**
* Sends the request entity over the connection.
* @param request the request whose entity to send.
* @throws HttpException
* @throws IOException
*/
public void sendRequestEntity(final HttpEntityEnclosingRequest request)
throws HttpException, IOException {
if (request == null) {
throw new IllegalArgumentException("HTTP request may not be null");
}
assertOpen();
if (request.getEntity() == null) {
return;
}
this.entityserializer.serialize(
this.outbuffer,
request,
request.getEntity());
}
protected void doFlush() throws IOException {
this.outbuffer.flush();
}
public void flush() throws IOException {
assertOpen();
doFlush();
}
/**
* Parses the response headers and adds them to the
* given {@code headers} object, and returns the response StatusLine
* @param headers store parsed header to headers.
* @throws IOException
* @return StatusLine
* @see HttpClientConnection#receiveResponseHeader()
*/
public StatusLine parseResponseHeader(Headers headers)
throws IOException, ParseException {
assertOpen();
CharArrayBuffer current = new CharArrayBuffer(64);
if (inbuffer.readLine(current) == -1) {
throw new NoHttpResponseException("The target server failed to respond");
}
// Create the status line from the status string
StatusLine statusline = BasicLineParser.DEFAULT.parseStatusLine(
current, new ParserCursor(0, current.length()));
if (HttpLog.LOGV) HttpLog.v("read: " + statusline);
int statusCode = statusline.getStatusCode();
// Parse header body
CharArrayBuffer previous = null;
int headerNumber = 0;
while(true) {
if (current == null) {
current = new CharArrayBuffer(64);
} else {
// This must be he buffer used to parse the status
current.clear();
}
int l = inbuffer.readLine(current);
if (l == -1 || current.length() < 1) {
break;
}
// Parse the header name and value
// Check for folded headers first
// Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
// discussion on folded headers
char first = current.charAt(0);
if ((first == ' ' || first == '\t') && previous != null) {
// we have continuation folded header
// so append value
int start = 0;
int length = current.length();
while (start < length) {
char ch = current.charAt(start);
if (ch != ' ' && ch != '\t') {
break;
}
start++;
}
if (maxLineLength > 0 &&
previous.length() + 1 + current.length() - start >
maxLineLength) {
throw new IOException("Maximum line length limit exceeded");
}
previous.append(' ');
previous.append(current, start, current.length() - start);
} else {
if (previous != null) {
headers.parseHeader(previous);
}
headerNumber++;
previous = current;
current = null;
}
if (maxHeaderCount > 0 && headerNumber >= maxHeaderCount) {
throw new IOException("Maximum header count exceeded");
}
}
if (previous != null) {
headers.parseHeader(previous);
}
if (statusCode >= 200) {
this.metrics.incrementResponseCount();
}
return statusline;
}
/**
* Return the next response entity.
* @param headers contains values for parsing entity
* @see HttpClientConnection#receiveResponseEntity(HttpResponse response)
*/
public HttpEntity receiveResponseEntity(final Headers headers) {
assertOpen();
BasicHttpEntity entity = new BasicHttpEntity();
long len = determineLength(headers);
if (len == ContentLengthStrategy.CHUNKED) {
entity.setChunked(true);
entity.setContentLength(-1);
entity.setContent(new ChunkedInputStream(inbuffer));
} else if (len == ContentLengthStrategy.IDENTITY) {
entity.setChunked(false);
entity.setContentLength(-1);
entity.setContent(new IdentityInputStream(inbuffer));
} else {
entity.setChunked(false);
entity.setContentLength(len);
entity.setContent(new ContentLengthInputStream(inbuffer, len));
}
String contentTypeHeader = headers.getContentType();
if (contentTypeHeader != null) {
entity.setContentType(contentTypeHeader);
}
String contentEncodingHeader = headers.getContentEncoding();
if (contentEncodingHeader != null) {
entity.setContentEncoding(contentEncodingHeader);
}
return entity;
}
private long determineLength(final Headers headers) {
long transferEncoding = headers.getTransferEncoding();
// We use Transfer-Encoding if present and ignore Content-Length.
// RFC2616, 4.4 item number 3
if (transferEncoding < Headers.NO_TRANSFER_ENCODING) {
return transferEncoding;
} else {
long contentlen = headers.getContentLength();
if (contentlen > Headers.NO_CONTENT_LENGTH) {
return contentlen;
} else {
return ContentLengthStrategy.IDENTITY;
}
}
}
/**
* Checks whether this connection has gone down.
* Network connections may get closed during some time of inactivity
* for several reasons. The next time a read is attempted on such a
* connection it will throw an IOException.
* This method tries to alleviate this inconvenience by trying to
* find out if a connection is still usable. Implementations may do
* that by attempting a read with a very small timeout. Thus this
* method may block for a small amount of time before returning a result.
* It is therefore an <i>expensive</i> operation.
*
* @return <code>true</code> if attempts to use this connection are
* likely to succeed, or <code>false</code> if they are likely
* to fail and this connection should be closed
*/
public boolean isStale() {
assertOpen();
try {
this.inbuffer.isDataAvailable(1);
return false;
} catch (IOException ex) {
return true;
}
}
/**
* Returns a collection of connection metrcis
* @return HttpConnectionMetrics
*/
public HttpConnectionMetrics getMetrics() {
return this.metrics;
}
}
@@ -0,0 +1,236 @@
/*
* Copyright (C) 2008 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.net.http;
import com.android.internal.net.DomainNameValidator;
import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.Date;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Class responsible for all server certificate validation functionality
*
* {@hide}
*/
class CertificateChainValidator {
/**
* The singleton instance of the certificate chain validator
*/
private static final CertificateChainValidator sInstance
= new CertificateChainValidator();
/**
* @return The singleton instance of the certificates chain validator
*/
public static CertificateChainValidator getInstance() {
return sInstance;
}
/**
* Creates a new certificate chain validator. This is a private constructor.
* If you need a Certificate chain validator, call getInstance().
*/
private CertificateChainValidator() {}
/**
* Performs the handshake and server certificates validation
* Notice a new chain will be rebuilt by tracing the issuer and subject
* before calling checkServerTrusted().
* And if the last traced certificate is self issued and it is expired, it
* will be dropped.
* @param sslSocket The secure connection socket
* @param domain The website domain
* @return An SSL error object if there is an error and null otherwise
*/
public SslError doHandshakeAndValidateServerCertificates(
HttpsConnection connection, SSLSocket sslSocket, String domain)
throws IOException {
X509Certificate[] serverCertificates = null;
// start handshake, close the socket if we fail
try {
sslSocket.setUseClientMode(true);
sslSocket.startHandshake();
} catch (IOException e) {
closeSocketThrowException(
sslSocket, e.getMessage(),
"failed to perform SSL handshake");
}
// retrieve the chain of the server peer certificates
Certificate[] peerCertificates =
sslSocket.getSession().getPeerCertificates();
if (peerCertificates == null || peerCertificates.length <= 0) {
closeSocketThrowException(
sslSocket, "failed to retrieve peer certificates");
} else {
serverCertificates =
new X509Certificate[peerCertificates.length];
for (int i = 0; i < peerCertificates.length; ++i) {
serverCertificates[i] =
(X509Certificate)(peerCertificates[i]);
}
// update the SSL certificate associated with the connection
if (connection != null) {
if (serverCertificates[0] != null) {
connection.setCertificate(
new SslCertificate(serverCertificates[0]));
}
}
}
// check if the first certificate in the chain is for this site
X509Certificate currCertificate = serverCertificates[0];
if (currCertificate == null) {
closeSocketThrowException(
sslSocket, "certificate for this site is null");
} else {
if (!DomainNameValidator.match(currCertificate, domain)) {
String errorMessage = "certificate not for this host: " + domain;
if (HttpLog.LOGV) {
HttpLog.v(errorMessage);
}
sslSocket.getSession().invalidate();
return new SslError(
SslError.SSL_IDMISMATCH, currCertificate);
}
}
// Clean up the certificates chain and build a new one.
// Theoretically, we shouldn't have to do this, but various web servers
// in practice are mis-configured to have out-of-order certificates or
// expired self-issued root certificate.
int chainLength = serverCertificates.length;
if (serverCertificates.length > 1) {
// 1. we clean the received certificates chain.
// We start from the end-entity certificate, tracing down by matching
// the "issuer" field and "subject" field until we can't continue.
// This helps when the certificates are out of order or
// some certificates are not related to the site.
int currIndex;
for (currIndex = 0; currIndex < serverCertificates.length; ++currIndex) {
boolean foundNext = false;
for (int nextIndex = currIndex + 1;
nextIndex < serverCertificates.length;
++nextIndex) {
if (serverCertificates[currIndex].getIssuerDN().equals(
serverCertificates[nextIndex].getSubjectDN())) {
foundNext = true;
// Exchange certificates so that 0 through currIndex + 1 are in proper order
if (nextIndex != currIndex + 1) {
X509Certificate tempCertificate = serverCertificates[nextIndex];
serverCertificates[nextIndex] = serverCertificates[currIndex + 1];
serverCertificates[currIndex + 1] = tempCertificate;
}
break;
}
}
if (!foundNext) break;
}
// 2. we exam if the last traced certificate is self issued and it is expired.
// If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
// have a similar but unexpired trusted root.
chainLength = currIndex + 1;
X509Certificate lastCertificate = serverCertificates[chainLength - 1];
Date now = new Date();
if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
&& now.after(lastCertificate.getNotAfter())) {
--chainLength;
}
}
// 3. Now we copy the newly built chain into an appropriately sized array.
X509Certificate[] newServerCertificates = null;
newServerCertificates = new X509Certificate[chainLength];
for (int i = 0; i < chainLength; ++i) {
newServerCertificates[i] = serverCertificates[i];
}
// first, we validate the new chain using the standard validation
// solution; if we do not find any errors, we are done; if we
// fail the standard validation, we re-validate again below,
// this time trying to retrieve any individual errors we can
// report back to the user.
//
try {
SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(
newServerCertificates, "RSA");
// no errors!!!
return null;
} catch (CertificateException e) {
sslSocket.getSession().invalidate();
if (HttpLog.LOGV) {
HttpLog.v(
"failed to pre-validate the certificate chain, error: " +
e.getMessage());
}
return new SslError(
SslError.SSL_UNTRUSTED, currCertificate);
}
}
private void closeSocketThrowException(
SSLSocket socket, String errorMessage, String defaultErrorMessage)
throws IOException {
closeSocketThrowException(
socket, errorMessage != null ? errorMessage : defaultErrorMessage);
}
private void closeSocketThrowException(SSLSocket socket,
String errorMessage) throws IOException {
if (HttpLog.LOGV) {
HttpLog.v("validation error: " + errorMessage);
}
if (socket != null) {
SSLSession session = socket.getSession();
if (session != null) {
session.invalidate();
}
socket.close();
}
throw new SSLHandshakeException(errorMessage);
}
}
@@ -0,0 +1,256 @@
/*
* Copyright (C) 2008 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.net.http;
import android.os.SystemClock;
import android.security.Sha1MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertPath;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
/**
* Validator cache used to speed-up certificate chain validation. The idea is
* to keep each secure domain name associated with a cryptographically secure
* hash of the certificate chain successfully used to validate the domain. If
* we establish connection with the domain more than once and each time receive
* the same list of certificates, we do not have to re-validate.
*
* {@hide}
*/
class CertificateValidatorCache {
// TODO: debug only!
public static long mSave = 0;
public static long mCost = 0;
// TODO: debug only!
/**
* The cache-entry lifetime in milliseconds (here, 10 minutes)
*/
private static final long CACHE_ENTRY_LIFETIME = 10 * 60 * 1000;
/**
* The certificate factory
*/
private static CertificateFactory sCertificateFactory;
/**
* The certificate validator cache map (domain to a cache entry)
*/
private HashMap<Integer, CacheEntry> mCacheMap;
/**
* Random salt
*/
private int mBigScrew;
/**
* @param certificate The array of server certificates to compute a
* secure hash from
* @return The secure hash computed from server certificates
*/
public static byte[] secureHash(Certificate[] certificates) {
byte[] secureHash = null;
// TODO: debug only!
long beg = SystemClock.uptimeMillis();
// TODO: debug only!
if (certificates != null && certificates.length != 0) {
byte[] encodedCertPath = null;
try {
synchronized (CertificateValidatorCache.class) {
if (sCertificateFactory == null) {
try {
sCertificateFactory =
CertificateFactory.getInstance("X.509");
} catch(GeneralSecurityException e) {
if (HttpLog.LOGV) {
HttpLog.v("CertificateValidatorCache:" +
" failed to create the certificate factory");
}
}
}
}
CertPath certPath =
sCertificateFactory.generateCertPath(Arrays.asList(certificates));
if (certPath != null) {
encodedCertPath = certPath.getEncoded();
if (encodedCertPath != null) {
Sha1MessageDigest messageDigest =
new Sha1MessageDigest();
secureHash = messageDigest.digest(encodedCertPath);
}
}
} catch (GeneralSecurityException e) {}
}
// TODO: debug only!
long end = SystemClock.uptimeMillis();
mCost += (end - beg);
// TODO: debug only!
return secureHash;
}
/**
* Creates a new certificate-validator cache
*/
public CertificateValidatorCache() {
Random random = new Random();
mBigScrew = random.nextInt();
mCacheMap = new HashMap<Integer, CacheEntry>();
}
/**
* @param domain The domain to check against
* @param secureHash The secure hash to check against
* @return True iff there is a valid (not expired) cache entry
* associated with the domain and the secure hash
*/
public boolean has(String domain, byte[] secureHash) {
boolean rval = false;
if (domain != null && domain.length() != 0) {
if (secureHash != null && secureHash.length != 0) {
CacheEntry cacheEntry = (CacheEntry)mCacheMap.get(
new Integer(mBigScrew ^ domain.hashCode()));
if (cacheEntry != null) {
if (!cacheEntry.expired()) {
rval = cacheEntry.has(domain, secureHash);
// TODO: debug only!
if (rval) {
mSave += cacheEntry.mSave;
}
// TODO: debug only!
} else {
mCacheMap.remove(cacheEntry);
}
}
}
}
return rval;
}
/**
* Adds the (domain, secureHash) tuple to the cache
* @param domain The domain to be added to the cache
* @param secureHash The secure hash to be added to the cache
* @return True iff succeeds
*/
public boolean put(String domain, byte[] secureHash, long save) {
if (domain != null && domain.length() != 0) {
if (secureHash != null && secureHash.length != 0) {
mCacheMap.put(
new Integer(mBigScrew ^ domain.hashCode()),
new CacheEntry(domain, secureHash, save));
return true;
}
}
return false;
}
/**
* Certificate-validator cache entry. We have one per domain
*/
private class CacheEntry {
/**
* The hash associated with this cache entry
*/
private byte[] mHash;
/**
* The time associated with this cache entry
*/
private long mTime;
// TODO: debug only!
public long mSave;
// TODO: debug only!
/**
* The host associated with this cache entry
*/
private String mDomain;
/**
* Creates a new certificate-validator cache entry
* @param domain The domain to be associated with this cache entry
* @param secureHash The secure hash to be associated with this cache
* entry
*/
public CacheEntry(String domain, byte[] secureHash, long save) {
mDomain = domain;
mHash = secureHash;
// TODO: debug only!
mSave = save;
// TODO: debug only!
mTime = SystemClock.uptimeMillis();
}
/**
* @return True iff the cache item has expired
*/
public boolean expired() {
return CACHE_ENTRY_LIFETIME < SystemClock.uptimeMillis() - mTime;
}
/**
* @param domain The domain to check
* @param secureHash The secure hash to check
* @return True iff the given domain and hash match those associated
* with this entry
*/
public boolean has(String domain, byte[] secureHash) {
if (domain != null && 0 < domain.length()) {
if (!mDomain.equals(domain)) {
return false;
}
}
if (secureHash != null) {
int hashLength = secureHash.length;
if (0 < hashLength) {
if (hashLength == mHash.length) {
for (int i = 0; i < hashLength; ++i) {
if (secureHash[i] != mHash[i]) {
return false;
}
}
return true;
}
}
}
return false;
}
}
};
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2008 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.net.http;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.protocol.HTTP;
/**
* Utility methods for working on CharArrayBuffers.
*
* {@hide}
*/
class CharArrayBuffers {
static final char uppercaseAddon = 'a' - 'A';
/**
* Returns true if the buffer contains the given string. Ignores leading
* whitespace and case.
*
* @param buffer to search
* @param beginIndex index at which we should start
* @param str to search for
*/
static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer,
int beginIndex, final String str) {
int len = buffer.length();
char[] chars = buffer.buffer();
while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) {
beginIndex++;
}
int size = str.length();
boolean ok = len >= beginIndex + size;
for (int j=0; ok && (j<size); j++) {
char a = chars[beginIndex+j];
char b = str.charAt(j);
if (a != b) {
a = toLower(a);
b = toLower(b);
ok = a == b;
}
}
return ok;
}
/**
* Returns index of first occurence ch. Lower cases characters leading up
* to first occurrence of ch.
*/
static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) {
int beginIndex = 0;
int endIndex = buffer.length();
char[] chars = buffer.buffer();
for (int i = beginIndex; i < endIndex; i++) {
char current = chars[i];
if (current == ch) {
return i;
} else if (current >= 'A' && current <= 'Z'){
// make lower case
current += uppercaseAddon;
chars[i] = current;
}
}
return -1;
}
private static char toLower(char c) {
if (c >= 'A' && c <= 'Z'){
c += uppercaseAddon;
}
return c;
}
}
@@ -0,0 +1,587 @@
/*
* Copyright (C) 2007 The Android Open Source Project
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
*
* 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.net.http;
import android.content.Context;
import android.os.SystemClock;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ListIterator;
import java.util.LinkedList;
import javax.net.ssl.SSLHandshakeException;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
/**
* {@hide}
*/
abstract class Connection {
/**
* Allow a TCP connection 60 idle seconds before erroring out
*/
static final int SOCKET_TIMEOUT = 60000;
private static final int MAX_PRIORITY = 1000;
private static final int SEND = 0;
private static final int READ = 1;
private static final int DRAIN = 2;
private static final int DONE = 3;
private static final int PRIO = 4;
private static final String[] states = {"SEND", "READ", "DRAIN", "DONE", "PRIO"};
private ConnectionThread mConnectionThread;
Context mContext;
/** The low level connection */
protected AndroidHttpClientConnection mHttpClientConnection = null;
/**
* The server SSL certificate associated with this connection
* (null if the connection is not secure)
* It would be nice to store the whole certificate chain, but
* we want to keep things as light-weight as possible
*/
protected SslCertificate mCertificate = null;
/**
* The host this connection is connected to. If using proxy,
* this is set to the proxy address
*/
HttpHost mHost;
/** true if the connection can be reused for sending more requests */
private boolean mCanPersist;
/** context required by ConnectionReuseStrategy. */
private HttpContext mHttpContext;
/** set when cancelled */
private static int STATE_NORMAL = 0;
private static int STATE_CANCEL_REQUESTED = 1;
private int mActive = STATE_NORMAL;
/** The number of times to try to re-connect (if connect fails). */
private final static int RETRY_REQUEST_LIMIT = 2;
private static final int MIN_PIPE = 2;
private static final int MAX_PIPE = 3;
/**
* Doesn't seem to exist anymore in the new HTTP client, so copied here.
*/
private static final String HTTP_CONNECTION = "http.connection";
RequestFeeder mRequestFeeder;
/**
* Buffer for feeding response blocks to webkit. One block per
* connection reduces memory churn.
*/
private byte[] mBuf;
private boolean mIsTcpPreConnect;
protected Connection(Context context, HttpHost host,
RequestFeeder requestFeeder) {
mContext = context;
mHost = host;
mRequestFeeder = requestFeeder;
mCanPersist = false;
mHttpContext = new BasicHttpContext(null);
mConnectionThread = null;
mIsTcpPreConnect = false;
}
HttpHost getHost() {
return mHost;
}
/**
* connection factory: returns an HTTP or HTTPS connection as
* necessary
*/
static Connection getConnection(
Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
return new HttpsConnection(context, host, proxy, requestFeeder);
}
/**
* @return The server SSL certificate associated with this
* connection (null if the connection is not secure)
*/
/* package */ SslCertificate getCertificate() {
return mCertificate;
}
void setConnectionThread(ConnectionThread thread) {
mConnectionThread = thread;
}
/**
* Close current network connection
* Note: this runs in non-network thread
*/
void cancel() {
mActive = STATE_CANCEL_REQUESTED;
closeConnection();
if (HttpLog.LOGV) HttpLog.v(
"Connection.cancel(): connection closed " + mHost);
}
/**
* Process requests in queue
* pipelines requests
*/
void processRequests(Request firstRequest) {
Request req = null;
Request peek = null;
boolean empty;
int error = EventHandler.OK;
Exception exception = null;
LinkedList<Request> pipe = new LinkedList<Request>();
int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
int state = SEND;
while (state != DONE) {
if (HttpLog.LOGV) HttpLog.v(
states[state] + " pipe " + pipe.size());
/* If a request was cancelled, give other cancel requests
some time to go through so we don't uselessly restart
connections */
if (mActive == STATE_CANCEL_REQUESTED) {
try {
Thread.sleep(100);
} catch (InterruptedException x) { /* ignore */ }
mActive = STATE_NORMAL;
}
switch (state) {
case SEND: {
if (pipe.size() == maxPipe) {
state = READ;
break;
}
/* get a request */
if (firstRequest == null) {
req = mRequestFeeder.getRequest(mHost);
} else {
req = firstRequest;
firstRequest = null;
}
if (req == null) {
state = DRAIN;
break;
}
// ### synchronize on mRequestFeeder instead of requeing newreq?
if (req.mPriority == -1 ||
req.mPriority > MAX_PRIORITY) {
/*
|| pipe.size() + 1 == maxPipe
|| req.mPriority == -1) {
*/
peek = mRequestFeeder.peekRequest();
if (peek != null) {
int ppri = peek.mPriority;
if ((req.mPriority == -1 && ppri >= 0)
|| (req.mPriority >= 0 && ppri < req.mPriority)) {
Request newreq = mRequestFeeder.getRequest();
if (newreq != null) {
if (!newreq.equals(peek)
|| peek.mPriority != ppri) {
mRequestFeeder.requeueRequest(newreq, false, true);
} else {
mConnectionThread.setNewRequest(newreq);
state = PRIO;
mRequestFeeder.requeueRequest(req, false, true);
break;
}
}
}
}
}
req.setConnection(this);
/* Don't work on cancelled requests. */
if (req.mCancelled) {
if (HttpLog.LOGV) HttpLog.v(
"processRequests(): skipping cancelled request "
+ req);
req.complete();
break;
}
if (mHttpClientConnection == null ||
!mHttpClientConnection.isOpen()) {
/* If this call fails, the address is bad or
the net is down. Punt for now.
FIXME: blow out entire queue here on
connection failure if net up? */
if (!openHttpConnection(req)) {
state = DONE;
break;
}
}
/* we have a connection, let the event handler
* know of any associated certificate,
* potentially none.
*/
req.mEventHandler.certificate(mCertificate);
try {
/* FIXME: don't increment failure count if old
connection? There should not be a penalty for
attempting to reuse an old connection */
req.sendRequest(mHttpClientConnection);
} catch (HttpException e) {
exception = e;
error = EventHandler.ERROR;
} catch (IOException e) {
exception = e;
error = EventHandler.ERROR_IO;
} catch (IllegalStateException e) {
exception = e;
error = EventHandler.ERROR_IO;
}
if (exception != null) {
if (httpFailure(req, error, exception) &&
!req.mCancelled) {
/* retry request if not permanent failure
or cancelled */
pipe.addLast(req);
}
exception = null;
if (clearPipe(pipe))
state = DONE;
else if (state != PRIO)
state = SEND;
minPipe = maxPipe = 1;
break;
}
pipe.addLast(req);
if (!mCanPersist && state != PRIO) state = READ;
break;
}
case PRIO:
case DRAIN:
case READ: {
empty = !mRequestFeeder.haveRequest(mHost);
int pipeSize = pipe.size();
if (state != PRIO && state != DRAIN && pipeSize < minPipe &&
!empty && mCanPersist) {
state = SEND;
break;
} else if (pipeSize == 0) {
/* Done if no other work to do */
if (state == PRIO)
state = DONE;
else
state = empty ? DONE : SEND;
break;
}
req = (Request)pipe.removeFirst();
if (HttpLog.LOGV) HttpLog.v(
"processRequests() reading " + req);
try {
req.readResponse(mHttpClientConnection);
} catch (ParseException e) {
exception = e;
error = EventHandler.ERROR_IO;
} catch (IOException e) {
exception = e;
error = EventHandler.ERROR_IO;
} catch (IllegalStateException e) {
exception = e;
error = EventHandler.ERROR_IO;
}
if (exception != null) {
if (httpFailure(req, error, exception) &&
!req.mCancelled) {
/* retry request if not permanent failure
or cancelled */
req.reset();
pipe.addFirst(req);
}
exception = null;
mCanPersist = false;
}
if (!mCanPersist) {
if (HttpLog.LOGV) HttpLog.v(
"processRequests(): no persist, closing " +
mHost);
closeConnection();
mHttpContext.removeAttribute(HTTP_CONNECTION);
clearPipe(pipe);
minPipe = maxPipe = 1;
if (state != PRIO)
state = SEND;
}
break;
}
}
}
}
/**
* After a send/receive failure, any pipelined requests must be
* cleared back to the mRequest queue
* @return true if mRequests is empty after pipe cleared
*/
private boolean clearPipe(LinkedList<Request> pipe) {
boolean empty = true;
if (HttpLog.LOGV) HttpLog.v(
"Connection.clearPipe(): clearing pipe " + pipe.size());
synchronized (mRequestFeeder) {
Request tReq;
while (!pipe.isEmpty()) {
tReq = (Request)pipe.removeLast();
if (HttpLog.LOGV) HttpLog.v(
"clearPipe() adding back " + mHost + " " + tReq);
mRequestFeeder.requeueRequest(tReq, true, false);
empty = false;
}
if (empty) empty = !mRequestFeeder.haveRequest(mHost);
}
return empty;
}
/**
* @return true on success
*/
private boolean openHttpConnection(Request req) {
long now = SystemClock.uptimeMillis();
int error = EventHandler.OK;
Exception exception = null;
try {
// reset the certificate to null before opening a connection
mCertificate = null;
mHttpClientConnection = openConnection(req);
if (mHttpClientConnection != null) {
mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
mHttpContext.setAttribute(HTTP_CONNECTION,
mHttpClientConnection);
} else {
// we tried to do SSL tunneling, failed,
// and need to drop the request;
// we have already informed the handler
req.mFailCount = RETRY_REQUEST_LIMIT;
return false;
}
} catch (UnknownHostException e) {
if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
error = EventHandler.ERROR_LOOKUP;
exception = e;
} catch (IllegalArgumentException e) {
if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
error = EventHandler.ERROR_CONNECT;
req.mFailCount = RETRY_REQUEST_LIMIT;
exception = e;
} catch (SSLConnectionClosedByUserException e) {
// hack: if we have an SSL connection failure,
// we don't want to reconnect
req.mFailCount = RETRY_REQUEST_LIMIT;
// no error message
return false;
} catch (SSLHandshakeException e) {
// hack: if we have an SSL connection failure,
// we don't want to reconnect
req.mFailCount = RETRY_REQUEST_LIMIT;
if (HttpLog.LOGV) HttpLog.v(
"SSL exception performing handshake");
error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
exception = e;
} catch (IOException e) {
error = EventHandler.ERROR_CONNECT;
exception = e;
}
if (HttpLog.LOGV) {
long now2 = SystemClock.uptimeMillis();
HttpLog.v("Connection.openHttpConnection() " +
(now2 - now) + " " + mHost);
}
if (error == EventHandler.OK) {
return true;
} else {
if (req.mFailCount < RETRY_REQUEST_LIMIT) {
// requeue
mRequestFeeder.requeueRequest(req, true, false);
req.mFailCount++;
} else {
httpFailure(req, error, exception);
}
return error == EventHandler.OK;
}
}
/**
* Helper. Calls the mEventHandler's error() method only if
* request failed permanently. Increments mFailcount on failure.
*
* Increments failcount only if the network is believed to be
* connected
*
* @return true if request can be retried (less than
* RETRY_REQUEST_LIMIT failures have occurred).
*/
private boolean httpFailure(Request req, int errorId, Exception e) {
boolean ret = true;
// e.printStackTrace();
if (HttpLog.LOGV) HttpLog.v(
"httpFailure() ******* " + e + " count " + req.mFailCount +
" " + mHost + " " + req.getUri());
if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
ret = false;
String error;
if (errorId < 0) {
error = mContext.getText(
EventHandler.errorStringResources[-errorId]).toString();
} else {
Throwable cause = e.getCause();
error = cause != null ? cause.toString() : e.getMessage();
}
req.mEventHandler.error(errorId, error);
req.complete();
}
closeConnection();
mHttpContext.removeAttribute(HTTP_CONNECTION);
return ret;
}
HttpContext getHttpContext() {
return mHttpContext;
}
/**
* Use same logic as ConnectionReuseStrategy
* @see ConnectionReuseStrategy
*/
private boolean keepAlive(HttpEntity entity,
ProtocolVersion ver, int connType, final HttpContext context) {
org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
context.getAttribute(ExecutionContext.HTTP_CONNECTION);
if (conn != null && !conn.isOpen())
return false;
// do NOT check for stale connection, that is an expensive operation
if (entity != null) {
if (entity.getContentLength() < 0) {
if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
// if the content length is not known and is not chunk
// encoded, the connection cannot be reused
return false;
}
}
}
// Check for 'Connection' directive
if (connType == Headers.CONN_CLOSE) {
return false;
} else if (connType == Headers.CONN_KEEP_ALIVE) {
return true;
}
// Resorting to protocol version default close connection policy
return !ver.lessEquals(HttpVersion.HTTP_1_0);
}
void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
}
void setCanPersist(boolean canPersist) {
mCanPersist = canPersist;
}
boolean getCanPersist() {
return mCanPersist;
}
/** typically http or https... set by subclass */
abstract String getScheme();
abstract void closeConnection();
abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
/**
* Prints request queue to log, for debugging.
* returns request count
*/
public synchronized String toString() {
return mHost.toString();
}
byte[] getBuf() {
if (mBuf == null) mBuf = new byte[8192];
return mBuf;
}
public void setTcpPreConnect(boolean isTcpPreConnect)
{
mIsTcpPreConnect = isTcpPreConnect;
return;
}
public boolean getTcpPreConnect()
{
return mIsTcpPreConnect;
}
}
@@ -0,0 +1,174 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.net.http;
import android.content.Context;
import android.os.SystemClock;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.http.HttpHost;
import java.lang.Thread;
/**
* {@hide}
*/
class ConnectionThread extends Thread {
static AtomicInteger sRunning = new AtomicInteger();
static final int WAIT_TIMEOUT = 5000;
static final int WAIT_TICK = 1000;
// Performance probe
long mCurrentThreadTime;
long mTotalThreadTime;
private boolean mWaiting;
private volatile boolean mRunning = true;
private Context mContext;
private RequestQueue.ConnectionManager mConnectionManager;
private RequestFeeder mRequestFeeder;
private volatile HttpHost mCurrentHost;
private volatile Request mNewRequest;
private int mId;
Connection mConnection;
ConnectionThread(Context context,
int id,
RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
super();
mContext = context;
setName("http" + id);
mId = id;
mConnectionManager = connectionManager;
mRequestFeeder = requestFeeder;
mCurrentHost = null;
mNewRequest = null;
}
public void requestStop() {
synchronized (mRequestFeeder) {
mRunning = false;
mRequestFeeder.notify();
}
}
public HttpHost getCurrentHost() {
return mCurrentHost;
}
public void setNewRequest(Request req) {
mNewRequest = req;
}
/**
* Loop until app shutdown. Runs connections in priority
* order.
*/
public void run() {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_DEFAULT +
android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
// these are used to get performance data. When it is not in the timing,
// mCurrentThreadTime is 0. When it starts timing, mCurrentThreadTime is
// first set to -1, it will be set to the current thread time when the
// next request starts.
mCurrentThreadTime = 0;
mTotalThreadTime = 0;
sRunning.incrementAndGet();
while (mRunning) {
if (mCurrentThreadTime == -1) {
mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
}
Request request;
/* Get a request to process */
if (mNewRequest != null) {
request = mNewRequest;
mNewRequest = null;
} else
request = mRequestFeeder.getRequest();
/* wait for work */
if (request == null) {
synchronized(mRequestFeeder) {
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: Waiting for work");
mWaiting = true;
sRunning.decrementAndGet();
RequestQueue rq = (RequestQueue)mRequestFeeder;
rq.checkPageFinished();
try {
mRequestFeeder.wait();
} catch (InterruptedException e) {
}
mWaiting = false;
sRunning.incrementAndGet();
if (mCurrentThreadTime != 0) {
mCurrentThreadTime = SystemClock
.currentThreadTimeMillis();
}
// Make sure the connection does not start to drain before the first request has been processed
}
} else {
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
request.mHost + " " + request );
// ### this should possibly have some kind of lock to prevent the requestqueue from seeing the host as busy when it's not
mCurrentHost = request.mHost;
synchronized (this) {
mConnection = mConnectionManager.getConnection(mContext, request.mHost);
mConnection.setConnectionThread(this);
}
mConnection.processRequests(request);
if (mConnection.getCanPersist()) {
if (!mConnectionManager.recycleConnection(mConnection)) {
mConnection.closeConnection();
}
} else {
mConnection.closeConnection();
}
synchronized (this) {
mConnection.setConnectionThread(null);
mConnection = null;
}
mCurrentHost = null;
if (mCurrentThreadTime > 0) {
long start = mCurrentThreadTime;
mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
mTotalThreadTime += mCurrentThreadTime - start;
}
}
}
sRunning.decrementAndGet();
}
public synchronized String toString() {
String con = mConnection == null ? "" : mConnection.toString();
String active = mWaiting ? "w" : "a";
return "cid " + mId + " " + active + " " + con;
}
}
@@ -0,0 +1,150 @@
/*
* 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.net.http;
/**
* Callbacks in this interface are made as an HTTP request is
* processed. The normal order of callbacks is status(), headers(),
* then multiple data() then endData(). handleSslErrorRequest(), if
* there is an SSL certificate error. error() can occur anywhere
* in the transaction.
*
* {@hide}
*/
public interface EventHandler {
/**
* Error codes used in the error() callback. Positive error codes
* are reserved for codes sent by http servers. Negative error
* codes are connection/parsing failures, etc.
*/
/** Success */
public static final int OK = 0;
/** Generic error */
public static final int ERROR = -1;
/** Server or proxy hostname lookup failed */
public static final int ERROR_LOOKUP = -2;
/** Unsupported authentication scheme (ie, not basic or digest) */
public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
/** User authentication failed on server */
public static final int ERROR_AUTH = -4;
/** User authentication failed on proxy */
public static final int ERROR_PROXYAUTH = -5;
/** Could not connect to server */
public static final int ERROR_CONNECT = -6;
/** Failed to write to or read from server */
public static final int ERROR_IO = -7;
/** Connection timed out */
public static final int ERROR_TIMEOUT = -8;
/** Too many redirects */
public static final int ERROR_REDIRECT_LOOP = -9;
/** Unsupported URI scheme (ie, not http, https, etc) */
public static final int ERROR_UNSUPPORTED_SCHEME = -10;
/** Failed to perform SSL handshake */
public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
/** Bad URL */
public static final int ERROR_BAD_URL = -12;
/** Generic file error for file:/// loads */
public static final int FILE_ERROR = -13;
/** File not found error for file:/// loads */
public static final int FILE_NOT_FOUND_ERROR = -14;
/** Too many requests queued */
public static final int TOO_MANY_REQUESTS_ERROR = -15;
final static int[] errorStringResources = {
com.android.internal.R.string.httpErrorOk,
com.android.internal.R.string.httpError,
com.android.internal.R.string.httpErrorLookup,
com.android.internal.R.string.httpErrorUnsupportedAuthScheme,
com.android.internal.R.string.httpErrorAuth,
com.android.internal.R.string.httpErrorProxyAuth,
com.android.internal.R.string.httpErrorConnect,
com.android.internal.R.string.httpErrorIO,
com.android.internal.R.string.httpErrorTimeout,
com.android.internal.R.string.httpErrorRedirectLoop,
com.android.internal.R.string.httpErrorUnsupportedScheme,
com.android.internal.R.string.httpErrorFailedSslHandshake,
com.android.internal.R.string.httpErrorBadUrl,
com.android.internal.R.string.httpErrorFile,
com.android.internal.R.string.httpErrorFileNotFound,
com.android.internal.R.string.httpErrorTooManyRequests
};
/**
* Called after status line has been sucessfully processed.
* @param major_version HTTP version advertised by server. major
* is the part before the "."
* @param minor_version HTTP version advertised by server. minor
* is the part after the "."
* @param code HTTP Status code. See RFC 2616.
* @param reason_phrase Textual explanation sent by server
*/
public void status(int major_version,
int minor_version,
int code,
String reason_phrase);
/**
* Called after all headers are successfully processed.
*/
public void headers(Headers headers);
/**
* An array containing all or part of the http body as read from
* the server.
* @param data A byte array containing the content
* @param len The length of valid content in data
*
* Note: chunked and compressed encodings are handled within
* android.net.http. Decoded data is passed through this
* interface.
*/
public void data(byte[] data, int len);
/**
* Called when the document is completely read. No more data()
* callbacks will be made after this call
*/
public void endData();
/**
* SSL certificate callback called before resource request is
* made, which will be null for insecure connection.
*/
public void certificate(SslCertificate certificate);
/**
* There was trouble.
* @param id One of the error codes defined below
* @param description of error
*/
public void error(int id, String description);
/**
* SSL certificate error callback. Handles SSL error(s) on the way
* up to the user. The callback has to make sure that restartConnection() is called,
* otherwise the connection will be suspended indefinitely.
* @return True if the callback can handle the error, which means it will
* call restartConnection() to unblock the thread later,
* otherwise return false.
*/
public boolean handleSslErrorRequest(SslError error);
}
@@ -0,0 +1,464 @@
/*
* 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.net.http;
import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
import org.apache.http.HeaderElement;
import org.apache.http.entity.ContentLengthStrategy;
import org.apache.http.message.BasicHeaderValueParser;
import org.apache.http.message.ParserCursor;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharArrayBuffer;
/**
* Manages received headers
*
* {@hide}
*/
public final class Headers {
private static final String LOGTAG = "Http";
// header parsing constant
/**
* indicate HTTP 1.0 connection close after the response
*/
public final static int CONN_CLOSE = 1;
/**
* indicate HTTP 1.1 connection keep alive
*/
public final static int CONN_KEEP_ALIVE = 2;
// initial values.
public final static int NO_CONN_TYPE = 0;
public final static long NO_TRANSFER_ENCODING = 0;
public final static long NO_CONTENT_LENGTH = -1;
// header strings
public final static String TRANSFER_ENCODING = "transfer-encoding";
public final static String CONTENT_LEN = "content-length";
public final static String CONTENT_TYPE = "content-type";
public final static String CONTENT_ENCODING = "content-encoding";
public final static String CONN_DIRECTIVE = "connection";
public final static String LOCATION = "location";
public final static String PROXY_CONNECTION = "proxy-connection";
public final static String WWW_AUTHENTICATE = "www-authenticate";
public final static String PROXY_AUTHENTICATE = "proxy-authenticate";
public final static String CONTENT_DISPOSITION = "content-disposition";
public final static String ACCEPT_RANGES = "accept-ranges";
public final static String EXPIRES = "expires";
public final static String CACHE_CONTROL = "cache-control";
public final static String LAST_MODIFIED = "last-modified";
public final static String ETAG = "etag";
public final static String SET_COOKIE = "set-cookie";
public final static String PRAGMA = "pragma";
public final static String REFRESH = "refresh";
public final static String X_PERMITTED_CROSS_DOMAIN_POLICIES = "x-permitted-cross-domain-policies";
// following hash are generated by String.hashCode()
private final static int HASH_TRANSFER_ENCODING = 1274458357;
private final static int HASH_CONTENT_LEN = -1132779846;
private final static int HASH_CONTENT_TYPE = 785670158;
private final static int HASH_CONTENT_ENCODING = 2095084583;
private final static int HASH_CONN_DIRECTIVE = -775651618;
private final static int HASH_LOCATION = 1901043637;
private final static int HASH_PROXY_CONNECTION = 285929373;
private final static int HASH_WWW_AUTHENTICATE = -243037365;
private final static int HASH_PROXY_AUTHENTICATE = -301767724;
private final static int HASH_CONTENT_DISPOSITION = -1267267485;
private final static int HASH_ACCEPT_RANGES = 1397189435;
private final static int HASH_EXPIRES = -1309235404;
private final static int HASH_CACHE_CONTROL = -208775662;
private final static int HASH_LAST_MODIFIED = 150043680;
private final static int HASH_ETAG = 3123477;
private final static int HASH_SET_COOKIE = 1237214767;
private final static int HASH_PRAGMA = -980228804;
private final static int HASH_REFRESH = 1085444827;
private final static int HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES = -1345594014;
// keep any headers that require direct access in a presized
// string array
private final static int IDX_TRANSFER_ENCODING = 0;
private final static int IDX_CONTENT_LEN = 1;
private final static int IDX_CONTENT_TYPE = 2;
private final static int IDX_CONTENT_ENCODING = 3;
private final static int IDX_CONN_DIRECTIVE = 4;
private final static int IDX_LOCATION = 5;
private final static int IDX_PROXY_CONNECTION = 6;
private final static int IDX_WWW_AUTHENTICATE = 7;
private final static int IDX_PROXY_AUTHENTICATE = 8;
private final static int IDX_CONTENT_DISPOSITION = 9;
private final static int IDX_ACCEPT_RANGES = 10;
private final static int IDX_EXPIRES = 11;
private final static int IDX_CACHE_CONTROL = 12;
private final static int IDX_LAST_MODIFIED = 13;
private final static int IDX_ETAG = 14;
private final static int IDX_SET_COOKIE = 15;
private final static int IDX_PRAGMA = 16;
private final static int IDX_REFRESH = 17;
private final static int IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES = 18;
private final static int HEADER_COUNT = 19;
/* parsed values */
private long transferEncoding;
private long contentLength; // Content length of the incoming data
private int connectionType;
private ArrayList<String> cookies = new ArrayList<String>(2);
private String[] mHeaders = new String[HEADER_COUNT];
private final static String[] sHeaderNames = {
TRANSFER_ENCODING,
CONTENT_LEN,
CONTENT_TYPE,
CONTENT_ENCODING,
CONN_DIRECTIVE,
LOCATION,
PROXY_CONNECTION,
WWW_AUTHENTICATE,
PROXY_AUTHENTICATE,
CONTENT_DISPOSITION,
ACCEPT_RANGES,
EXPIRES,
CACHE_CONTROL,
LAST_MODIFIED,
ETAG,
SET_COOKIE,
PRAGMA,
REFRESH,
X_PERMITTED_CROSS_DOMAIN_POLICIES
};
// Catch-all for headers not explicitly handled
private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
public Headers() {
transferEncoding = NO_TRANSFER_ENCODING;
contentLength = NO_CONTENT_LENGTH;
connectionType = NO_CONN_TYPE;
}
public void parseHeader(CharArrayBuffer buffer) {
int pos = CharArrayBuffers.setLowercaseIndexOf(buffer, ':');
if (pos == -1) {
return;
}
String name = buffer.substringTrimmed(0, pos);
if (name.length() == 0) {
return;
}
pos++;
String val = buffer.substringTrimmed(pos, buffer.length());
if (HttpLog.LOGV) {
HttpLog.v("hdr " + buffer.length() + " " + buffer);
}
switch (name.hashCode()) {
case HASH_TRANSFER_ENCODING:
if (name.equals(TRANSFER_ENCODING)) {
mHeaders[IDX_TRANSFER_ENCODING] = val;
HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
.parseElements(buffer, new ParserCursor(pos,
buffer.length()));
// The chunked encoding must be the last one applied RFC2616,
// 14.41
int len = encodings.length;
if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
transferEncoding = ContentLengthStrategy.IDENTITY;
} else if ((len > 0)
&& (HTTP.CHUNK_CODING
.equalsIgnoreCase(encodings[len - 1].getName()))) {
transferEncoding = ContentLengthStrategy.CHUNKED;
} else {
transferEncoding = ContentLengthStrategy.IDENTITY;
}
}
break;
case HASH_CONTENT_LEN:
if (name.equals(CONTENT_LEN)) {
mHeaders[IDX_CONTENT_LEN] = val;
try {
contentLength = Long.parseLong(val);
} catch (NumberFormatException e) {
if (Config.LOGV) {
Log.v(LOGTAG, "Headers.headers(): error parsing"
+ " content length: " + buffer.toString());
}
}
}
break;
case HASH_CONTENT_TYPE:
if (name.equals(CONTENT_TYPE)) {
mHeaders[IDX_CONTENT_TYPE] = val;
}
break;
case HASH_CONTENT_ENCODING:
if (name.equals(CONTENT_ENCODING)) {
mHeaders[IDX_CONTENT_ENCODING] = val;
}
break;
case HASH_CONN_DIRECTIVE:
if (name.equals(CONN_DIRECTIVE)) {
mHeaders[IDX_CONN_DIRECTIVE] = val;
setConnectionType(buffer, pos);
}
break;
case HASH_LOCATION:
if (name.equals(LOCATION)) {
mHeaders[IDX_LOCATION] = val;
}
break;
case HASH_PROXY_CONNECTION:
if (name.equals(PROXY_CONNECTION)) {
mHeaders[IDX_PROXY_CONNECTION] = val;
setConnectionType(buffer, pos);
}
break;
case HASH_WWW_AUTHENTICATE:
if (name.equals(WWW_AUTHENTICATE)) {
mHeaders[IDX_WWW_AUTHENTICATE] = val;
}
break;
case HASH_PROXY_AUTHENTICATE:
if (name.equals(PROXY_AUTHENTICATE)) {
mHeaders[IDX_PROXY_AUTHENTICATE] = val;
}
break;
case HASH_CONTENT_DISPOSITION:
if (name.equals(CONTENT_DISPOSITION)) {
mHeaders[IDX_CONTENT_DISPOSITION] = val;
}
break;
case HASH_ACCEPT_RANGES:
if (name.equals(ACCEPT_RANGES)) {
mHeaders[IDX_ACCEPT_RANGES] = val;
}
break;
case HASH_EXPIRES:
if (name.equals(EXPIRES)) {
mHeaders[IDX_EXPIRES] = val;
}
break;
case HASH_CACHE_CONTROL:
if (name.equals(CACHE_CONTROL)) {
mHeaders[IDX_CACHE_CONTROL] = val;
}
break;
case HASH_LAST_MODIFIED:
if (name.equals(LAST_MODIFIED)) {
mHeaders[IDX_LAST_MODIFIED] = val;
}
break;
case HASH_ETAG:
if (name.equals(ETAG)) {
mHeaders[IDX_ETAG] = val;
}
break;
case HASH_SET_COOKIE:
if (name.equals(SET_COOKIE)) {
mHeaders[IDX_SET_COOKIE] = val;
cookies.add(val);
}
break;
case HASH_PRAGMA:
if (name.equals(PRAGMA)) {
mHeaders[IDX_PRAGMA] = val;
}
break;
case HASH_REFRESH:
if (name.equals(REFRESH)) {
mHeaders[IDX_REFRESH] = val;
}
break;
case HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES:
if (name.equals(X_PERMITTED_CROSS_DOMAIN_POLICIES)) {
mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = val;
}
break;
default:
mExtraHeaderNames.add(name);
mExtraHeaderValues.add(val);
}
}
public long getTransferEncoding() {
return transferEncoding;
}
public long getContentLength() {
return contentLength;
}
public int getConnectionType() {
return connectionType;
}
public String getContentType() {
return mHeaders[IDX_CONTENT_TYPE];
}
public String getContentEncoding() {
return mHeaders[IDX_CONTENT_ENCODING];
}
public String getLocation() {
return mHeaders[IDX_LOCATION];
}
public String getWwwAuthenticate() {
return mHeaders[IDX_WWW_AUTHENTICATE];
}
public String getProxyAuthenticate() {
return mHeaders[IDX_PROXY_AUTHENTICATE];
}
public String getContentDisposition() {
return mHeaders[IDX_CONTENT_DISPOSITION];
}
public String getAcceptRanges() {
return mHeaders[IDX_ACCEPT_RANGES];
}
public String getExpires() {
return mHeaders[IDX_EXPIRES];
}
public String getCacheControl() {
return mHeaders[IDX_CACHE_CONTROL];
}
public String getLastModified() {
return mHeaders[IDX_LAST_MODIFIED];
}
public String getEtag() {
return mHeaders[IDX_ETAG];
}
public ArrayList<String> getSetCookie() {
return this.cookies;
}
public String getPragma() {
return mHeaders[IDX_PRAGMA];
}
public String getRefresh() {
return mHeaders[IDX_REFRESH];
}
public String getXPermittedCrossDomainPolicies() {
return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
}
public void setContentLength(long value) {
this.contentLength = value;
}
public void setContentType(String value) {
mHeaders[IDX_CONTENT_TYPE] = value;
}
public void setContentEncoding(String value) {
mHeaders[IDX_CONTENT_ENCODING] = value;
}
public void setLocation(String value) {
mHeaders[IDX_LOCATION] = value;
}
public void setWwwAuthenticate(String value) {
mHeaders[IDX_WWW_AUTHENTICATE] = value;
}
public void setProxyAuthenticate(String value) {
mHeaders[IDX_PROXY_AUTHENTICATE] = value;
}
public void setContentDisposition(String value) {
mHeaders[IDX_CONTENT_DISPOSITION] = value;
}
public void setAcceptRanges(String value) {
mHeaders[IDX_ACCEPT_RANGES] = value;
}
public void setExpires(String value) {
mHeaders[IDX_EXPIRES] = value;
}
public void setCacheControl(String value) {
mHeaders[IDX_CACHE_CONTROL] = value;
}
public void setLastModified(String value) {
mHeaders[IDX_LAST_MODIFIED] = value;
}
public void setEtag(String value) {
mHeaders[IDX_ETAG] = value;
}
public void setXPermittedCrossDomainPolicies(String value) {
mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
}
public interface HeaderCallback {
public void header(String name, String value);
}
/**
* Reports all non-null headers to the callback
*/
public void getHeaders(HeaderCallback hcb) {
for (int i = 0; i < HEADER_COUNT; i++) {
String h = mHeaders[i];
if (h != null) {
hcb.header(sHeaderNames[i], h);
}
}
int extraLen = mExtraHeaderNames.size();
for (int i = 0; i < extraLen; i++) {
if (Config.LOGV) {
HttpLog.v("Headers.getHeaders() extra: " + i + " " +
mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
}
hcb.header(mExtraHeaderNames.get(i),
mExtraHeaderValues.get(i));
}
}
private void setConnectionType(CharArrayBuffer buffer, int pos) {
if (CharArrayBuffers.containsIgnoreCaseTrimmed(
buffer, pos, HTTP.CONN_CLOSE)) {
connectionType = CONN_CLOSE;
} else if (CharArrayBuffers.containsIgnoreCaseTrimmed(
buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
connectionType = CONN_KEEP_ALIVE;
}
}
}
@@ -0,0 +1,422 @@
/*
* Copyright (C) 2007 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.net.http;
/**
* HttpAuthHeader: a class to store HTTP authentication-header parameters.
* For more information, see: RFC 2617: HTTP Authentication.
*
* {@hide}
*/
public class HttpAuthHeader {
/**
* Possible HTTP-authentication header tokens to search for:
*/
public final static String BASIC_TOKEN = "Basic";
public final static String DIGEST_TOKEN = "Digest";
private final static String REALM_TOKEN = "realm";
private final static String NONCE_TOKEN = "nonce";
private final static String STALE_TOKEN = "stale";
private final static String OPAQUE_TOKEN = "opaque";
private final static String QOP_TOKEN = "qop";
private final static String ALGORITHM_TOKEN = "algorithm";
/**
* An authentication scheme. We currently support two different schemes:
* HttpAuthHeader.BASIC - basic, and
* HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
*/
private int mScheme;
public static final int UNKNOWN = 0;
public static final int BASIC = 1;
public static final int DIGEST = 2;
/**
* A flag, indicating that the previous request from the client was
* rejected because the nonce value was stale. If stale is TRUE
* (case-insensitive), the client may wish to simply retry the request
* with a new encrypted response, without reprompting the user for a
* new username and password.
*/
private boolean mStale;
/**
* A string to be displayed to users so they know which username and
* password to use.
*/
private String mRealm;
/**
* A server-specified data string which should be uniquely generated
* each time a 401 response is made.
*/
private String mNonce;
/**
* A string of data, specified by the server, which should be returned
* by the client unchanged in the Authorization header of subsequent
* requests with URIs in the same protection space.
*/
private String mOpaque;
/**
* This directive is optional, but is made so only for backward
* compatibility with RFC 2069 [6]; it SHOULD be used by all
* implementations compliant with this version of the Digest scheme.
* If present, it is a quoted string of one or more tokens indicating
* the "quality of protection" values supported by the server. The
* value "auth" indicates authentication; the value "auth-int"
* indicates authentication with integrity protection.
*/
private String mQop;
/**
* A string indicating a pair of algorithms used to produce the digest
* and a checksum. If this is not present it is assumed to be "MD5".
*/
private String mAlgorithm;
/**
* Is this authentication request a proxy authentication request?
*/
private boolean mIsProxy;
/**
* Username string we get from the user.
*/
private String mUsername;
/**
* Password string we get from the user.
*/
private String mPassword;
/**
* Creates a new HTTP-authentication header object from the
* input header string.
* The header string is assumed to contain parameters of at
* most one authentication-scheme (ensured by the caller).
*/
public HttpAuthHeader(String header) {
if (header != null) {
parseHeader(header);
}
}
/**
* @return True iff this is a proxy authentication header.
*/
public boolean isProxy() {
return mIsProxy;
}
/**
* Marks this header as a proxy authentication header.
*/
public void setProxy() {
mIsProxy = true;
}
/**
* @return The username string.
*/
public String getUsername() {
return mUsername;
}
/**
* Sets the username string.
*/
public void setUsername(String username) {
mUsername = username;
}
/**
* @return The password string.
*/
public String getPassword() {
return mPassword;
}
/**
* Sets the password string.
*/
public void setPassword(String password) {
mPassword = password;
}
/**
* @return True iff this is the BASIC-authentication request.
*/
public boolean isBasic () {
return mScheme == BASIC;
}
/**
* @return True iff this is the DIGEST-authentication request.
*/
public boolean isDigest() {
return mScheme == DIGEST;
}
/**
* @return The authentication scheme requested. We currently
* support two schemes:
* HttpAuthHeader.BASIC - basic, and
* HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
*/
public int getScheme() {
return mScheme;
}
/**
* @return True if indicating that the previous request from
* the client was rejected because the nonce value was stale.
*/
public boolean getStale() {
return mStale;
}
/**
* @return The realm value or null if there is none.
*/
public String getRealm() {
return mRealm;
}
/**
* @return The nonce value or null if there is none.
*/
public String getNonce() {
return mNonce;
}
/**
* @return The opaque value or null if there is none.
*/
public String getOpaque() {
return mOpaque;
}
/**
* @return The QOP ("quality-of_protection") value or null if
* there is none. The QOP value is always lower-case.
*/
public String getQop() {
return mQop;
}
/**
* @return The name of the algorithm used or null if there is
* none. By default, MD5 is used.
*/
public String getAlgorithm() {
return mAlgorithm;
}
/**
* @return True iff the authentication scheme requested by the
* server is supported; currently supported schemes:
* BASIC,
* DIGEST (only algorithm="md5", no qop or qop="auth).
*/
public boolean isSupportedScheme() {
// it is a good idea to enforce non-null realms!
if (mRealm != null) {
if (mScheme == BASIC) {
return true;
} else {
if (mScheme == DIGEST) {
return
mAlgorithm.equals("md5") &&
(mQop == null || mQop.equals("auth"));
}
}
}
return false;
}
/**
* Parses the header scheme name and then scheme parameters if
* the scheme is supported.
*/
private void parseHeader(String header) {
if (HttpLog.LOGV) {
HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header);
}
if (header != null) {
String parameters = parseScheme(header);
if (parameters != null) {
// if we have a supported scheme
if (mScheme != UNKNOWN) {
parseParameters(parameters);
}
}
}
}
/**
* Parses the authentication scheme name. If we have a Digest
* scheme, sets the algorithm value to the default of MD5.
* @return The authentication scheme parameters string to be
* parsed later (if the scheme is supported) or null if failed
* to parse the scheme (the header value is null?).
*/
private String parseScheme(String header) {
if (header != null) {
int i = header.indexOf(' ');
if (i >= 0) {
String scheme = header.substring(0, i).trim();
if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) {
mScheme = DIGEST;
// md5 is the default algorithm!!!
mAlgorithm = "md5";
} else {
if (scheme.equalsIgnoreCase(BASIC_TOKEN)) {
mScheme = BASIC;
}
}
return header.substring(i + 1);
}
}
return null;
}
/**
* Parses a comma-separated list of authentification scheme
* parameters.
*/
private void parseParameters(String parameters) {
if (HttpLog.LOGV) {
HttpLog.v("HttpAuthHeader.parseParameters():" +
" parameters: " + parameters);
}
if (parameters != null) {
int i;
do {
i = parameters.indexOf(',');
if (i < 0) {
// have only one parameter
parseParameter(parameters);
} else {
parseParameter(parameters.substring(0, i));
parameters = parameters.substring(i + 1);
}
} while (i >= 0);
}
}
/**
* Parses a single authentication scheme parameter. The parameter
* string is expected to follow the format: PARAMETER=VALUE.
*/
private void parseParameter(String parameter) {
if (parameter != null) {
// here, we are looking for the 1st occurence of '=' only!!!
int i = parameter.indexOf('=');
if (i >= 0) {
String token = parameter.substring(0, i).trim();
String value =
trimDoubleQuotesIfAny(parameter.substring(i + 1).trim());
if (HttpLog.LOGV) {
HttpLog.v("HttpAuthHeader.parseParameter():" +
" token: " + token +
" value: " + value);
}
if (token.equalsIgnoreCase(REALM_TOKEN)) {
mRealm = value;
} else {
if (mScheme == DIGEST) {
parseParameter(token, value);
}
}
}
}
}
/**
* If the token is a known parameter name, parses and initializes
* the token value.
*/
private void parseParameter(String token, String value) {
if (token != null && value != null) {
if (token.equalsIgnoreCase(NONCE_TOKEN)) {
mNonce = value;
return;
}
if (token.equalsIgnoreCase(STALE_TOKEN)) {
parseStale(value);
return;
}
if (token.equalsIgnoreCase(OPAQUE_TOKEN)) {
mOpaque = value;
return;
}
if (token.equalsIgnoreCase(QOP_TOKEN)) {
mQop = value.toLowerCase();
return;
}
if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
mAlgorithm = value.toLowerCase();
return;
}
}
}
/**
* Parses and initializes the 'stale' paramer value. Any value
* different from case-insensitive "true" is considered "false".
*/
private void parseStale(String value) {
if (value != null) {
if (value.equalsIgnoreCase("true")) {
mStale = true;
}
}
}
/**
* Trims double-quotes around a parameter value if there are any.
* @return The string value without the outermost pair of double-
* quotes or null if the original value is null.
*/
static private String trimDoubleQuotesIfAny(String value) {
if (value != null) {
int len = value.length();
if (len > 2 &&
value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
return value.substring(1, len - 1);
}
}
return value;
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2007 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.net.http;
import android.content.Context;
import java.net.Socket;
import java.io.IOException;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpHost;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
/**
* A requestConnection connecting to a normal (non secure) http server
*
* {@hide}
*/
class HttpConnection extends Connection {
HttpConnection(Context context, HttpHost host,
RequestFeeder requestFeeder) {
super(context, host, requestFeeder);
}
/**
* Opens the connection to a http server
*
* @return the opened low level connection
* @throws IOException if the connection fails for any reason.
*/
@Override
AndroidHttpClientConnection openConnection(Request req) throws IOException {
// Update the certificate info (connection not secure - set to null)
EventHandler eventHandler = req.getEventHandler();
mCertificate = null;
eventHandler.certificate(mCertificate);
AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
BasicHttpParams params = new BasicHttpParams();
Socket sock = new Socket(mHost.getHostName(), mHost.getPort());
params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
conn.bind(sock, params);
return conn;
}
/**
* Closes the low level connection.
*
* If an exception is thrown then it is assumed that the
* connection will have been closed (to the extent possible)
* anyway and the caller does not need to take any further action.
*
*/
void closeConnection() {
try {
if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
mHttpClientConnection.close();
}
} catch (IOException e) {
if (HttpLog.LOGV) HttpLog.v(
"closeConnection(): failed closing connection " +
mHost);
e.printStackTrace();
}
}
/**
* Restart a secure connection suspended waiting for user interaction.
*/
void restartConnection(boolean abort) {
// not required for plain http connections
}
String getScheme() {
return "http";
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2007 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-level logging flag
*/
package android.net.http;
import android.os.SystemClock;
import android.util.Log;
import android.util.Config;
/**
* {@hide}
*/
class HttpLog {
private final static String LOGTAG = "http";
private static final boolean DEBUG = false;
static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV;
static void v(String logMe) {
Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
}
static void e(String logMe) {
Log.e(LOGTAG, logMe);
}
}
@@ -0,0 +1,432 @@
/*
* Copyright (C) 2007 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.net.http;
import android.content.Context;
import android.util.Log;
import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
/**
* A Connection connecting to a secure http server or tunneling through
* a http proxy server to a https server.
*
* @hide
*/
public class HttpsConnection extends Connection {
/**
* SSL socket factory
*/
private static SSLSocketFactory mSslSocketFactory = null;
static {
// This initialization happens in the zygote. It triggers some
// lazy initialization that can will benefit later invocations of
// initializeEngine().
initializeEngine(null);
}
/**
* @hide
*
* @param sessionDir directory to cache SSL sessions
*/
public static void initializeEngine(File sessionDir) {
try {
SSLClientSessionCache cache = null;
if (sessionDir != null) {
Log.d("HttpsConnection", "Caching SSL sessions in "
+ sessionDir + ".");
cache = FileClientSessionCache.usingDirectory(sessionDir);
}
OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
// here, trust managers is a single trust-all manager
TrustManager[] trustManagers = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
X509Certificate[] certs, String authType) {
}
}
};
sslContext.engineInit(null, trustManagers, null);
sslContext.engineGetClientSessionContext().setPersistentCache(cache);
synchronized (HttpsConnection.class) {
mSslSocketFactory = sslContext.engineGetSocketFactory();
}
} catch (KeyManagementException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private synchronized static SSLSocketFactory getSocketFactory() {
return mSslSocketFactory;
}
/**
* Object to wait on when suspending the SSL connection
*/
private Object mSuspendLock = new Object();
/**
* True if the connection is suspended pending the result of asking the
* user about an error.
*/
private boolean mSuspended = false;
/**
* True if the connection attempt should be aborted due to an ssl
* error.
*/
private boolean mAborted = false;
// Used when connecting through a proxy.
private HttpHost mProxyHost;
/**
* Contructor for a https connection.
*/
HttpsConnection(Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
super(context, host, requestFeeder);
mProxyHost = proxy;
}
/**
* Sets the server SSL certificate associated with this
* connection.
* @param certificate The SSL certificate
*/
/* package */ void setCertificate(SslCertificate certificate) {
mCertificate = certificate;
}
/**
* Opens the connection to a http server or proxy.
*
* @return the opened low level connection
* @throws IOException if the connection fails for any reason.
*/
@Override
AndroidHttpClientConnection openConnection(Request req) throws IOException {
SSLSocket sslSock = null;
if (mProxyHost != null) {
// If we have a proxy set, we first send a CONNECT request
// to the proxy; if the proxy returns 200 OK, we negotiate
// a secure connection to the target server via the proxy.
// If the request fails, we drop it, but provide the event
// handler with the response status and headers. The event
// handler is then responsible for cancelling the load or
// issueing a new request.
AndroidHttpClientConnection proxyConnection = null;
Socket proxySock = null;
try {
proxySock = new Socket
(mProxyHost.getHostName(), mProxyHost.getPort());
proxySock.setSoTimeout(60 * 1000);
proxyConnection = new AndroidHttpClientConnection();
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setSocketBufferSize(params, 8192);
proxyConnection.bind(proxySock, params);
} catch(IOException e) {
if (proxyConnection != null) {
proxyConnection.close();
}
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage =
"failed to establish a connection to the proxy";
}
throw new IOException(errorMessage);
}
StatusLine statusLine = null;
int statusCode = 0;
Headers headers = new Headers();
try {
BasicHttpRequest proxyReq = new BasicHttpRequest
("CONNECT", mHost.toHostString());
// add all 'proxy' headers from the original request
for (Header h : req.mHttpRequest.getAllHeaders()) {
String headerName = h.getName().toLowerCase();
if (headerName.startsWith("proxy") || headerName.equals("keep-alive")) {
proxyReq.addHeader(h);
}
}
proxyConnection.sendRequestHeader(proxyReq);
proxyConnection.flush();
// it is possible to receive informational status
// codes prior to receiving actual headers;
// all those status codes are smaller than OK 200
// a loop is a standard way of dealing with them
do {
statusLine = proxyConnection.parseResponseHeader(headers);
statusCode = statusLine.getStatusCode();
} while (statusCode < HttpStatus.SC_OK);
} catch (ParseException e) {
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage =
"failed to send a CONNECT request";
}
throw new IOException(errorMessage);
} catch (HttpException e) {
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage =
"failed to send a CONNECT request";
}
throw new IOException(errorMessage);
} catch (IOException e) {
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage =
"failed to send a CONNECT request";
}
throw new IOException(errorMessage);
}
if (statusCode == HttpStatus.SC_OK) {
try {
sslSock = (SSLSocket) getSocketFactory().createSocket(
proxySock, mHost.getHostName(), mHost.getPort(), true);
} catch(IOException e) {
if (sslSock != null) {
sslSock.close();
}
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage =
"failed to create an SSL socket";
}
throw new IOException(errorMessage);
}
} else {
// if the code is not OK, inform the event handler
ProtocolVersion version = statusLine.getProtocolVersion();
req.mEventHandler.status(version.getMajor(),
version.getMinor(),
statusCode,
statusLine.getReasonPhrase());
req.mEventHandler.headers(headers);
req.mEventHandler.endData();
proxyConnection.close();
// here, we return null to indicate that the original
// request needs to be dropped
return null;
}
} else {
// if we do not have a proxy, we simply connect to the host
try {
sslSock = (SSLSocket) getSocketFactory().createSocket();
sslSock.setSoTimeout(SOCKET_TIMEOUT);
sslSock.connect(new InetSocketAddress(mHost.getHostName(),
mHost.getPort()));
} catch(IOException e) {
if (sslSock != null) {
sslSock.close();
}
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage = "failed to create an SSL socket";
}
throw new IOException(errorMessage);
}
}
// do handshake and validate server certificates
SslError error = CertificateChainValidator.getInstance().
doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
// Inform the user if there is a problem
if (error != null) {
// handleSslErrorRequest may immediately unsuspend if it wants to
// allow the certificate anyway.
// So we mark the connection as suspended, call handleSslErrorRequest
// then check if we're still suspended and only wait if we actually
// need to.
synchronized (mSuspendLock) {
mSuspended = true;
}
// don't hold the lock while calling out to the event handler
boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
if(!canHandle) {
throw new IOException("failed to handle "+ error);
}
synchronized (mSuspendLock) {
if (mSuspended) {
try {
// Put a limit on how long we are waiting; if the timeout
// expires (which should never happen unless you choose
// to ignore the SSL error dialog for a very long time),
// we wake up the thread and abort the request. This is
// to prevent us from stalling the network if things go
// very bad.
mSuspendLock.wait(10 * 60 * 1000);
if (mSuspended) {
// mSuspended is true if we have not had a chance to
// restart the connection yet (ie, the wait timeout
// has expired)
mSuspended = false;
mAborted = true;
if (HttpLog.LOGV) {
HttpLog.v("HttpsConnection.openConnection():" +
" SSL timeout expired and request was cancelled!!!");
}
}
} catch (InterruptedException e) {
// ignore
}
}
if (mAborted) {
// The user decided not to use this unverified connection
// so close it immediately.
sslSock.close();
throw new SSLConnectionClosedByUserException("connection closed by the user");
}
}
}
// All went well, we have an open, verified connection.
AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
BasicHttpParams params = new BasicHttpParams();
params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
conn.bind(sslSock, params);
return conn;
}
/**
* Closes the low level connection.
*
* If an exception is thrown then it is assumed that the connection will
* have been closed (to the extent possible) anyway and the caller does not
* need to take any further action.
*
*/
@Override
void closeConnection() {
// if the connection has been suspended due to an SSL error
if (mSuspended) {
// wake up the network thread
restartConnection(false);
}
try {
if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
mHttpClientConnection.close();
}
} catch (IOException e) {
if (HttpLog.LOGV)
HttpLog.v("HttpsConnection.closeConnection():" +
" failed closing connection " + mHost);
e.printStackTrace();
}
}
/**
* Restart a secure connection suspended waiting for user interaction.
*/
void restartConnection(boolean proceed) {
if (HttpLog.LOGV) {
HttpLog.v("HttpsConnection.restartConnection():" +
" proceed: " + proceed);
}
synchronized (mSuspendLock) {
if (mSuspended) {
mSuspended = false;
mAborted = !proceed;
mSuspendLock.notify();
}
}
}
@Override
String getScheme() {
return "https";
}
}
/**
* Simple exception we throw if the SSL connection is closed by the user.
*
* {@hide}
*/
class SSLConnectionClosedByUserException extends SSLException {
public SSLConnectionClosedByUserException(String reason) {
super(reason);
}
}
@@ -0,0 +1,219 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.
*/
/**
* Hangs onto idle live connections for a little while
*/
package android.net.http;
import org.apache.http.HttpHost;
import android.net.http.RequestQueue;
import android.os.SystemClock;
import android.os.SystemProperties;
import java.io.IOException;
/**
* {@hide}
*/
class IdleCache {
class Entry {
HttpHost mHost;
Connection mConnection;
long mTimeout;
};
private final static int IDLE_CACHE_MAX =
SystemProperties.getInt("net.http.idle_cache.size", 8);
/* Allow five consecutive empty queue checks before shutdown */
private final static int EMPTY_CHECK_MAX = 5;
/* six second timeout for connections */
private final static int TIMEOUT = 6 * 1000;
private final static int CHECK_INTERVAL = 2 * 1000;
private Entry[] mEntries = new Entry[IDLE_CACHE_MAX];
private boolean mShutdownOnPageFinish;
public boolean pageFinished;
private int mCount = 0;
private IdleReaper mThread = null;
/* stats */
private int mCached = 0;
private int mReused = 0;
IdleCache() {
setShutdownFeature(false);
pageFinished = false;
for (int i = 0; i < IDLE_CACHE_MAX; i++) {
mEntries[i] = new Entry();
}
}
/**
* Caches connection, if there is room.
* @return true if connection cached
*/
synchronized boolean cacheConnection(
HttpHost host, Connection connection) {
boolean ret = false;
if (HttpLog.LOGV) {
HttpLog.v("IdleCache size " + mCount + " host " + host);
}
if (mCount < IDLE_CACHE_MAX) {
long time = SystemClock.uptimeMillis();
for (int i = 0; i < IDLE_CACHE_MAX; i++) {
Entry entry = mEntries[i];
if (entry.mHost == null) {
entry.mHost = host;
entry.mConnection = connection;
entry.mTimeout = time + TIMEOUT;
mCount++;
if (HttpLog.LOGV) mCached++;
ret = true;
if (mThread == null) {
mThread = new IdleReaper();
mThread.start();
}
break;
}
}
}
return ret;
}
synchronized Connection getConnection(HttpHost host) {
Connection ret = null;
if (mCount > 0) {
for (int i = 0; i < IDLE_CACHE_MAX; i++) {
Entry entry = mEntries[i];
HttpHost eHost = entry.mHost;
if (eHost != null && eHost.equals(host)) {
ret = entry.mConnection;
entry.mHost = null;
entry.mConnection = null;
mCount--;
if (HttpLog.LOGV) mReused++;
break;
}
}
}
return ret;
}
public void setShutdownFeature(boolean isOn) {
if (isOn) {
isOn = SystemProperties.getBoolean("net.http.idle_cache.shutdown", true);
}
mShutdownOnPageFinish = isOn;
}
synchronized void clear() {
for (int i = 0; (mCount > 0) && (i < IDLE_CACHE_MAX); i++) {
Entry entry = mEntries[i];
if (entry.mHost != null) {
entry.mHost = null;
entry.mConnection.closeConnection();
entry.mConnection = null;
mCount--;
}
}
}
synchronized void clearTcpConnections() {
for (int i = 0; i < IDLE_CACHE_MAX; i++) {
Entry entry = mEntries[i];
if (entry.mHost != null) {
if (entry.mConnection.getTcpPreConnect()) {
entry.mConnection.setTcpPreConnect(false);
}
}
}
}
private synchronized void clearIdle() {
if (mCount > 0) {
long time = SystemClock.uptimeMillis();
for (int i = 0; (mCount > 0) && (i < IDLE_CACHE_MAX); i++) {
Entry entry = mEntries[i];
if (entry.mHost != null && time > entry.mTimeout) {
if (!entry.mConnection.getTcpPreConnect()) {
entry.mHost = null;
entry.mConnection.closeConnection();
entry.mConnection = null;
mCount--;
}
}
}
}
}
public synchronized void wakeup() {
notify();
}
private class IdleReaper extends Thread {
public void run() {
int check = 0;
setName("IdleReaper");
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_BACKGROUND);
synchronized (IdleCache.this) {
while (check < EMPTY_CHECK_MAX) {
try {
IdleCache.this.wait(CHECK_INTERVAL);
} catch (InterruptedException ex) {
}
if(pageFinished) {
clearTcpConnections();
}
if(mShutdownOnPageFinish && pageFinished && (ConnectionThread.sRunning.get() == 0)) {
clear();
break;
}
if (mCount == 0) {
check++;
} else {
check = 0;
clearIdle();
}
}
mThread = null;
}
if (HttpLog.LOGV) {
HttpLog.v("IdleCache IdleReaper shutdown: cached " + mCached +
" reused " + mReused);
mCached = 0;
mReused = 0;
}
pageFinished = false;
}
}
}
@@ -0,0 +1,92 @@
/*
* 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.
*/
/**
* A test EventHandler: Logs everything received
*/
package android.net.http;
import android.net.http.Headers;
/**
* {@hide}
*/
public class LoggingEventHandler implements EventHandler {
public void requestSent() {
HttpLog.v("LoggingEventHandler:requestSent()");
}
public void status(int major_version,
int minor_version,
int code, /* Status-Code value */
String reason_phrase) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler:status() major: " + major_version +
" minor: " + minor_version +
" code: " + code +
" reason: " + reason_phrase);
}
}
public void headers(Headers headers) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler:headers()");
HttpLog.v(headers.toString());
}
}
public void locationChanged(String newLocation, boolean permanent) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: locationChanged() " + newLocation +
" permanent " + permanent);
}
}
public void data(byte[] data, int len) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: data() " + len + " bytes");
}
// HttpLog.v(new String(data, 0, len));
}
public void endData() {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: endData() called");
}
}
public void certificate(SslCertificate certificate) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: certificate(): " + certificate);
}
}
public void error(int id, String description) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: error() called Id:" + id +
" description " + description);
}
}
public boolean handleSslErrorRequest(SslError error) {
if (HttpLog.LOGV) {
HttpLog.v("LoggingEventHandler: handleSslErrorRequest():" + error);
}
// return false so that the caller thread won't wait forever
return false;
}
}
@@ -0,0 +1,534 @@
/*
* 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.net.http;
import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.Header;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.protocol.RequestContent;
/**
* Represents an HTTP request for a given host.
*
* {@hide}
*/
class Request {
/** The eventhandler to call as the request progresses */
EventHandler mEventHandler;
private Connection mConnection;
/** The Apache http request */
BasicHttpRequest mHttpRequest;
/** The path component of this request */
String mPath;
/** Host serving this request */
HttpHost mHost;
/** Set if I'm using a proxy server */
HttpHost mProxyHost;
/** True if request has been cancelled */
volatile boolean mCancelled = false;
/** Priority */
volatile int mPriority = -1;
int mFailCount = 0;
// This will be used to set the Range field if we retry a connection. This
// is http/1.1 feature.
private int mReceivedBytes = 0;
private InputStream mBodyProvider;
private int mBodyLength;
private final static String HOST_HEADER = "Host";
private final static String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
private final static String CONTENT_LENGTH_HEADER = "content-length";
/* Used to synchronize waitUntilComplete() requests */
private final Object mClientResource = new Object();
/** True if loading should be paused **/
private boolean mLoadingPaused = false;
/**
* Processor used to set content-length and transfer-encoding
* headers.
*/
private static RequestContent requestContentProcessor =
new RequestContent();
/**
* Instantiates a new Request.
* @param method GET/POST/PUT
* @param host The server that will handle this request
* @param path path part of URI
* @param bodyProvider InputStream providing HTTP body, null if none
* @param bodyLength length of body, must be 0 if bodyProvider is null
* @param eventHandler request will make progress callbacks on
* this interface
* @param headers reqeust headers
*/
Request(String method, HttpHost host, HttpHost proxyHost, String path,
InputStream bodyProvider, int bodyLength,
EventHandler eventHandler,
Map<String, String> headers,
int pri) {
mEventHandler = eventHandler;
mHost = host;
mProxyHost = proxyHost;
mPath = path;
mBodyProvider = bodyProvider;
mBodyLength = bodyLength;
mPriority = pri;
if (bodyProvider == null && !"POST".equalsIgnoreCase(method)) {
mHttpRequest = new BasicHttpRequest(method, getUri());
} else {
mHttpRequest = new BasicHttpEntityEnclosingRequest(
method, getUri());
// it is ok to have null entity for BasicHttpEntityEnclosingRequest.
// By using BasicHttpEntityEnclosingRequest, it will set up the
// correct content-length, content-type and content-encoding.
if (bodyProvider != null) {
setBodyProvider(bodyProvider, bodyLength);
}
}
addHeader(HOST_HEADER, getHostPort());
/* FIXME: if webcore will make the root document a
high-priority request, we can ask for gzip encoding only on
high priority reqs (saving the trouble for images, etc) */
addHeader(ACCEPT_ENCODING_HEADER, "gzip");
addHeaders(headers);
}
/**
* @param pause True if the load should be paused.
*/
synchronized void setLoadingPaused(boolean pause) {
mLoadingPaused = pause;
// Wake up the paused thread if we're unpausing the load.
if (!mLoadingPaused) {
notify();
}
}
/**
* @param connection Request served by this connection
*/
void setConnection(Connection connection) {
mConnection = connection;
}
/* package */ EventHandler getEventHandler() {
return mEventHandler;
}
/**
* Add header represented by given pair to request. Header will
* be formatted in request as "name: value\r\n".
* @param name of header
* @param value of header
*/
void addHeader(String name, String value) {
if (name == null) {
String damage = "Null http header name";
HttpLog.e(damage);
throw new NullPointerException(damage);
}
if (value == null || value.length() == 0) {
String damage = "Null or empty value for header \"" + name + "\"";
HttpLog.e(damage);
throw new RuntimeException(damage);
}
mHttpRequest.addHeader(name, value);
}
/**
* Add all headers in given map to this request. This is a helper
* method: it calls addHeader for each pair in the map.
*/
void addHeaders(Map<String, String> headers) {
if (headers == null) {
return;
}
Entry<String, String> entry;
Iterator<Entry<String, String>> i = headers.entrySet().iterator();
while (i.hasNext()) {
entry = i.next();
addHeader(entry.getKey(), entry.getValue());
}
}
/**
* Send the request line and headers
*/
void sendRequest(AndroidHttpClientConnection httpClientConnection)
throws HttpException, IOException {
if (mCancelled) return; // don't send cancelled requests
if (HttpLog.LOGV) {
HttpLog.v("Request.sendRequest() " + mHost.getSchemeName() + "://" + getHostPort());
// HttpLog.v(mHttpRequest.getRequestLine().toString());
if (false) {
Iterator i = mHttpRequest.headerIterator();
while (i.hasNext()) {
Header header = (Header)i.next();
HttpLog.v(header.getName() + ": " + header.getValue());
}
}
}
requestContentProcessor.process(mHttpRequest,
mConnection.getHttpContext());
httpClientConnection.sendRequestHeader(mHttpRequest);
if (mHttpRequest instanceof HttpEntityEnclosingRequest) {
httpClientConnection.sendRequestEntity(
(HttpEntityEnclosingRequest) mHttpRequest);
}
if (HttpLog.LOGV) {
HttpLog.v("Request.requestSent() " + mHost.getSchemeName() + "://" + getHostPort() + mPath);
}
}
/**
* Receive a single http response.
*
* @param httpClientConnection the request to receive the response for.
*/
void readResponse(AndroidHttpClientConnection httpClientConnection)
throws IOException, ParseException {
if (mCancelled) return; // don't send cancelled requests
StatusLine statusLine = null;
boolean hasBody = false;
httpClientConnection.flush();
int statusCode = 0;
Headers header = new Headers();
do {
statusLine = httpClientConnection.parseResponseHeader(header);
statusCode = statusLine.getStatusCode();
} while (statusCode < HttpStatus.SC_OK);
if (HttpLog.LOGV) HttpLog.v(
"Request.readResponseStatus() " +
statusLine.toString().length() + " " + statusLine);
ProtocolVersion v = statusLine.getProtocolVersion();
mEventHandler.status(v.getMajor(), v.getMinor(),
statusCode, statusLine.getReasonPhrase());
mEventHandler.headers(header);
HttpEntity entity = null;
hasBody = canResponseHaveBody(mHttpRequest, statusCode);
if (hasBody)
entity = httpClientConnection.receiveResponseEntity(header);
// restrict the range request to the servers claiming that they are
// accepting ranges in bytes
boolean supportPartialContent = "bytes".equalsIgnoreCase(header
.getAcceptRanges());
if (entity != null) {
InputStream is = entity.getContent();
// process gzip content encoding
Header contentEncoding = entity.getContentEncoding();
InputStream nis = null;
byte[] buf = null;
int count = 0;
try {
if (contentEncoding != null &&
contentEncoding.getValue().equals("gzip")) {
nis = new GZIPInputStream(is);
} else {
nis = is;
}
/* accumulate enough data to make it worth pushing it
* up the stack */
buf = mConnection.getBuf();
int len = 0;
int lowWater = buf.length / 2;
while (len != -1) {
synchronized(this) {
while (mLoadingPaused) {
// Put this (network loading) thread to sleep if WebCore
// has asked us to. This can happen with plugins for
// example, if we are streaming data but the plugin has
// filled its internal buffers.
try {
wait();
} catch (InterruptedException e) {
HttpLog.e("Interrupted exception whilst "
+ "network thread paused at WebCore's request."
+ " " + e.getMessage());
}
}
}
len = nis.read(buf, count, buf.length - count);
if (len != -1) {
count += len;
if (supportPartialContent) mReceivedBytes += len;
}
if (len == -1 || count >= lowWater) {
if (HttpLog.LOGV) HttpLog.v("Request.readResponse() " + count);
mEventHandler.data(buf, count);
count = 0;
}
}
} catch (EOFException e) {
/* InflaterInputStream throws an EOFException when the
server truncates gzipped content. Handle this case
as we do truncated non-gzipped content: no error */
if (count > 0) {
// if there is uncommited content, we should commit them
mEventHandler.data(buf, count);
}
if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
} catch(IOException e) {
// don't throw if we have a non-OK status code
if (statusCode == HttpStatus.SC_OK
|| statusCode == HttpStatus.SC_PARTIAL_CONTENT) {
if (supportPartialContent && count > 0) {
// if there is uncommited content, we should commit them
// as we will continue the request
mEventHandler.data(buf, count);
}
throw e;
}
} finally {
if (nis != null) {
nis.close();
}
}
}
mConnection.setCanPersist(entity, statusLine.getProtocolVersion(),
header.getConnectionType());
mEventHandler.endData();
complete();
if (HttpLog.LOGV) HttpLog.v("Request.readResponse(): done " +
mHost.getSchemeName() + "://" + getHostPort() + mPath);
}
/**
* Data will not be sent to or received from server after cancel()
* call. Does not close connection--use close() below for that.
*
* Called by RequestHandle from non-network thread
*/
synchronized void cancel() {
if (HttpLog.LOGV) {
HttpLog.v("Request.cancel(): " + getUri());
}
// Ensure that the network thread is not blocked by a hanging request from WebCore to
// pause the load.
mLoadingPaused = false;
notify();
mCancelled = true;
if (mConnection != null) {
mConnection.cancel();
}
}
String getHostPort() {
String myScheme = mHost.getSchemeName();
int myPort = mHost.getPort();
// Only send port when we must... many servers can't deal with it
if (myPort != 80 && myScheme.equals("http") ||
myPort != 443 && myScheme.equals("https")) {
return mHost.toHostString();
} else {
return mHost.getHostName();
}
}
String getUri() {
if (mProxyHost == null ||
mHost.getSchemeName().equals("https")) {
return mPath;
}
return mHost.getSchemeName() + "://" + getHostPort() + mPath;
}
/**
* for debugging
*/
public String toString() {
return mPath;
}
/**
* If this request has been sent once and failed, it must be reset
* before it can be sent again.
*/
void reset() {
/* clear content-length header */
mHttpRequest.removeHeaders(CONTENT_LENGTH_HEADER);
if (mBodyProvider != null) {
try {
mBodyProvider.reset();
} catch (IOException ex) {
if (HttpLog.LOGV) HttpLog.v(
"failed to reset body provider " +
getUri());
}
setBodyProvider(mBodyProvider, mBodyLength);
}
if (mReceivedBytes > 0) {
// reset the fail count as we continue the request
mFailCount = 0;
// set the "Range" header to indicate that the retry will continue
// instead of restarting the request
HttpLog.v("*** Request.reset() to range:" + mReceivedBytes);
mHttpRequest.setHeader("Range", "bytes=" + mReceivedBytes + "-");
}
}
/**
* Pause thread request completes. Used for synchronous requests,
* and testing
*/
void waitUntilComplete() {
synchronized (mClientResource) {
try {
if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete()");
mClientResource.wait();
if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete() done waiting");
} catch (InterruptedException e) {
}
}
}
void complete() {
synchronized (mClientResource) {
mClientResource.notifyAll();
}
}
/**
* Decide whether a response comes with an entity.
* The implementation in this class is based on RFC 2616.
* Unknown methods and response codes are supposed to
* indicate responses with an entity.
* <br/>
* Derived executors can override this method to handle
* methods and response codes not specified in RFC 2616.
*
* @param request the request, to obtain the executed method
* @param response the response, to obtain the status code
*/
private static boolean canResponseHaveBody(final HttpRequest request,
final int status) {
if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
return false;
}
return status >= HttpStatus.SC_OK
&& status != HttpStatus.SC_NO_CONTENT
&& status != HttpStatus.SC_NOT_MODIFIED;
}
/**
* Supply an InputStream that provides the body of a request. It's
* not great that the caller must also provide the length of the data
* returned by that InputStream, but the client needs to know up
* front, and I'm not sure how to get this out of the InputStream
* itself without a costly readthrough. I'm not sure skip() would
* do what we want. If you know a better way, please let me know.
*/
private void setBodyProvider(InputStream bodyProvider, int bodyLength) {
if (!bodyProvider.markSupported()) {
throw new IllegalArgumentException(
"bodyProvider must support mark()");
}
// Mark beginning of stream
bodyProvider.mark(Integer.MAX_VALUE);
((BasicHttpEntityEnclosingRequest)mHttpRequest).setEntity(
new InputStreamEntity(bodyProvider, bodyLength));
}
/**
* Handles SSL error(s) on the way down from the user (the user
* has already provided their feedback).
*/
public void handleSslErrorResponse(boolean proceed) {
HttpsConnection connection = (HttpsConnection)(mConnection);
if (connection != null) {
connection.restartConnection(proceed);
}
}
/**
* Helper: calls error() on eventhandler with appropriate message
* This should not be called before the mConnection is set.
*/
void error(int errorId, int resourceId) {
mEventHandler.error(
errorId,
mConnection.mContext.getText(
resourceId).toString());
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2008 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.
*/
/**
* Supplies Requests to a Connection
*/
package android.net.http;
import org.apache.http.HttpHost;
/**
* {@hide}
*/
interface RequestFeeder {
Request getRequest();
Request getRequest(HttpHost host);
Request peekRequest();
/**
* @return true if a request for this host is available
*/
boolean haveRequest(HttpHost host);
/**
* Put request back on head of queue
*/
void requeueRequest(Request request);
void requeueRequest(Request request, boolean commit, boolean notif);
}
@@ -0,0 +1,461 @@
/*
* 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.net.http;
import android.net.ParseException;
import android.net.WebAddress;
import android.security.Md5MessageDigest;
import junit.framework.Assert;
import android.webkit.CookieManager;
import org.apache.commons.codec.binary.Base64;
import java.io.InputStream;
import java.lang.Math;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* RequestHandle: handles a request session that may include multiple
* redirects, HTTP authentication requests, etc.
*
* {@hide}
*/
public class RequestHandle {
private String mUrl;
private WebAddress mUri;
private String mMethod;
private Map<String, String> mHeaders;
private RequestQueue mRequestQueue;
private Request mRequest;
private InputStream mBodyProvider;
private int mBodyLength;
private int mRedirectCount = 0;
// Used only with synchronous requests.
private Connection mConnection;
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
public final static int MAX_REDIRECT_COUNT = 16;
/**
* Creates a new request session.
*/
public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
String method, Map<String, String> headers,
InputStream bodyProvider, int bodyLength, Request request) {
if (headers == null) {
headers = new HashMap<String, String>();
}
mHeaders = headers;
mBodyProvider = bodyProvider;
mBodyLength = bodyLength;
mMethod = method == null? "GET" : method;
mUrl = url;
mUri = uri;
mRequestQueue = requestQueue;
mRequest = request;
}
/**
* Creates a new request session with a given Connection. This connection
* is used during a synchronous load to handle this request.
*/
public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
String method, Map<String, String> headers,
InputStream bodyProvider, int bodyLength, Request request,
Connection conn) {
this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
request);
mConnection = conn;
}
/**
* Cancels this request
*/
public void cancel() {
if (mRequest != null) {
mRequest.cancel();
}
}
/**
* Pauses the loading of this request. For example, called from the WebCore thread
* when the plugin can take no more data.
*/
public void pauseRequest(boolean pause) {
if (mRequest != null) {
mRequest.setLoadingPaused(pause);
}
}
/**
* Handles SSL error(s) on the way down from the user (the user
* has already provided their feedback).
*/
public void handleSslErrorResponse(boolean proceed) {
if (mRequest != null) {
mRequest.handleSslErrorResponse(proceed);
}
}
/**
* @return true if we've hit the max redirect count
*/
public boolean isRedirectMax() {
return mRedirectCount >= MAX_REDIRECT_COUNT;
}
public int getRedirectCount() {
return mRedirectCount;
}
public void setRedirectCount(int count) {
mRedirectCount = count;
}
/**
* Create and queue a redirect request.
*
* @param redirectTo URL to redirect to
* @param statusCode HTTP status code returned from original request
* @param cacheHeaders Cache header for redirect URL
* @return true if setup succeeds, false otherwise (redirect loop
* count exceeded, body provider unable to rewind on 307 redirect)
*/
public boolean setupRedirect(String redirectTo, int statusCode,
Map<String, String> cacheHeaders) {
if (HttpLog.LOGV) {
HttpLog.v("RequestHandle.setupRedirect(): redirectCount " +
mRedirectCount);
}
// be careful and remove authentication headers, if any
mHeaders.remove(AUTHORIZATION_HEADER);
mHeaders.remove(PROXY_AUTHORIZATION_HEADER);
if (++mRedirectCount == MAX_REDIRECT_COUNT) {
// Way too many redirects -- fail out
if (HttpLog.LOGV) HttpLog.v(
"RequestHandle.setupRedirect(): too many redirects " +
mRequest);
mRequest.error(EventHandler.ERROR_REDIRECT_LOOP,
com.android.internal.R.string.httpErrorRedirectLoop);
return false;
}
if (mUrl.startsWith("https:") && redirectTo.startsWith("http:")) {
// implement http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
if (HttpLog.LOGV) {
HttpLog.v("blowing away the referer on an https -> http redirect");
}
mHeaders.remove("Referer");
}
mUrl = redirectTo;
try {
mUri = new WebAddress(mUrl);
} catch (ParseException e) {
e.printStackTrace();
}
// update the "Cookie" header based on the redirected url
mHeaders.remove("Cookie");
String cookie = CookieManager.getInstance().getCookie(mUri);
if (cookie != null && cookie.length() > 0) {
mHeaders.put("Cookie", cookie);
}
if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) {
if (HttpLog.LOGV) {
HttpLog.v("replacing POST with GET on redirect to " + redirectTo);
}
mMethod = "GET";
}
/* Only repost content on a 307. If 307, reset the body
provider so we can replay the body */
if (statusCode == 307) {
try {
if (mBodyProvider != null) mBodyProvider.reset();
} catch (java.io.IOException ex) {
if (HttpLog.LOGV) {
HttpLog.v("setupRedirect() failed to reset body provider");
}
return false;
}
} else {
mHeaders.remove("Content-Type");
mBodyProvider = null;
}
// Update the cache headers for this URL
mHeaders.putAll(cacheHeaders);
createAndQueueNewRequest();
return true;
}
/**
* Create and queue an HTTP authentication-response (basic) request.
*/
public void setupBasicAuthResponse(boolean isProxy, String username, String password) {
String response = computeBasicAuthResponse(username, password);
if (HttpLog.LOGV) {
HttpLog.v("setupBasicAuthResponse(): response: " + response);
}
mHeaders.put(authorizationHeader(isProxy), "Basic " + response);
setupAuthResponse();
}
/**
* Create and queue an HTTP authentication-response (digest) request.
*/
public void setupDigestAuthResponse(boolean isProxy,
String username,
String password,
String realm,
String nonce,
String QOP,
String algorithm,
String opaque) {
String response = computeDigestAuthResponse(
username, password, realm, nonce, QOP, algorithm, opaque);
if (HttpLog.LOGV) {
HttpLog.v("setupDigestAuthResponse(): response: " + response);
}
mHeaders.put(authorizationHeader(isProxy), "Digest " + response);
setupAuthResponse();
}
private void setupAuthResponse() {
try {
if (mBodyProvider != null) mBodyProvider.reset();
} catch (java.io.IOException ex) {
if (HttpLog.LOGV) {
HttpLog.v("setupAuthResponse() failed to reset body provider");
}
}
createAndQueueNewRequest();
}
/**
* @return HTTP request method (GET, PUT, etc).
*/
public String getMethod() {
return mMethod;
}
/**
* @return Basic-scheme authentication response: BASE64(username:password).
*/
public static String computeBasicAuthResponse(String username, String password) {
Assert.assertNotNull(username);
Assert.assertNotNull(password);
// encode username:password to base64
return new String(Base64.encodeBase64((username + ':' + password).getBytes()));
}
public void waitUntilComplete() {
mRequest.waitUntilComplete();
}
public void processRequest() {
if (mConnection != null) {
mConnection.processRequests(mRequest);
}
}
/**
* @return Digest-scheme authentication response.
*/
private String computeDigestAuthResponse(String username,
String password,
String realm,
String nonce,
String QOP,
String algorithm,
String opaque) {
Assert.assertNotNull(username);
Assert.assertNotNull(password);
Assert.assertNotNull(realm);
String A1 = username + ":" + realm + ":" + password;
String A2 = mMethod + ":" + mUrl;
// because we do not preemptively send authorization headers, nc is always 1
String nc = "000001";
String cnonce = computeCnonce();
String digest = computeDigest(A1, A2, nonce, QOP, nc, cnonce);
String response = "";
response += "username=" + doubleQuote(username) + ", ";
response += "realm=" + doubleQuote(realm) + ", ";
response += "nonce=" + doubleQuote(nonce) + ", ";
response += "uri=" + doubleQuote(mUrl) + ", ";
response += "response=" + doubleQuote(digest) ;
if (opaque != null) {
response += ", opaque=" + doubleQuote(opaque);
}
if (algorithm != null) {
response += ", algorithm=" + algorithm;
}
if (QOP != null) {
response += ", qop=" + QOP + ", nc=" + nc + ", cnonce=" + doubleQuote(cnonce);
}
return response;
}
/**
* @return The right authorization header (dependeing on whether it is a proxy or not).
*/
public static String authorizationHeader(boolean isProxy) {
if (!isProxy) {
return AUTHORIZATION_HEADER;
} else {
return PROXY_AUTHORIZATION_HEADER;
}
}
/**
* @return Double-quoted MD5 digest.
*/
private String computeDigest(
String A1, String A2, String nonce, String QOP, String nc, String cnonce) {
if (HttpLog.LOGV) {
HttpLog.v("computeDigest(): QOP: " + QOP);
}
if (QOP == null) {
return KD(H(A1), nonce + ":" + H(A2));
} else {
if (QOP.equalsIgnoreCase("auth")) {
return KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + QOP + ":" + H(A2));
}
}
return null;
}
/**
* @return MD5 hash of concat(secret, ":", data).
*/
private String KD(String secret, String data) {
return H(secret + ":" + data);
}
/**
* @return MD5 hash of param.
*/
private String H(String param) {
if (param != null) {
Md5MessageDigest md5 = new Md5MessageDigest();
byte[] d = md5.digest(param.getBytes());
if (d != null) {
return bufferToHex(d);
}
}
return null;
}
/**
* @return HEX buffer representation.
*/
private String bufferToHex(byte[] buffer) {
final char hexChars[] =
{ '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
if (buffer != null) {
int length = buffer.length;
if (length > 0) {
StringBuilder hex = new StringBuilder(2 * length);
for (int i = 0; i < length; ++i) {
byte l = (byte) (buffer[i] & 0x0F);
byte h = (byte)((buffer[i] & 0xF0) >> 4);
hex.append(hexChars[h]);
hex.append(hexChars[l]);
}
return hex.toString();
} else {
return "";
}
}
return null;
}
/**
* Computes a random cnonce value based on the current time.
*/
private String computeCnonce() {
Random rand = new Random();
int nextInt = rand.nextInt();
nextInt = (nextInt == Integer.MIN_VALUE) ?
Integer.MAX_VALUE : Math.abs(nextInt);
return Integer.toString(nextInt, 16);
}
/**
* "Double-quotes" the argument.
*/
private String doubleQuote(String param) {
if (param != null) {
return "\"" + param + "\"";
}
return null;
}
/**
* Creates and queues new request.
*/
private void createAndQueueNewRequest() {
// mConnection is non-null if and only if the requests are synchronous.
if (mConnection != null) {
RequestHandle newHandle = mRequestQueue.queueSynchronousRequest(
mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
mBodyProvider, mBodyLength);
mRequest = newHandle.mRequest;
mConnection = newHandle.mConnection;
newHandle.processRequest();
return;
}
mRequest = mRequestQueue.queueRequest(
mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
mBodyProvider,
mBodyLength, -1, false).mRequest;
}
}
@@ -0,0 +1,752 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
*
* 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.
*/
/**
* High level HTTP Interface
* Queues requests as necessary
*/
package android.net.http;
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.Proxy;
import android.net.WebAddress;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import android.os.SystemClock;
import java.io.InputStream;
import java.util.Comparator;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import org.apache.http.HttpHost;
import java.lang.Thread;
import android.webkit.Subhost;
import android.webkit.PreConnectionManager;
import java.lang.Exception;
/**
* {@hide}
*/
public class RequestQueue implements RequestFeeder {
/**
* Requests, indexed by HttpHost (scheme, host, port)
*/
private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
private final Context mContext;
private final ActivePool mActivePool;
private final ConnectivityManager mConnectivityManager;
private final HashSet<HttpHost> mPriorities;
private HttpHost mProxyHost = null;
private BroadcastReceiver mProxyChangeReceiver;
/* default simultaneous connection count */
private static final int CONNECTION_COUNT =
SystemProperties.getInt("net.http.threads", 4);
private static final int PRE_CONNECTION_THREADS = 5;
private PreConnectionThread[] mPreConnectionThreads;
/**
* This class maintains active connection threads
*/
class ActivePool implements ConnectionManager {
/** Threads used to process requests */
ConnectionThread[] mThreads;
IdleCache mIdleCache;
private int mTotalRequest;
private int mTotalConnection;
private int mConnectionCount;
ActivePool(int connectionCount) {
mIdleCache = new IdleCache();
mConnectionCount = connectionCount;
mThreads = new ConnectionThread[mConnectionCount];
for (int i = 0; i < mConnectionCount; i++) {
mThreads[i] = new ConnectionThread(
mContext, i, this, RequestQueue.this);
}
}
void startup() {
for (int i = 0; i < mConnectionCount; i++) {
mThreads[i].start();
}
}
void shutdown() {
for (int i = 0; i < mConnectionCount; i++) {
mThreads[i].requestStop();
}
}
void startConnectionThread() {
synchronized (RequestQueue.this) {
RequestQueue.this.notify();
}
}
public void startTiming() {
for (int i = 0; i < mConnectionCount; i++) {
ConnectionThread rt = mThreads[i];
rt.mCurrentThreadTime = -1;
rt.mTotalThreadTime = 0;
}
mTotalRequest = 0;
mTotalConnection = 0;
}
public void stopTiming() {
int totalTime = 0;
for (int i = 0; i < mConnectionCount; i++) {
ConnectionThread rt = mThreads[i];
if (rt.mCurrentThreadTime != -1) {
totalTime += rt.mTotalThreadTime;
}
rt.mCurrentThreadTime = 0;
}
Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
+ mTotalRequest + " requests and " + mTotalConnection
+ " new connections");
}
void logState() {
StringBuilder dump = new StringBuilder();
for (int i = 0; i < mConnectionCount; i++) {
dump.append(mThreads[i] + "\n");
}
HttpLog.v(dump.toString());
}
public HttpHost getProxyHost() {
return mProxyHost;
}
void checkPageFinished() {
if (mIdleCache.pageFinished) {
int active = ConnectionThread.sRunning.get();
if (active == 0) {
mIdleCache.wakeup();
}
}
}
void setPageFinished(boolean done) {
mIdleCache.pageFinished = done;
checkPageFinished();
}
void setShutdownFeature(boolean isOn) {
mIdleCache.setShutdownFeature(isOn);
}
/**
* Turns off persistence on all live connections
*/
void disablePersistence() {
for (int i = 0; i < mConnectionCount; i++) {
Connection connection = mThreads[i].mConnection;
if (connection != null) connection.setCanPersist(false);
}
}
/* Linear lookup -- okay for small thread counts. Might use
private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
if this turns out to be a hotspot */
ConnectionThread getThread(HttpHost host) {
synchronized(RequestQueue.this) {
for (int i = 0; i < mThreads.length; i++) {
ConnectionThread ct = mThreads[i];
Connection connection = ct.mConnection;
if (connection != null && connection.mHost.equals(host)) {
return ct;
}
}
}
return null;
}
public Connection getConnection(Context context, HttpHost host) {
host = RequestQueue.this.determineHost(host);
Connection con = mIdleCache.getConnection(host);
if (con == null) {
mTotalConnection++;
con = Connection.getConnection(mContext, host, mProxyHost,
RequestQueue.this);
}
return con;
}
public boolean recycleConnection(Connection connection) {
return mIdleCache.cacheConnection(connection.getHost(), connection);
}
}
/**
* A RequestQueue class instance maintains a set of queued
* requests. It orders them, makes the requests against HTTP
* servers, and makes callbacks to supplied eventHandlers as data
* is read. It supports request prioritization, connection reuse
* and pipelining.
*
* @param context application context
*/
public RequestQueue(Context context) {
this(context, CONNECTION_COUNT);
}
/**
* A RequestQueue class instance maintains a set of queued
* requests. It orders them, makes the requests against HTTP
* servers, and makes callbacks to supplied eventHandlers as data
* is read. It supports request prioritization, connection reuse
* and pipelining.
*
* @param context application context
* @param connectionCount The number of simultaneous connections
*/
public RequestQueue(Context context, int connectionCount) {
mContext = context;
mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
mPriorities = new HashSet<HttpHost>();
mActivePool = new ActivePool(connectionCount);
mActivePool.startup();
mConnectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
mPreConnectionThreads = new PreConnectionThread[PRE_CONNECTION_THREADS];
for (int i = 0; i < PRE_CONNECTION_THREADS; i++) {
mPreConnectionThreads[i] = null;
}
}
public synchronized boolean setRequestPriority(WebAddress uri, int priority) {
// ### this lookup won't work if a proxy is being used
HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
if (mPending.containsKey(host)) {
LinkedList<Request> reqList = mPending.get(host);
// ### O(n) lookup, certainly not ideal
ListIterator iter = reqList.listIterator(0);
while (iter.hasNext()) {
Request request = (Request)iter.next();
if (request.mPath.equals(uri.mPath)) {
request.mPriority = priority;
mPriorities.add(host);
return true;
}
}
}
return false;
}
private void commitPrioritiesForList(LinkedList<Request> reqList) {
Collections.sort(reqList, new Comparator<Object>() {
public int compare(Object o1, Object o2) {
int r1 = ((Request)o1).mPriority;
int r2 = ((Request)o2).mPriority;
if (r1 == r2)
return 0;
else if (r1 == -1)
return 1;
else if (r2 == -1)
return -1;
else if (r1 < r2)
return -1;
return 1;
}
});
}
public synchronized void commitRequestPriorities() {
if (mPriorities.isEmpty())
return;
Iterator iter = mPriorities.iterator();
while (iter.hasNext()) {
HttpHost host = (HttpHost)iter.next();
if (mPending.containsKey(host)) {
LinkedList<Request> reqList = mPending.get(host);
commitPrioritiesForList(reqList);
}
}
mPriorities.clear();
}
/**
* Enables data state and proxy tracking
*/
public synchronized void enablePlatformNotifications() {
if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
if (mProxyChangeReceiver == null) {
mProxyChangeReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
setProxyConfig();
}
};
mContext.registerReceiver(mProxyChangeReceiver,
new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
}
// we need to resample the current proxy setup
setProxyConfig();
}
/**
* If platform notifications have been enabled, call this method
* to disable before destroying RequestQueue
*/
public synchronized void disablePlatformNotifications() {
if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
if (mProxyChangeReceiver != null) {
mContext.unregisterReceiver(mProxyChangeReceiver);
mProxyChangeReceiver = null;
}
}
/**
* Because our IntentReceiver can run within a different thread,
* synchronize setting the proxy
*/
private synchronized void setProxyConfig() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
mProxyHost = null;
} else {
String host = Proxy.getHost(mContext);
if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
if (host == null) {
mProxyHost = null;
} else {
mActivePool.disablePersistence();
mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
}
}
mActivePool.setShutdownFeature((info != null) &&
(info.getType() == ConnectivityManager.TYPE_MOBILE));
}
/**
* used by webkit
* @return proxy host if set, null otherwise
*/
public HttpHost getProxyHost() {
return mProxyHost;
}
/**
* Queues an HTTP request
* @param url The url to load.
* @param method "GET" or "POST."
* @param headers A hashmap of http headers.
* @param eventHandler The event handler for handling returned
* data. Callbacks will be made on the supplied instance.
* @param bodyProvider InputStream providing HTTP body, null if none
* @param bodyLength length of body, must be 0 if bodyProvider is null
*/
public RequestHandle queueRequest(
String url, String method,
Map<String, String> headers, EventHandler eventHandler,
InputStream bodyProvider, int bodyLength) {
return queueRequest(url, method, headers, eventHandler,
bodyProvider, bodyLength, -1, false);
}
public RequestHandle queueRequest(
String url, String method,
Map<String, String> headers, EventHandler eventHandler,
InputStream bodyProvider, int bodyLength, int pri, boolean commit) {
WebAddress uri = new WebAddress(url);
return queueRequest(url, uri, method, headers, eventHandler,
bodyProvider, bodyLength, pri, commit);
}
/**
* Queues an HTTP request
* @param url The url to load.
* @param uri The uri of the url to load.
* @param method "GET" or "POST."
* @param headers A hashmap of http headers.
* @param eventHandler The event handler for handling returned
* data. Callbacks will be made on the supplied instance.
* @param bodyProvider InputStream providing HTTP body, null if none
* @param bodyLength length of body, must be 0 if bodyProvider is null
*/
public RequestHandle queueRequest(
String url, WebAddress uri, String method, Map<String, String> headers,
EventHandler eventHandler,
InputStream bodyProvider, int bodyLength, int pri, boolean commit) {
if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
// Ensure there is an eventHandler set
if (eventHandler == null) {
eventHandler = new LoggingEventHandler();
}
/* Create and queue request */
Request req;
HttpHost httpHost = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
// set up request
req = new Request(method, httpHost, mProxyHost, uri.mPath, bodyProvider,
bodyLength, eventHandler, headers, pri);
queueRequest(req, false, commit);
mActivePool.mTotalRequest++;
mActivePool.startConnectionThread();
return new RequestHandle(
this, url, uri, method, headers, bodyProvider, bodyLength,
req);
}
private static class SyncFeeder implements RequestFeeder {
// This is used in the case where the request fails and needs to be
// requeued into the RequestFeeder.
private Request mRequest;
SyncFeeder() {
}
public Request getRequest() {
Request r = mRequest;
mRequest = null;
return r;
}
public Request getRequest(HttpHost host) {
return getRequest();
}
public Request peekRequest() {
return mRequest;
}
public boolean haveRequest(HttpHost host) {
return mRequest != null;
}
public void requeueRequest(Request r) {
mRequest = r;
}
public void requeueRequest(Request r, boolean commit, boolean notif) {
requeueRequest(r);
}
}
public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
String method, Map<String, String> headers,
EventHandler eventHandler, InputStream bodyProvider,
int bodyLength) {
if (HttpLog.LOGV) {
HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
}
HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
Request req = new Request(method, host, mProxyHost, uri.mPath,
bodyProvider, bodyLength, eventHandler, headers, 0);
// Open a new connection that uses our special RequestFeeder
// implementation.
host = determineHost(host);
Connection conn = Connection.getConnection(mContext, host, mProxyHost,
new SyncFeeder());
// TODO: I would like to process the request here but LoadListener
// needs a RequestHandle to process some messages.
return new RequestHandle(this, url, uri, method, headers, bodyProvider,
bodyLength, req, conn);
}
// Chooses between the proxy and the request's host.
private HttpHost determineHost(HttpHost host) {
// There used to be a comment in ConnectionThread about t-mob's proxy
// being really bad about https. But, HttpsConnection actually looks
// for a proxy and connects through it anyway. I think that this check
// is still valid because if a site is https, we will use
// HttpsConnection rather than HttpConnection if the proxy address is
// not secure.
return (mProxyHost == null || "https".equals(host.getSchemeName()))
? host : mProxyHost;
}
/**
* @return true iff there are any non-active requests pending
*/
synchronized boolean requestsPending() {
return !mPending.isEmpty();
}
/**
* debug tool: prints request queue to log
*/
synchronized void dump() {
HttpLog.v("dump()");
StringBuilder dump = new StringBuilder();
int count = 0;
Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
// mActivePool.log(dump);
if (!mPending.isEmpty()) {
iter = mPending.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
String hostName = entry.getKey().getHostName();
StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
LinkedList<Request> reqList = entry.getValue();
ListIterator reqIter = reqList.listIterator(0);
while (iter.hasNext()) {
Request request = (Request)iter.next();
line.append(request + " ");
}
dump.append(line);
dump.append("\n");
}
}
HttpLog.v(dump.toString());
}
private Map.Entry<HttpHost, LinkedList<Request>> priorityList() {
int curPri = -1;
int entryPri;
Map.Entry<HttpHost, LinkedList<Request>> ret = null;
if (!mPending.isEmpty()) {
Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = mPending.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
if (ret == null) {
ret = entry;
}
entryPri = entry.getValue().getFirst().mPriority;
if (entryPri != -1 && (curPri == -1 || curPri > entryPri)) {
ret = entry;
curPri = entryPri;
}
}
}
return ret;
}
public synchronized Request peekRequest() {
Request ret = null;
Map.Entry<HttpHost, LinkedList<Request>> entry = priorityList();
if (entry != null && !entry.getValue().isEmpty()) {
if (entry.getValue().getFirst().mPriority != -1)
ret = entry.getValue().getFirst();
}
return ret;
}
/*
* RequestFeeder implementation
*/
public synchronized Request getRequest() {
Request ret = null;
Map.Entry<HttpHost, LinkedList<Request>> entry = priorityList();
if (entry != null) {
LinkedList<Request> reqList = entry.getValue();
ret = reqList.removeFirst();
if (reqList.isEmpty()) {
mPending.remove(entry.getKey());
}
}
if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
return ret;
}
/**
* @return a request for given host if possible
*/
public synchronized Request getRequest(HttpHost host) {
Request ret = null;
if (mPending.containsKey(host)) {
LinkedList<Request> reqList = mPending.get(host);
ret = reqList.removeFirst();
if (reqList.isEmpty()) {
mPending.remove(host);
}
}
if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
return ret;
}
/**
* @return true if a request for this host is available
*/
public synchronized boolean haveRequest(HttpHost host) {
return mPending.containsKey(host);
}
/**
* Put request back on head of queue
*/
public void requeueRequest(Request request) {
requeueRequest(request, true, true);
}
public void requeueRequest(Request request, boolean commit, boolean notif) {
queueRequest(request, true, commit);
if (notif)
mActivePool.startConnectionThread();
}
/**
* This must be called to cleanly shutdown RequestQueue
*/
public void shutdown() {
mActivePool.shutdown();
}
protected synchronized void queueRequest(Request request, boolean head, boolean commit) {
HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
LinkedList<Request> reqList;
if (mPending.containsKey(host)) {
reqList = mPending.get(host);
} else {
reqList = new LinkedList<Request>();
mPending.put(host, reqList);
}
if (head) {
reqList.addFirst(request);
} else {
reqList.add(request);
}
if (commit && request.mPriority != -1) {
commitPrioritiesForList(reqList);
} else if (!commit && request.mPriority != -1) {
mPriorities.add(host);
}
}
public void startTiming() {
mActivePool.startTiming();
}
public void stopTiming() {
mActivePool.stopTiming();
}
public void setPageFinished(boolean done) {
mActivePool.setPageFinished(done);
}
public void checkPageFinished() {
mActivePool.checkPageFinished();
}
/* helper */
private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
Request ret = null;
Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
if (iter.hasNext()) {
Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
LinkedList<Request> reqList = entry.getValue();
ret = reqList.removeFirst();
if (reqList.isEmpty()) {
requestQueue.remove(entry.getKey());
}
}
return ret;
}
public int handleConnectionRequest(PreConnectionManager preConnectionMgr, LinkedList<Subhost> subhosts)
{
if (null != subhosts) {
for (int i = 0; i < PRE_CONNECTION_THREADS; ++i) {
if (null == mPreConnectionThreads[i]) {
mPreConnectionThreads[i] = new PreConnectionThread(subhosts, mActivePool, mContext, RequestQueue.this, preConnectionMgr);
mPreConnectionThreads[i].start();
return i;
}
}
}
return -1;
}
public void stopConnectionRequest(int preConnectionThreadId)
{
try {
if (null != mPreConnectionThreads[preConnectionThreadId]) {
if (mPreConnectionThreads[preConnectionThreadId].isAlive()) {
mPreConnectionThreads[preConnectionThreadId].Stop();
}
mPreConnectionThreads[preConnectionThreadId] = null;
}
}catch (Exception e) {
}
return;
}
public void cleanPreConnectionThreadEntry(int preConnectionThreadId)
{
if(0<=preConnectionThreadId && preConnectionThreadId < PreConnectionManager.MAX_PRE_CONNECTION_THREADS) {
mPreConnectionThreads[preConnectionThreadId] = null;
}
return;
}
/**
* This interface is exposed to each connection
*/
interface ConnectionManager {
HttpHost getProxyHost();
Connection getConnection(Context context, HttpHost host);
boolean recycleConnection(Connection connection);
}
}
@@ -0,0 +1,329 @@
/*
* 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.net.http;
import android.os.Bundle;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.x509.X509Name;
/**
* SSL certificate info (certificate details) class
*/
public class SslCertificate {
/**
* SimpleDateFormat pattern for an ISO 8601 date
*/
private static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
/**
* Name of the entity this certificate is issued to
*/
private DName mIssuedTo;
/**
* Name of the entity this certificate is issued by
*/
private DName mIssuedBy;
/**
* Not-before date from the validity period
*/
private Date mValidNotBefore;
/**
* Not-after date from the validity period
*/
private Date mValidNotAfter;
/**
* Bundle key names
*/
private static final String ISSUED_TO = "issued-to";
private static final String ISSUED_BY = "issued-by";
private static final String VALID_NOT_BEFORE = "valid-not-before";
private static final String VALID_NOT_AFTER = "valid-not-after";
/**
* Saves the certificate state to a bundle
* @param certificate The SSL certificate to store
* @return A bundle with the certificate stored in it or null if fails
*/
public static Bundle saveState(SslCertificate certificate) {
Bundle bundle = null;
if (certificate != null) {
bundle = new Bundle();
bundle.putString(ISSUED_TO, certificate.getIssuedTo().getDName());
bundle.putString(ISSUED_BY, certificate.getIssuedBy().getDName());
bundle.putString(VALID_NOT_BEFORE, certificate.getValidNotBefore());
bundle.putString(VALID_NOT_AFTER, certificate.getValidNotAfter());
}
return bundle;
}
/**
* Restores the certificate stored in the bundle
* @param bundle The bundle with the certificate state stored in it
* @return The SSL certificate stored in the bundle or null if fails
*/
public static SslCertificate restoreState(Bundle bundle) {
if (bundle != null) {
return new SslCertificate(
bundle.getString(ISSUED_TO),
bundle.getString(ISSUED_BY),
bundle.getString(VALID_NOT_BEFORE),
bundle.getString(VALID_NOT_AFTER));
}
return null;
}
/**
* Creates a new SSL certificate object
* @param issuedTo The entity this certificate is issued to
* @param issuedBy The entity that issued this certificate
* @param validNotBefore The not-before date from the certificate validity period in ISO 8601 format
* @param validNotAfter The not-after date from the certificate validity period in ISO 8601 format
* @deprecated Use {@link #SslCertificate(String, String, Date, Date)}
*/
@Deprecated
public SslCertificate(
String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
this(issuedTo, issuedBy, parseDate(validNotBefore), parseDate(validNotAfter));
}
/**
* Creates a new SSL certificate object
* @param issuedTo The entity this certificate is issued to
* @param issuedBy The entity that issued this certificate
* @param validNotBefore The not-before date from the certificate validity period
* @param validNotAfter The not-after date from the certificate validity period
*/
public SslCertificate(
String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter) {
mIssuedTo = new DName(issuedTo);
mIssuedBy = new DName(issuedBy);
mValidNotBefore = cloneDate(validNotBefore);
mValidNotAfter = cloneDate(validNotAfter);
}
/**
* Creates a new SSL certificate object from an X509 certificate
* @param certificate X509 certificate
*/
public SslCertificate(X509Certificate certificate) {
this(certificate.getSubjectDN().getName(),
certificate.getIssuerDN().getName(),
certificate.getNotBefore(),
certificate.getNotAfter());
}
/**
* @return Not-before date from the certificate validity period or
* "" if none has been set
*/
public Date getValidNotBeforeDate() {
return cloneDate(mValidNotBefore);
}
/**
* @return Not-before date from the certificate validity period in
* ISO 8601 format or "" if none has been set
*
* @deprecated Use {@link #getValidNotBeforeDate()}
*/
@Deprecated
public String getValidNotBefore() {
return formatDate(mValidNotBefore);
}
/**
* @return Not-after date from the certificate validity period or
* "" if none has been set
*/
public Date getValidNotAfterDate() {
return cloneDate(mValidNotAfter);
}
/**
* @return Not-after date from the certificate validity period in
* ISO 8601 format or "" if none has been set
*
* @deprecated Use {@link #getValidNotAfterDate()}
*/
@Deprecated
public String getValidNotAfter() {
return formatDate(mValidNotAfter);
}
/**
* @return Issued-to distinguished name or null if none has been set
*/
public DName getIssuedTo() {
return mIssuedTo;
}
/**
* @return Issued-by distinguished name or null if none has been set
*/
public DName getIssuedBy() {
return mIssuedBy;
}
/**
* @return A string representation of this certificate for debugging
*/
public String toString() {
return
"Issued to: " + mIssuedTo.getDName() + ";\n" +
"Issued by: " + mIssuedBy.getDName() + ";\n";
}
/**
* Parse an ISO 8601 date converting ParseExceptions to a null result;
*/
private static Date parseDate(String string) {
try {
return new SimpleDateFormat(ISO_8601_DATE_FORMAT).parse(string);
} catch (ParseException e) {
return null;
}
}
/**
* Format a date as an ISO 8601 string, return "" for a null date
*/
private static String formatDate(Date date) {
if (date == null) {
return "";
}
return new SimpleDateFormat(ISO_8601_DATE_FORMAT).format(date);
}
/**
* Clone a possibly null Date
*/
private static Date cloneDate(Date date) {
if (date == null) {
return null;
}
return (Date) date.clone();
}
/**
* A distinguished name helper class: a 3-tuple of:
* - common name (CN),
* - organization (O),
* - organizational unit (OU)
*/
public class DName {
/**
* Distinguished name (normally includes CN, O, and OU names)
*/
private String mDName;
/**
* Common-name (CN) component of the name
*/
private String mCName;
/**
* Organization (O) component of the name
*/
private String mOName;
/**
* Organizational Unit (OU) component of the name
*/
private String mUName;
/**
* Creates a new distinguished name
* @param dName The distinguished name
*/
public DName(String dName) {
if (dName != null) {
mDName = dName;
try {
X509Name x509Name = new X509Name(dName);
Vector val = x509Name.getValues();
Vector oid = x509Name.getOIDs();
for (int i = 0; i < oid.size(); i++) {
if (oid.elementAt(i).equals(X509Name.CN)) {
mCName = (String) val.elementAt(i);
continue;
}
if (oid.elementAt(i).equals(X509Name.O)) {
mOName = (String) val.elementAt(i);
continue;
}
if (oid.elementAt(i).equals(X509Name.OU)) {
mUName = (String) val.elementAt(i);
continue;
}
}
} catch (IllegalArgumentException ex) {
// thrown if there is an error parsing the string
}
}
}
/**
* @return The distinguished name (normally includes CN, O, and OU names)
*/
public String getDName() {
return mDName != null ? mDName : "";
}
/**
* @return The Common-name (CN) component of this name
*/
public String getCName() {
return mCName != null ? mCName : "";
}
/**
* @return The Organization (O) component of this name
*/
public String getOName() {
return mOName != null ? mOName : "";
}
/**
* @return The Organizational Unit (OU) component of this name
*/
public String getUName() {
return mUName != null ? mUName : "";
}
}
}
@@ -0,0 +1,142 @@
/*
* 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.net.http;
import java.security.cert.X509Certificate;
/**
* One or more individual SSL errors and the associated SSL certificate
*/
public class SslError {
/**
* Individual SSL errors (in the order from the least to the most severe):
*/
/**
* The certificate is not yet valid
*/
public static final int SSL_NOTYETVALID = 0;
/**
* The certificate has expired
*/
public static final int SSL_EXPIRED = 1;
/**
* Hostname mismatch
*/
public static final int SSL_IDMISMATCH = 2;
/**
* The certificate authority is not trusted
*/
public static final int SSL_UNTRUSTED = 3;
/**
* The number of different SSL errors (update if you add a new SSL error!!!)
*/
public static final int SSL_MAX_ERROR = 4;
/**
* The SSL error set bitfield (each individual error is an bit index;
* multiple individual errors can be OR-ed)
*/
int mErrors;
/**
* The SSL certificate associated with the error set
*/
SslCertificate mCertificate;
/**
* Creates a new SSL error set object
* @param error The SSL error
* @param certificate The associated SSL certificate
*/
public SslError(int error, SslCertificate certificate) {
addError(error);
mCertificate = certificate;
}
/**
* Creates a new SSL error set object
* @param error The SSL error
* @param certificate The associated SSL certificate
*/
public SslError(int error, X509Certificate certificate) {
addError(error);
mCertificate = new SslCertificate(certificate);
}
/**
* @return The SSL certificate associated with the error set
*/
public SslCertificate getCertificate() {
return mCertificate;
}
/**
* Adds the SSL error to the error set
* @param error The SSL error to add
* @return True iff the error being added is a known SSL error
*/
public boolean addError(int error) {
boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
if (rval) {
mErrors |= (0x1 << error);
}
return rval;
}
/**
* @param error The SSL error to check
* @return True iff the set includes the error
*/
public boolean hasError(int error) {
boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
if (rval) {
rval = ((mErrors & (0x1 << error)) != 0);
}
return rval;
}
/**
* @return The primary, most severe, SSL error in the set
*/
public int getPrimaryError() {
if (mErrors != 0) {
// go from the most to the least severe errors
for (int error = SslError.SSL_MAX_ERROR - 1; error >= 0; --error) {
if ((mErrors & (0x1 << error)) != 0) {
return error;
}
}
}
return 0;
}
/**
* @return A String representation of this SSL error object
* (used mostly for debugging).
*/
public String toString() {
return "primary error: " + getPrimaryError() +
" certificate: " + getCertificate();
}
}
@@ -0,0 +1,41 @@
/*
* 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.net.http;
import android.os.SystemClock;
/**
* {@hide}
* Debugging tool
*/
class Timer {
private long mStart;
private long mLast;
public Timer() {
mStart = mLast = SystemClock.uptimeMillis();
}
public void mark(String message) {
long now = SystemClock.uptimeMillis();
if (HttpLog.LOGV) {
HttpLog.v(message + " " + (now - mLast) + " total " + (now - mStart));
}
mLast = now;
}
}
+2
View File
@@ -0,0 +1,2 @@
<body>
</body>
+5
View File
@@ -0,0 +1,5 @@
<body>
Classes that help with network access, beyond the normal java.net.* APIs.
</body>
@@ -0,0 +1,103 @@
package android.net.wimax;
/**
* {@hide}
*/
public class WimaxManagerConstants
{
/**
* Used by android.net.wimax.WimaxManager for handling management of
* Wimax access.
*/
public static final String WIMAX_SERVICE = "WiMax";
/**
* Broadcast intent action indicating that Wimax has been enabled, disabled,
* enabling, disabling, or unknown. One extra provides this state as an int.
* Another extra provides the previous state, if available.
*/
public static final String WIMAX_ENABLED_STATUS_CHANGED =
"android.net.wimax.WIMAX_STATUS_CHANGED";
/**
* The lookup key for an int that indicates whether Wimax is enabled,
* disabled, enabling, disabling, or unknown.
*/
public static final String EXTRA_WIMAX_STATUS = "wimax_status";
/**
* Broadcast intent action indicating that Wimax state has been changed
* state could be scanning, connecting, connected, disconnecting, disconnected
* initializing, initialized, unknown and ready. One extra provides this state as an int.
* Another extra provides the previous state, if available.
*/
public static final String WIMAX_STATE_CHANGED_ACTION =
"android.net.wimax.WIMAX_STATE_CHANGE";
/**
* Broadcast intent action indicating that Wimax signal level has been changed.
* Level varies from 0 to 3.
*/
public static final String SIGNAL_LEVEL_CHANGED_ACTION =
"android.net.wimax.SIGNAL_LEVEL_CHANGED";
/**
* The lookup key for an int that indicates whether Wimax state is
* scanning, connecting, connected, disconnecting, disconnected
* initializing, initialized, unknown and ready.
*/
public static final String EXTRA_WIMAX_STATE = "WimaxState";
/**
* The lookup key for an int that indicates whether state of Wimax
* is idle.
*/
public static final String EXTRA_WIMAX_STATE_DETAIL = "WimaxStateDetail";
/**
* The lookup key for an int that indicates Wimax signal level.
*/
public static final String EXTRA_NEW_SIGNAL_LEVEL = "newSignalLevel";
/**
* Indicatates Wimax is disabled.
*/
public static final int WIMAX_STATUS_DISABLED = 1;
/**
* Indicatates Wimax is enabled.
*/
public static final int WIMAX_STATUS_ENABLED = 3;
/**
* Indicatates Wimax status is known.
*/
public static final int WIMAX_STATUS_UNKNOWN = 4;
/**
* Indicatates Wimax is in idle state.
*/
public static final int WIMAX_IDLE = 6;
/**
* Indicatates Wimax is being deregistered.
*/
public static final int WIMAX_DEREGISTRATION = 8;
/**
* Indicatates wimax state is unknown.
*/
public static final int WIMAX_STATE_UNKNOWN = 0;
/**
* Indicatates wimax state is connected.
*/
public static final int WIMAX_STATE_CONNECTED = 7;
/**
* Indicatates wimax state is disconnected.
*/
public static final int WIMAX_STATE_DISCONNECTED = 9;
}