blob: b754dbacf7c7f48f5153f1c6adbf3338fc68870a [file] [log] [blame]
Chia-chi Yehff3bdca2011-05-23 17:26:46 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageManager;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
29import android.graphics.drawable.Drawable;
30import android.net.INetworkManagementEventObserver;
31import android.os.Binder;
32import android.os.Bundle;
33import android.os.ParcelFileDescriptor;
34import android.os.RemoteException;
35import android.util.Log;
36
37import com.android.internal.R;
38import com.android.server.ConnectivityService.VpnCallback;
39
40/**
41 * @hide
42 */
43public class Vpn extends INetworkManagementEventObserver.Stub {
44
45 private final static String TAG = "Vpn";
46 private final static String VPN = android.Manifest.permission.VPN;
47
48 private final Context mContext;
49 private final VpnCallback mCallback;
50
51 private String mPackageName;
52 private String mInterfaceName;
53 private String mDnsPropertyPrefix;
54
55 public Vpn(Context context, VpnCallback callback) {
56 mContext = context;
57 mCallback = callback;
58 }
59
60 /**
61 * Prepare for a VPN application.
62 *
63 * @param packageName The package name of the new VPN application.
64 * @return The name of the current prepared package.
65 */
66 public synchronized String prepare(String packageName) {
67
68 // TODO: Check if the caller is VpnDialogs.
69
70 if (packageName == null) {
71 return mPackageName;
72 }
73
74 // Check the permission of the given application.
75 PackageManager pm = mContext.getPackageManager();
76 if (pm.checkPermission(VPN, packageName) != PackageManager.PERMISSION_GRANTED) {
77 throw new SecurityException(packageName + " does not have " + VPN);
78 }
79
80 // Reset the interface and hide the notification.
81 if (mInterfaceName != null) {
82 nativeReset(mInterfaceName);
83 mInterfaceName = null;
84 hideNotification();
85 // TODO: Send out a broadcast.
86 }
87
88 mPackageName = packageName;
89 Log.i(TAG, "Prepared for " + packageName);
90 return mPackageName;
91 }
92
93 /**
94 * Protect a socket from routing changes by binding it to the given
95 * interface. The socket is NOT closed by this method.
96 *
97 * @param socket The socket to be bound.
98 * @param name The name of the interface.
99 */
100 public void protect(ParcelFileDescriptor socket, String name) {
101 mContext.enforceCallingPermission(VPN, "protect");
102 nativeProtect(socket.getFd(), name);
103 }
104
105 /**
106 * Configure a TUN interface and return its file descriptor.
107 *
108 * @param configuration The parameters to configure the interface.
109 * @return The file descriptor of the interface.
110 */
111 public synchronized ParcelFileDescriptor establish(Bundle config) {
112 // Check the permission of the caller.
113 mContext.enforceCallingPermission(VPN, "establish");
114
115 // Check if the caller is already prepared.
116 PackageManager pm = mContext.getPackageManager();
117 ApplicationInfo app = null;
118 try {
119 app = pm.getApplicationInfo(mPackageName, 0);
120 } catch (Exception e) {
121 throw new SecurityException("Not prepared");
122 }
123 if (Binder.getCallingUid() != app.uid) {
124 throw new SecurityException("Not prepared");
125 }
126
127 // Unpack the config.
128 // TODO: move constants into VpnBuilder.
129 String session = config.getString("session");
130 String addresses = config.getString("addresses");
131 String routes = config.getString("routes");
132 String dnsServers = config.getString("dnsServers");
133
134 // Create interface and configure addresses and routes.
135 ParcelFileDescriptor descriptor = nativeConfigure(addresses, routes);
136
137 // Replace the interface and abort if it fails.
138 try {
139 String interfaceName = nativeGetName(descriptor.getFd());
140
141 if (mInterfaceName != null && !mInterfaceName.equals(interfaceName)) {
142 nativeReset(mInterfaceName);
143 }
144 mInterfaceName = interfaceName;
145 } catch (RuntimeException e) {
146 try {
147 descriptor.close();
148 } catch (Exception ex) {
149 // ignore
150 }
151 throw e;
152 }
153
154 dnsServers = (dnsServers == null) ? "" : dnsServers.trim();
155 mCallback.override(dnsServers.isEmpty() ? null : dnsServers.split(" "));
156
157 showNotification(pm, app, session);
158 return descriptor;
159 }
160
161 public synchronized boolean onInterfaceRemoved(String name) {
162 if (name.equals(mInterfaceName) && nativeCheck(name) == 0) {
163 hideNotification();
164 mInterfaceName = null;
165 return true;
166 }
167 return false;
168 }
169
170 // INetworkManagementEventObserver.Stub
171 public void interfaceLinkStatusChanged(String name, boolean up) {
172 }
173
174 // INetworkManagementEventObserver.Stub
175 public void interfaceAdded(String name) {
176 }
177
178 // INetworkManagementEventObserver.Stub
179 public synchronized void interfaceRemoved(String name) {
180 if (name.equals(mInterfaceName) && nativeCheck(name) == 0) {
181 hideNotification();
182 mInterfaceName = null;
183 mCallback.restore();
184 }
185 }
186
187 private void showNotification(PackageManager pm, ApplicationInfo app, String session) {
188 NotificationManager nm = (NotificationManager)
189 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
190
191 if (nm != null) {
192 // Load the icon and convert it into a bitmap.
193 Drawable icon = app.loadIcon(pm);
194 Bitmap bitmap = null;
195 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
196 int width = mContext.getResources().getDimensionPixelSize(
197 android.R.dimen.notification_large_icon_width);
198 int height = mContext.getResources().getDimensionPixelSize(
199 android.R.dimen.notification_large_icon_height);
200 icon.setBounds(0, 0, width, height);
201 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
202 icon.draw(new Canvas(bitmap));
203 }
204
205 // Load the label.
206 String label = app.loadLabel(pm).toString();
207
208 // If session is null, use the application name instead.
209 if (session == null) {
210 session = label;
211 }
212
213 // Build the intent.
214 // TODO: move these into VpnBuilder.
215 Intent intent = new Intent();
216 intent.setClassName("com.android.vpndialogs",
217 "com.android.vpndialogs.ManageDialog");
218 intent.putExtra("packageName", mPackageName);
219 intent.putExtra("interfaceName", mInterfaceName);
220 intent.putExtra("session", session);
221 intent.putExtra("startTime", android.os.SystemClock.elapsedRealtime());
222 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
223
224 // Build the notification.
225 long identity = Binder.clearCallingIdentity();
226 Notification notification = new Notification.Builder(mContext)
227 .setSmallIcon(R.drawable.vpn_connected)
228 .setLargeIcon(bitmap)
229 .setTicker(mContext.getString(R.string.vpn_ticker, label))
230 .setContentTitle(mContext.getString(R.string.vpn_title, label))
231 .setContentText(mContext.getString(R.string.vpn_text, session))
232 .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0))
233 .setDefaults(Notification.DEFAULT_ALL)
234 .setOngoing(true)
235 .getNotification();
236
237 nm.notify(R.drawable.vpn_connected, notification);
238 Binder.restoreCallingIdentity(identity);
239 }
240 }
241
242 private void hideNotification() {
243 NotificationManager nm = (NotificationManager)
244 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
245
246 if (nm != null) {
247 long identity = Binder.clearCallingIdentity();
248 nm.cancel(R.drawable.vpn_connected);
249 Binder.restoreCallingIdentity(identity);
250 }
251 }
252
253 private native ParcelFileDescriptor nativeConfigure(String addresses, String routes);
254 private native String nativeGetName(int fd);
255 private native void nativeReset(String name);
256 private native int nativeCheck(String name);
257 private native void nativeProtect(int fd, String name);
258}