Chia-chi Yeh | ff3bdca | 2011-05-23 17:26:46 -0700 | [diff] [blame^] | 1 | /* |
| 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 | |
| 17 | package com.android.server.connectivity; |
| 18 | |
| 19 | import android.app.Notification; |
| 20 | import android.app.NotificationManager; |
| 21 | import android.app.PendingIntent; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.pm.ApplicationInfo; |
| 25 | import android.content.pm.PackageManager; |
| 26 | import android.content.res.Resources; |
| 27 | import android.graphics.Bitmap; |
| 28 | import android.graphics.Canvas; |
| 29 | import android.graphics.drawable.Drawable; |
| 30 | import android.net.INetworkManagementEventObserver; |
| 31 | import android.os.Binder; |
| 32 | import android.os.Bundle; |
| 33 | import android.os.ParcelFileDescriptor; |
| 34 | import android.os.RemoteException; |
| 35 | import android.util.Log; |
| 36 | |
| 37 | import com.android.internal.R; |
| 38 | import com.android.server.ConnectivityService.VpnCallback; |
| 39 | |
| 40 | /** |
| 41 | * @hide |
| 42 | */ |
| 43 | public 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 | } |