Merge change 3424 into donut

* changes:
  Add the VPN services package, VPN service base classes and L2tpIpsecService.
This commit is contained in:
Android (Google) Code Review
2009-06-12 05:33:40 -07:00
20 changed files with 1655 additions and 79 deletions

View File

@@ -0,0 +1,16 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := user
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES :=
LOCAL_PACKAGE_NAME := VpnServices
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
########################
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.vpn"
android:sharedUserId="android.uid.system"
>
<application android:label="VPN Services">
<service android:name=".VpnServiceBinder" android:process=":remote">
<intent-filter>
<!-- These are the interfaces supported by the service, which
you can bind to. -->
<action android:name="android.net.vpn.IVpnService" />
<!-- This is an action code you can use to select the service
without explicitly supplying the implementation class. -->
<action android:name="android.net.vpn.SERVICE" />
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>

190
packages/VpnServices/NOTICE Normal file
View File

@@ -0,0 +1,190 @@
Copyright (c) 2005-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.
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="vpn_notification_title">%s VPN %s</string>
<string name="vpn_notification_connected">connected</string>
<string name="vpn_notification_disconnected">disconnected</string>
<string name="vpn_notification_connected_message">Up time: %s</string>
</resources>

View File

@@ -0,0 +1,258 @@
/*
* 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 com.android.server.vpn;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.SystemProperties;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Proxy to start, stop and interact with an Android service defined in init.rc.
* The android service is expected to accept connection through Unix domain
* socket. When the proxy successfully starts the service, it will establish a
* socket connection with the service. The socket serves two purposes: (1) send
* commands to the service; (2) for the proxy to know whether the service is
* alive.
*
* After the service receives commands from the proxy, it should return either
* 0 if the service will close the socket (and the proxy will re-establish
* another connection immediately after), or 1 if the socket is remained alive.
*/
public class AndroidServiceProxy extends ProcessProxy {
private static final int WAITING_TIME = 15; // sec
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
private static final String SVC_START_CMD = "ctl.start";
private static final String SVC_STOP_CMD = "ctl.stop";
private static final String SVC_STATE_RUNNING = "running";
private static final String SVC_STATE_STOPPED = "stopped";
private static final int END_OF_ARGUMENTS = 255;
private String mServiceName;
private String mSocketName;
private LocalSocket mKeepaliveSocket;
private boolean mControlSocketInUse;
private Integer mSocketResult = null;
private String mTag;
/**
* Creates a proxy with the service name.
* @param serviceName the service name
*/
public AndroidServiceProxy(String serviceName) {
mServiceName = serviceName;
mSocketName = serviceName;
mTag = "SProxy_" + serviceName;
}
@Override
public String getName() {
return "Service " + mServiceName;
}
@Override
public synchronized void stop() {
if (isRunning()) setResultAndCloseControlSocket(-1);
SystemProperties.set(SVC_STOP_CMD, mServiceName);
}
/**
* Sends a command with arguments to the service through the control socket.
* Each argument is sent as a C-style zero-terminated string.
*/
public void sendCommand(String ...args) throws IOException {
OutputStream out = getControlSocketOutput();
for (String arg : args) outputString(out, arg);
checkSocketResult();
}
/**
* Sends a command with arguments to the service through the control socket.
*/
public void sendCommand2(String ...args) throws IOException {
OutputStream out = getControlSocketOutput();
for (String arg : args) outputString2(out, arg);
out.write(END_OF_ARGUMENTS);
out.flush();
checkSocketResult();
}
/**
* {@inheritDoc}
* The method returns when the service exits.
*/
@Override
protected void performTask() throws IOException {
String svc = mServiceName;
Log.d(mTag, "+++++ Execute: " + svc);
SystemProperties.set(SVC_START_CMD, svc);
boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME);
if (success) {
Log.d(mTag, "----- Running: " + svc + ", create keepalive socket");
LocalSocket s = mKeepaliveSocket = createServiceSocket();
setState(ProcessState.RUNNING);
if (s == null) {
// no socket connection, stop hosting the service
stop();
return;
}
try {
for (;;) {
InputStream in = s.getInputStream();
int data = in.read();
if (data >= 0) {
Log.d(mTag, "got data from keepalive socket: " + data);
if (data == 0) {
// re-establish the connection:
// synchronized here so that checkSocketResult() returns
// when new mKeepaliveSocket is available for next cmd
synchronized (this) {
setResultAndCloseControlSocket((byte) data);
s = mKeepaliveSocket = createServiceSocket();
}
} else {
// keep the socket
setSocketResult(data);
}
} else {
// service is gone
if (mControlSocketInUse) setSocketResult(-1);
break;
}
}
Log.d(mTag, "keepalive connection closed");
} catch (IOException e) {
Log.d(mTag, "keepalive socket broken: " + e.getMessage());
}
// Wait 5 seconds for the service to exit
success = blockUntil(SVC_STATE_STOPPED, 5);
Log.d(mTag, "stopping " + svc + ", success? " + success);
} else {
setState(ProcessState.STOPPED);
throw new IOException("cannot start service: " + svc);
}
}
private LocalSocket createServiceSocket() throws IOException {
LocalSocket s = new LocalSocket();
LocalSocketAddress a = new LocalSocketAddress(mSocketName,
LocalSocketAddress.Namespace.RESERVED);
// try a few times in case the service has not listen()ed
IOException excp = null;
for (int i = 0; i < 10; i++) {
try {
s.connect(a);
return s;
} catch (IOException e) {
Log.d(mTag, "service not yet listen()ing; try again");
excp = e;
sleep(500);
}
}
throw excp;
}
private OutputStream getControlSocketOutput() throws IOException {
if (mKeepaliveSocket != null) {
mControlSocketInUse = true;
mSocketResult = null;
return mKeepaliveSocket.getOutputStream();
} else {
throw new IOException("no control socket available");
}
}
private synchronized void checkSocketResult() throws IOException {
try {
// will be notified when the result comes back from service
if (mSocketResult == null) wait();
} catch (InterruptedException e) {
Log.d(mTag, "checkSocketResult(): " + e);
} finally {
mControlSocketInUse = false;
if ((mSocketResult == null) || (mSocketResult < 0)) {
throw new IOException("socket error, result from service: "
+ mSocketResult);
}
}
}
private synchronized void setSocketResult(int result) {
if (mControlSocketInUse) {
mSocketResult = result;
notifyAll();
}
}
private void setResultAndCloseControlSocket(int result) {
setSocketResult(result);
try {
mKeepaliveSocket.shutdownInput();
mKeepaliveSocket.shutdownOutput();
mKeepaliveSocket.close();
} catch (IOException e) {
Log.e(mTag, "close keepalive socket", e);
} finally {
mKeepaliveSocket = null;
}
}
/**
* Waits for the process to be in the expected state. The method returns
* false if after the specified duration (in seconds), the process is still
* not in the expected state.
*/
private boolean blockUntil(String expectedState, int waitTime) {
String cmd = SVC_STATE_CMD_PREFIX + mServiceName;
int sleepTime = 200; // ms
int n = waitTime * 1000 / sleepTime;
for (int i = 0; i < n; i++) {
if (expectedState.equals(SystemProperties.get(cmd))) {
Log.d(mTag, mServiceName + " is " + expectedState + " after "
+ (i * sleepTime) + " msec");
break;
}
sleep(sleepTime);
}
return expectedState.equals(SystemProperties.get(cmd));
}
private void outputString(OutputStream out, String s) throws IOException {
out.write(s.getBytes());
out.write(0);
out.flush();
}
private void outputString2(OutputStream out, String s) throws IOException {
byte[] bytes = s.getBytes();
out.write(bytes.length);
out.write(bytes);
out.flush();
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 com.android.server.vpn;
import android.net.vpn.L2tpIpsecProfile;
import android.security.Keystore;
import java.io.IOException;
/**
* The service that manages the L2TP-over-IPSec VPN connection.
*/
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
private static final String IPSEC_SERVICE = "racoon";
private static final String L2TP_SERVICE = "mtpd";
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
String hostIp = getHostIp();
// IPSEC
AndroidServiceProxy ipsecService = startService(IPSEC_SERVICE);
ipsecService.sendCommand(
String.format("SETKEY %s %s", hostIp, serverIp));
ipsecService.sendCommand(String.format("SET_CERTS %s %s %s %s",
serverIp, getCaCertPath(), getUserCertPath(),
getUserkeyPath()));
// L2TP
AndroidServiceProxy l2tpService = startService(L2TP_SERVICE);
l2tpService.sendCommand2("l2tp", serverIp, "1701", "",
"file", getPppOptionFilePath(),
"name", username,
"password", password);
}
private String getCaCertPath() {
return Keystore.getInstance().getCertificate(
getProfile().getCaCertificate());
}
private String getUserCertPath() {
return Keystore.getInstance().getCertificate(
getProfile().getUserCertificate());
}
private String getUserkeyPath() {
return Keystore.getInstance().getUserkey(getProfile().getUserkey());
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 com.android.server.vpn;
import android.net.vpn.L2tpProfile;
import java.io.IOException;
/**
* The service that manages the L2TP VPN connection.
*/
class L2tpService extends VpnService<L2tpProfile> {
private static final String L2TP_SERVICE = "mtpd";
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
String hostIp = getHostIp();
// L2TP
AndroidServiceProxy l2tpService = startService(L2TP_SERVICE);
l2tpService.sendCommand2("l2tp", serverIp, "1701", "",
"file", getPppOptionFilePath(),
"name", username,
"password", password);
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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 com.android.server.vpn;
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
/**
* Proxy to perform a command with arguments.
*/
public class NormalProcessProxy extends ProcessProxy {
private Process mProcess;
private String[] mArgs;
private String mTag;
/**
* Creates a proxy with the arguments.
* @param args the argument list with the first one being the command
*/
public NormalProcessProxy(String ...args) {
if ((args == null) || (args.length == 0)) {
throw new IllegalArgumentException();
}
mArgs = args;
mTag = "PProxy_" + getName();
}
@Override
public String getName() {
return mArgs[0];
}
@Override
public synchronized void stop() {
if (isStopped()) return;
getHostThread().interrupt();
// TODO: not sure how to reliably kill a process
mProcess.destroy();
setState(ProcessState.STOPPING);
}
@Override
protected void performTask() throws IOException, InterruptedException {
String[] args = mArgs;
Log.d(mTag, "+++++ Execute: " + getEntireCommand());
ProcessBuilder pb = new ProcessBuilder(args);
setState(ProcessState.RUNNING);
Process p = mProcess = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(
p.getInputStream()));
while (true) {
String line = reader.readLine();
if ((line == null) || isStopping()) break;
Log.d(mTag, line);
}
Log.d(mTag, "----- p.waitFor(): " + getName());
p.waitFor();
Log.d(mTag, "----- Done: " + getName());
}
private CharSequence getEntireCommand() {
String[] args = mArgs;
StringBuilder sb = new StringBuilder(args[0]);
for (int i = 1; i < args.length; i++) sb.append(' ').append(args[i]);
return sb;
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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 com.android.server.vpn;
import android.os.ConditionVariable;
import java.io.IOException;
/**
* A proxy class that spawns a process to accomplish a certain task.
*/
public abstract class ProcessProxy {
/**
* Defines the interface to call back when the process is finished or an
* error occurs during execution.
*/
public static interface Callback {
/**
* Called when the process is finished.
* @param proxy the proxy that hosts the process
*/
void done(ProcessProxy proxy);
/**
* Called when some error occurs.
* @param proxy the proxy that hosts the process
*/
void error(ProcessProxy proxy, Throwable error);
}
protected enum ProcessState {
STOPPED, STARTING, RUNNING, STOPPING, ERROR
}
private ProcessState mState = ProcessState.STOPPED;
private ConditionVariable mLock = new ConditionVariable();
private Thread mThread;
/**
* Returns the name of the process.
*/
public abstract String getName();
/**
* Starts the process with a callback.
* @param callback the callback to get notified when the process is finished
* or an error occurs during execution
* @throws IOException when the process is already running or failed to
* start
*/
public synchronized void start(final Callback callback) throws IOException {
if (!isStopped()) {
throw new IOException("Process is already running: " + this);
}
mLock.close();
setState(ProcessState.STARTING);
Thread thread = new Thread(new Runnable() {
public void run() {
try {
performTask();
setState(ProcessState.STOPPED);
mLock.open();
if (callback != null) callback.done(ProcessProxy.this);
} catch (Throwable e) {
setState(ProcessState.ERROR);
if (callback != null) callback.error(ProcessProxy.this, e);
} finally {
mThread = null;
}
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
mThread = thread;
if (!waitUntilRunning()) {
throw new IOException("Failed to start the process: " + this);
}
}
/**
* Starts the process.
* @throws IOException when the process is already running or failed to
* start
*/
public synchronized void start() throws IOException {
start(null);
if (!waitUntilDone()) {
throw new IOException("Failed to complete the process: " + this);
}
}
/**
* Returns the thread that hosts the process.
*/
public Thread getHostThread() {
return mThread;
}
/**
* Blocks the current thread until the hosted process is finished.
*
* @return true if the process is finished normally; false if an error
* occurs
*/
public boolean waitUntilDone() {
while (!mLock.block(1000)) {
if (isStopped() || isInError()) break;
}
return isStopped();
}
/**
* Blocks the current thread until the hosted process is running.
*
* @return true if the process is running normally; false if the process
* is in another state
*/
private boolean waitUntilRunning() {
for (;;) {
if (!isStarting()) break;
}
return isRunning();
}
/**
* Stops and destroys the process.
*/
public abstract void stop();
/**
* Checks whether the process is finished.
* @return true if the process is stopped
*/
public boolean isStopped() {
return (mState == ProcessState.STOPPED);
}
/**
* Checks whether the process is being stopped.
* @return true if the process is being stopped
*/
public boolean isStopping() {
return (mState == ProcessState.STOPPING);
}
/**
* Checks whether the process is being started.
* @return true if the process is being started
*/
public boolean isStarting() {
return (mState == ProcessState.STARTING);
}
/**
* Checks whether the process is running.
* @return true if the process is running
*/
public boolean isRunning() {
return (mState == ProcessState.RUNNING);
}
/**
* Checks whether some error has occurred and the process is stopped.
* @return true if some error has occurred and the process is stopped
*/
public boolean isInError() {
return (mState == ProcessState.ERROR);
}
/**
* Performs the actual task. Subclasses must make sure that the method
* is blocked until the task is done or an error occurs.
*/
protected abstract void performTask()
throws IOException, InterruptedException;
/**
* Sets the process state.
* @param state the new state to be in
*/
protected void setState(ProcessState state) {
mState = state;
}
/**
* Makes the current thread sleep for the specified time.
* @param msec time to sleep in miliseconds
*/
protected void sleep(int msec) {
try {
Thread.currentThread().sleep(msec);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,627 @@
/*
* 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 com.android.server.vpn;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.net.vpn.SingleServerProfile;
import android.net.vpn.VpnManager;
import android.net.vpn.VpnState;
import android.os.FileObserver;
import android.os.SystemProperties;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* The service base class for managing a type of VPN connection.
*/
abstract class VpnService<E extends SingleServerProfile> {
private static final int NOTIFICATION_ID = 1;
private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/";
public static final String DEFAULT_CONFIG_PATH = "/etc";
private static final int DNS_TIMEOUT = 3000; // ms
private static final String DNS1 = "net.dns1";
private static final String DNS2 = "net.dns2";
private static final String REMOTE_IP = "net.ipremote";
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
private static final String SERVER_IP = "net.vpn.server_ip";
private static final int VPN_TIMEOUT = 30000; // milliseconds
private static final int ONE_SECOND = 1000; // milliseconds
private static final int FIVE_SECOND = 5000; // milliseconds
private static final String LOGWRAPPER = "/system/bin/logwrapper";
private final String TAG = VpnService.class.getSimpleName();
E mProfile;
VpnServiceBinder mContext;
private VpnState mState = VpnState.IDLE;
private boolean mInError;
// connection settings
private String mOriginalDns1;
private String mOriginalDns2;
private String mVpnDns1 = "";
private String mVpnDns2 = "";
private String mOriginalDomainSuffices;
private String mHostIp;
private long mStartTime; // VPN connection start time
// monitors if the VPN connection is sucessfully established
private FileMonitor mConnectMonitor;
// watch dog timer; fired up if the connection cannot be established within
// VPN_TIMEOUT
private Object mWatchdog;
// for helping managing multiple Android services
private ServiceHelper mServiceHelper = new ServiceHelper();
// for helping showing, updating notification
private NotificationHelper mNotification = new NotificationHelper();
/**
* Establishes a VPN connection with the specified username and password.
*/
protected abstract void connect(String serverIp, String username,
String password) throws IOException;
/**
* Tears down the VPN connection. The base class simply terminates all the
* Android services. A subclass may need to do some clean-up before that.
*/
protected void disconnect() {
}
/**
* Starts an Android service defined in init.rc.
*/
protected AndroidServiceProxy startService(String serviceName)
throws IOException {
return mServiceHelper.startService(serviceName);
}
protected String getPppOptionFilePath() throws IOException {
String subpath = getProfileSubpath("/ppp/peers");
String[] kids = new File(subpath).list();
if ((kids == null) || (kids.length == 0)) {
throw new IOException("no option file found in " + subpath);
}
if (kids.length > 1) {
Log.w(TAG, "more than one option file found in " + subpath
+ ", arbitrarily choose " + kids[0]);
}
return subpath + "/" + kids[0];
}
/**
* Returns the VPN profile associated with the connection.
*/
protected E getProfile() {
return mProfile;
}
/**
* Returns the profile path where configuration files reside.
*/
protected String getProfilePath() throws IOException {
String path = PROFILES_ROOT + mProfile.getId();
File dir = new File(path);
if (!dir.exists()) throw new IOException("Profile dir does not exist");
return path;
}
/**
* Returns the path where default configuration files reside.
*/
protected String getDefaultConfigPath() throws IOException {
return DEFAULT_CONFIG_PATH;
}
/**
* Returns the host IP for establishing the VPN connection.
*/
protected String getHostIp() throws IOException {
if (mHostIp == null) mHostIp = reallyGetHostIp();
return mHostIp;
}
/**
* Returns the IP of the specified host name.
*/
protected String getIp(String hostName) throws IOException {
InetAddress iaddr = InetAddress.getByName(hostName);
byte[] aa = iaddr.getAddress();
StringBuilder sb = new StringBuilder().append(byteToInt(aa[0]));
for (int i = 1; i < aa.length; i++) {
sb.append(".").append(byteToInt(aa[i]));
}
return sb.toString();
}
/**
* Returns the path of the script file that is executed when the VPN
* connection is established.
*/
protected String getConnectMonitorFile() {
return "/etc/ppp/ip-up";
}
/**
* Sets the system property. The method is blocked until the value is
* settled in.
* @param name the name of the property
* @param value the value of the property
* @throws IOException if it fails to set the property within 2 seconds
*/
protected void setSystemProperty(String name, String value)
throws IOException {
SystemProperties.set(name, value);
for (int i = 0; i < 5; i++) {
String v = SystemProperties.get(name);
if (v.equals(value)) {
return;
} else {
Log.d(TAG, "sys_prop: wait for " + name + " to settle in");
sleep(400);
}
}
throw new IOException("Failed to set system property: " + name);
}
void setContext(VpnServiceBinder context, E profile) {
mContext = context;
mProfile = profile;
}
VpnState getState() {
return mState;
}
synchronized void onConnect(String username, String password)
throws IOException {
mState = VpnState.CONNECTING;
broadcastConnectivity(VpnState.CONNECTING);
String serverIp = getIp(getProfile().getServerName());
setSystemProperty(SERVER_IP, serverIp);
onBeforeConnect();
connect(serverIp, username, password);
}
synchronized void onDisconnect(boolean cleanUpServices) {
try {
mState = VpnState.DISCONNECTING;
broadcastConnectivity(VpnState.DISCONNECTING);
mNotification.showDisconnect();
// subclass implementation
if (cleanUpServices) disconnect();
mServiceHelper.stop();
} catch (Throwable e) {
Log.e(TAG, "onError()", e);
onFinalCleanUp();
}
}
synchronized void onError() {
// error may occur during or after connection setup
// and it may be due to one or all services gone
mInError = true;
switch (mState) {
case CONNECTED:
onDisconnect(true);
break;
case CONNECTING:
onDisconnect(false);
break;
}
}
private void createConnectMonitor() {
mConnectMonitor = new FileMonitor(getConnectMonitorFile(),
new Runnable() {
public void run() {
onConnectMonitorTriggered();
}
});
}
private void onBeforeConnect() {
mNotification.disableNotification();
createConnectMonitor();
mConnectMonitor.startWatching();
saveOriginalDnsProperties();
mWatchdog = startTimer(VPN_TIMEOUT, new Runnable() {
public void run() {
synchronized (VpnService.this) {
if (mState == VpnState.CONNECTING) {
Log.d(TAG, " watchdog timer is fired !!");
onError();
}
}
}
});
}
private synchronized void onConnectMonitorTriggered() {
Log.d(TAG, "onConnectMonitorTriggered()");
stopTimer(mWatchdog);
mConnectMonitor.stopWatching();
saveVpnDnsProperties();
saveAndSetDomainSuffices();
startConnectivityMonitor();
mState = VpnState.CONNECTED;
broadcastConnectivity(VpnState.CONNECTED);
}
private synchronized void onFinalCleanUp() {
Log.d(TAG, "onFinalCleanUp()");
if (mState == VpnState.IDLE) return;
// keep the notification when error occurs
if (!mInError) mNotification.disableNotification();
restoreOriginalDnsProperties();
restoreOriginalDomainSuffices();
if (mConnectMonitor != null) mConnectMonitor.stopWatching();
if (mWatchdog != null) stopTimer(mWatchdog);
mState = VpnState.IDLE;
broadcastConnectivity(VpnState.IDLE);
// stop the service itself
mContext.stopSelf();
}
private synchronized void onOneServiceGone() {
switch (mState) {
case IDLE:
case DISCONNECTING:
break;
default:
onError();
}
}
private synchronized void onAllServicesGone() {
switch (mState) {
case IDLE:
break;
case DISCONNECTING:
// daemons are gone; now clean up everything
onFinalCleanUp();
break;
default:
onError();
}
}
private void saveOriginalDnsProperties() {
mOriginalDns1 = SystemProperties.get(DNS1);
mOriginalDns2 = SystemProperties.get(DNS2);
Log.d(TAG, String.format("save original dns prop: %s, %s",
mOriginalDns1, mOriginalDns2));
}
private void restoreOriginalDnsProperties() {
// restore only if they are not overridden
if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
SystemProperties.get(DNS1), mOriginalDns1));
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
SystemProperties.get(DNS2), mOriginalDns2));
SystemProperties.set(DNS1, mOriginalDns1);
SystemProperties.set(DNS2, mOriginalDns2);
}
}
private void saveVpnDnsProperties() {
mVpnDns1 = mVpnDns2 = "";
for (int i = 0; i < 10; i++) {
mVpnDns1 = SystemProperties.get(DNS1);
mVpnDns2 = SystemProperties.get(DNS2);
if (mVpnDns1.equals(mOriginalDns1)) {
Log.d(TAG, "wait for vpn dns to settle in..." + i);
sleep(500);
} else {
Log.d(TAG, String.format("save vpn dns prop: %s, %s",
mVpnDns1, mVpnDns2));
return;
}
}
Log.e(TAG, "saveVpnDnsProperties(): DNS not updated??");
}
private void restoreVpnDnsProperties() {
if (isNullOrEmpty(mVpnDns1) && isNullOrEmpty(mVpnDns2)) {
return;
}
Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
SystemProperties.get(DNS1), mVpnDns1));
Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
SystemProperties.get(DNS2), mVpnDns2));
SystemProperties.set(DNS1, mVpnDns1);
SystemProperties.set(DNS2, mVpnDns2);
}
private void saveAndSetDomainSuffices() {
mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
Log.d(TAG, "save original dns search: " + mOriginalDomainSuffices);
String list = mProfile.getDomainSuffices();
if (!isNullOrEmpty(list)) {
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
}
}
private void restoreOriginalDomainSuffices() {
Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices);
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
}
private void broadcastConnectivity(VpnState s) {
new VpnManager(mContext).broadcastConnectivity(mProfile.getName(), s);
}
private void startConnectivityMonitor() {
mStartTime = System.currentTimeMillis();
new Thread(new Runnable() {
public void run() {
Log.d(TAG, " +++++ connectivity monitor running");
try {
for (;;) {
synchronized (VpnService.this) {
if (mState != VpnState.CONNECTED) break;
mNotification.update();
checkConnectivity();
VpnService.this.wait(ONE_SECOND);
}
}
} catch (InterruptedException e) {
Log.e(TAG, "connectivity monitor", e);
}
Log.d(TAG, " ----- connectivity monitor stopped");
}
}).start();
}
private void checkConnectivity() {
checkDnsProperties();
}
private void checkDnsProperties() {
String dns1 = SystemProperties.get(DNS1);
if (!mVpnDns1.equals(dns1)) {
Log.w(TAG, " @@ !!! dns being overridden");
onError();
}
}
private Object startTimer(final int milliseconds, final Runnable task) {
Thread thread = new Thread(new Runnable() {
public void run() {
Log.d(TAG, "watchdog timer started");
Thread t = Thread.currentThread();
try {
synchronized (t) {
t.wait(milliseconds);
}
task.run();
} catch (InterruptedException e) {
// ignored
}
Log.d(TAG, "watchdog timer stopped");
}
});
thread.start();
return thread;
}
private void stopTimer(Object timer) {
synchronized (timer) {
timer.notify();
}
}
private String reallyGetHostIp() throws IOException {
Socket s = new Socket();
s.connect(new InetSocketAddress("www.google.com", 80), DNS_TIMEOUT);
String ipAddress = s.getLocalAddress().getHostAddress();
Log.d(TAG, "Host IP: " + ipAddress);
s.close();
return ipAddress;
}
private String getProfileSubpath(String subpath) throws IOException {
String path = getProfilePath() + subpath;
if (new File(path).exists()) {
return path;
} else {
Log.w(TAG, "Profile subpath does not exist: " + path
+ ", use default one");
String path2 = getDefaultConfigPath() + subpath;
if (!new File(path2).exists()) {
throw new IOException("Profile subpath does not exist at "
+ path + " or " + path2);
}
return path2;
}
}
private void sleep(int ms) {
try {
Thread.currentThread().sleep(ms);
} catch (InterruptedException e) {
}
}
private static boolean isNullOrEmpty(String message) {
return ((message == null) || (message.length() == 0));
}
private static int byteToInt(byte b) {
return ((int) b) & 0x0FF;
}
private class ServiceHelper implements ProcessProxy.Callback {
private List<AndroidServiceProxy> mServiceList =
new ArrayList<AndroidServiceProxy>();
// starts an Android service
AndroidServiceProxy startService(String serviceName)
throws IOException {
AndroidServiceProxy service = new AndroidServiceProxy(serviceName);
mServiceList.add(service);
service.start(this);
return service;
}
// stops all the Android services
void stop() {
if (mServiceList.isEmpty()) {
onFinalCleanUp();
} else {
for (AndroidServiceProxy s : mServiceList) s.stop();
}
}
//@Override
public void done(ProcessProxy p) {
Log.d(TAG, "service done: " + p.getName());
commonCallback((AndroidServiceProxy) p);
}
//@Override
public void error(ProcessProxy p, Throwable e) {
Log.e(TAG, "service error: " + p.getName(), e);
commonCallback((AndroidServiceProxy) p);
}
private void commonCallback(AndroidServiceProxy service) {
mServiceList.remove(service);
onOneServiceGone();
if (mServiceList.isEmpty()) onAllServicesGone();
}
}
private class FileMonitor extends FileObserver {
private Runnable mCallback;
FileMonitor(String path, Runnable callback) {
super(path, CLOSE_NOWRITE);
mCallback = callback;
}
@Override
public void onEvent(int event, String path) {
if ((event & CLOSE_NOWRITE) > 0) mCallback.run();
}
}
// Helper class for showing, updating notification.
private class NotificationHelper {
void update() {
String title = getNotificationTitle(true);
Notification n = new Notification(R.drawable.vpn_connected, title,
mStartTime);
n.setLatestEventInfo(mContext, title,
getNotificationMessage(true), prepareNotificationIntent());
n.flags |= Notification.FLAG_NO_CLEAR;
n.flags |= Notification.FLAG_ONGOING_EVENT;
enableNotification(n);
}
void showDisconnect() {
String title = getNotificationTitle(false);
Notification n = new Notification(R.drawable.vpn_disconnected,
title, System.currentTimeMillis());
n.setLatestEventInfo(mContext, title,
getNotificationMessage(false), prepareNotificationIntent());
n.flags |= Notification.FLAG_AUTO_CANCEL;
disableNotification();
enableNotification(n);
}
void disableNotification() {
((NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE)).cancel(NOTIFICATION_ID);
}
private void enableNotification(Notification n) {
((NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, n);
}
private PendingIntent prepareNotificationIntent() {
return PendingIntent.getActivity(mContext, 0,
new VpnManager(mContext).createSettingsActivityIntent(), 0);
}
private String getNotificationTitle(boolean connected) {
String connectedOrNot = connected
? mContext.getString(R.string.vpn_notification_connected)
: mContext.getString(
R.string.vpn_notification_disconnected);
return String.format(
mContext.getString(R.string.vpn_notification_title),
mProfile.getName(), connectedOrNot);
}
private String getTimeFormat(long duration) {
long hours = duration / 3600;
StringBuilder sb = new StringBuilder();
if (hours > 0) sb.append(hours).append(':');
sb.append(String.format("%02d:%02d", (duration % 3600 / 60),
(duration % 60)));
return sb.toString();
}
private String getNotificationMessage(boolean connected) {
if (connected) {
long time = (System.currentTimeMillis() - mStartTime) / 1000;
return String.format(mContext.getString(
R.string.vpn_notification_connected_message),
getTimeFormat(time));
} else {
return "";
}
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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 com.android.server.vpn;
import android.app.Service;
import android.content.Intent;
import android.net.vpn.IVpnService;
import android.net.vpn.L2tpIpsecProfile;
import android.net.vpn.L2tpProfile;
import android.net.vpn.VpnManager;
import android.net.vpn.VpnProfile;
import android.net.vpn.VpnState;
import android.os.IBinder;
import android.util.Log;
import java.io.IOException;
/**
* The service class for managing a VPN connection. It implements the
* {@link IVpnService} binder interface.
*/
public class VpnServiceBinder extends Service {
private final String TAG = VpnServiceBinder.class.getSimpleName();
// The actual implementation is delegated to the VpnService class.
private VpnService<? extends VpnProfile> mService;
private final IBinder mBinder = new IVpnService.Stub() {
public boolean connect(VpnProfile p, String username, String password) {
return VpnServiceBinder.this.connect(p, username, password);
}
public void disconnect() {
if (mService != null) mService.onDisconnect(true);
}
public void checkStatus(VpnProfile p) {
VpnServiceBinder.this.checkStatus(p);
}
};
public IBinder onBind(Intent intent) {
return mBinder;
}
private synchronized boolean connect(
VpnProfile p, String username, String password) {
if (mService != null) return false;
try {
mService = createService(p);
mService.onConnect(username, password);
return true;
} catch (Throwable e) {
Log.e(TAG, "connect()", e);
if (mService != null) mService.onError();
return false;
}
}
private synchronized void checkStatus(VpnProfile p) {
if (mService == null) broadcastConnectivity(p.getName(), VpnState.IDLE);
if (!p.getName().equals(mService.mProfile.getName())) {
broadcastConnectivity(p.getName(), VpnState.IDLE);
} else {
broadcastConnectivity(p.getName(), mService.getState());
}
}
private VpnService<? extends VpnProfile> createService(VpnProfile p) {
switch (p.getType()) {
case L2TP_IPSEC:
L2tpIpsecService l2tpIpsec = new L2tpIpsecService();
l2tpIpsec.setContext(this, (L2tpIpsecProfile) p);
return l2tpIpsec;
case L2TP:
L2tpService l2tp = new L2tpService();
l2tp.setContext(this, (L2tpProfile) p);
return l2tp;
default:
return null;
}
}
private void broadcastConnectivity(String name, VpnState s) {
new VpnManager(this).broadcastConnectivity(name, s);
}
}

View File

@@ -23,6 +23,8 @@ import android.os.Parcel;
* {@hide} * {@hide}
*/ */
public class L2tpIpsecProfile extends SingleServerProfile { public class L2tpIpsecProfile extends SingleServerProfile {
private static final long serialVersionUID = 1L;
private String mUserCertificate; private String mUserCertificate;
private String mCaCertificate; private String mCaCertificate;
private String mUserkey; private String mUserkey;

View File

@@ -21,6 +21,8 @@ package android.net.vpn;
* {@hide} * {@hide}
*/ */
public class L2tpProfile extends SingleServerProfile { public class L2tpProfile extends SingleServerProfile {
private static final long serialVersionUID = 1L;
@Override @Override
public VpnType getType() { public VpnType getType() {
return VpnType.L2TP; return VpnType.L2TP;

View File

@@ -48,19 +48,8 @@ public class VpnManager {
private static final String PACKAGE_PREFIX = private static final String PACKAGE_PREFIX =
VpnManager.class.getPackage().getName() + "."; VpnManager.class.getPackage().getName() + ".";
/** Action to start the activity of installing a new profile. */ // Action to start VPN service
public static final String ACTION_VPN_INSTALL_PROFILE = private static final String ACTION_VPN_SERVICE = PACKAGE_PREFIX + "SERVICE";
PACKAGE_PREFIX + "INSTALL_PROFILE";
/**
* Key to the installation path in the intent of installing a new profile.
*/
public static final String KEY_INSTALLATION_PATH = "install_path";
public static final String DEFAULT_INSTALLATION_PATH =
"/data/local/tmp/vpn";
// Action to start VPN installation monitor service
private static final String SERVICE_VPN_INSTALL_MONITOR =
PACKAGE_PREFIX + "INSTALLATION_MONITOR";
// Action to start VPN settings // Action to start VPN settings
private static final String ACTION_VPN_SETTINGS = PACKAGE_PREFIX + "SETTINGS"; private static final String ACTION_VPN_SETTINGS = PACKAGE_PREFIX + "SETTINGS";
@@ -112,53 +101,29 @@ public class VpnManager {
} }
} }
private String getServiceActionName(VpnType type) { /**
return PACKAGE_PREFIX + type.getServiceName(); * Starts the VPN service to establish VPN connection.
*/
public void startVpnService() {
mContext.startService(new Intent(ACTION_VPN_SERVICE));
} }
/** /**
* Starts the VPN service of the specified type. * Stops the VPN service.
*/ */
public boolean startService(VpnType type) { public void stopVpnService() {
String serviceAction = getServiceActionName(type); mContext.stopService(new Intent(ACTION_VPN_SERVICE));
if (serviceAction != null) {
Log.i(TAG, "start service: " + serviceAction);
mContext.startService(new Intent(serviceAction));
return true;
} else {
Log.w(TAG, "unknown vpn type to start service for: " + type);
return false;
}
} }
/** /**
* Stops the VPN service of the specified type. * Binds the specified ServiceConnection with the VPN service.
*/ */
public void stopService(VpnType type) { public boolean bindVpnService(ServiceConnection c) {
String serviceAction = getServiceActionName(type); if (!mContext.bindService(new Intent(ACTION_VPN_SERVICE), c, 0)) {
if (serviceAction != null) { Log.w(TAG, "failed to connect to VPN service");
Log.i(TAG, "stop service for: " + type);
mContext.stopService(new Intent(serviceAction));
} else {
Log.w(TAG, "unknown vpn type to stop service for: " + type);
}
}
/**
* Binds the specified ServiceConnection with the VPN service of the
* specified type.
*/
public boolean bindService(VpnType type, ServiceConnection c) {
String serviceAction = getServiceActionName(type);
if (serviceAction == null) {
Log.w(TAG, "unknown vpn type to bind service for: " + type);
return false;
}
if (!mContext.bindService(new Intent(serviceAction), c, 0)) {
Log.w(TAG, "failed to connect to service: " + type);
return false; return false;
} else { } else {
Log.v(TAG, "succeeded to connect to service: " + type); Log.d(TAG, "succeeded to connect to VPN service");
return true; return true;
} }
} }
@@ -181,21 +146,6 @@ public class VpnManager {
mContext.unregisterReceiver(r); mContext.unregisterReceiver(r);
} }
/**
* Starts the installation monitor service.
* The service monitors the default installtion path (under /data/local/tmp)
* and automatically starts the activity to create a new profile when new
* configuration files appear in that path.
*/
public void startInstallationMonitorService() {
mContext.startService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
}
/** Stops the installation monitor service. */
public void stopInstallationMonitorService() {
mContext.stopService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
}
/** Starts the VPN settings activity. */ /** Starts the VPN settings activity. */
public void startSettingsActivity() { public void startSettingsActivity() {
Intent intent = new Intent(ACTION_VPN_SETTINGS); Intent intent = new Intent(ACTION_VPN_SETTINGS);
@@ -203,14 +153,10 @@ public class VpnManager {
mContext.startActivity(intent); mContext.startActivity(intent);
} }
/** /** Creates an intent to start the VPN settings activity. */
* Starts the activity to install a customized profile. public Intent createSettingsActivityIntent() {
* @param installPath the path where all the configuration files are located Intent intent = new Intent(ACTION_VPN_SETTINGS);
*/
public void startInstallProfileActivity(String installPath) {
Intent intent = new Intent(ACTION_VPN_INSTALL_PROFILE);
intent.putExtra(KEY_INSTALLATION_PATH, installPath);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent); return intent;
} }
} }

View File

@@ -27,7 +27,8 @@ import java.io.Serializable;
* A VPN profile. * A VPN profile.
* {@hide} * {@hide}
*/ */
public abstract class VpnProfile implements Parcelable { public abstract class VpnProfile implements Parcelable, Serializable {
private static final long serialVersionUID = 1L;
private String mName; // unique display name private String mName; // unique display name
private String mId; // unique identifier private String mId; // unique identifier
private String mDomainSuffices; // space separated list private String mDomainSuffices; // space separated list

View File

@@ -24,8 +24,10 @@ package android.net.vpn;
* {@link CONNECTED} if successful; back to {@link IDLE} if failed. * {@link CONNECTED} if successful; back to {@link IDLE} if failed.
* When the connection is about to be torn down, it goes to * When the connection is about to be torn down, it goes to
* {@link DISCONNECTING} and then {@link IDLE}. * {@link DISCONNECTING} and then {@link IDLE}.
* {@link CANCELLED} is a state when a VPN connection attempt is aborted, and
* is in transition to {@link IDLE}.
* {@hide} * {@hide}
*/ */
public enum VpnState { public enum VpnState {
CONNECTING, DISCONNECTING, CONNECTED, IDLE CONNECTING, DISCONNECTING, CANCELLED, CONNECTED, IDLE
} }

View File

@@ -39,8 +39,4 @@ public enum VpnType {
public Class<? extends VpnProfile> getProfileClass() { public Class<? extends VpnProfile> getProfileClass() {
return mClass; return mClass;
} }
public String getServiceName() {
return this.toString();
}
} }