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.example.android.toyvpn; 18 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.Intent; 24 import android.content.SharedPreferences; 25 import android.net.VpnService; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.ParcelFileDescriptor; 29 import android.util.Log; 30 import android.util.Pair; 31 import android.widget.Toast; 32 33 import java.io.IOException; 34 import java.util.Collections; 35 import java.util.Set; 36 import java.util.concurrent.atomic.AtomicInteger; 37 import java.util.concurrent.atomic.AtomicReference; 38 39 public class ToyVpnService extends VpnService implements Handler.Callback { 40 private static final String TAG = ToyVpnService.class.getSimpleName(); 41 42 public static final String ACTION_CONNECT = "com.example.android.toyvpn.START"; 43 public static final String ACTION_DISCONNECT = "com.example.android.toyvpn.STOP"; 44 45 private Handler mHandler; 46 47 private static class Connection extends Pair<Thread, ParcelFileDescriptor> { Connection(Thread thread, ParcelFileDescriptor pfd)48 public Connection(Thread thread, ParcelFileDescriptor pfd) { 49 super(thread, pfd); 50 } 51 } 52 53 private final AtomicReference<Thread> mConnectingThread = new AtomicReference<>(); 54 private final AtomicReference<Connection> mConnection = new AtomicReference<>(); 55 56 private AtomicInteger mNextConnectionId = new AtomicInteger(1); 57 58 private PendingIntent mConfigureIntent; 59 60 @Override onCreate()61 public void onCreate() { 62 // The handler is only used to show messages. 63 if (mHandler == null) { 64 mHandler = new Handler(this); 65 } 66 67 // Create the intent to "configure" the connection (just start ToyVpnClient). 68 mConfigureIntent = PendingIntent.getActivity(this, 0, new Intent(this, ToyVpnClient.class), 69 PendingIntent.FLAG_UPDATE_CURRENT); 70 } 71 72 @Override onStartCommand(Intent intent, int flags, int startId)73 public int onStartCommand(Intent intent, int flags, int startId) { 74 if (intent != null && ACTION_DISCONNECT.equals(intent.getAction())) { 75 disconnect(); 76 return START_NOT_STICKY; 77 } else { 78 connect(); 79 return START_STICKY; 80 } 81 } 82 83 @Override onDestroy()84 public void onDestroy() { 85 disconnect(); 86 } 87 88 @Override handleMessage(Message message)89 public boolean handleMessage(Message message) { 90 Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show(); 91 if (message.what != R.string.disconnected) { 92 updateForegroundNotification(message.what); 93 } 94 return true; 95 } 96 connect()97 private void connect() { 98 // Become a foreground service. Background services can be VPN services too, but they can 99 // be killed by background check before getting a chance to receive onRevoke(). 100 updateForegroundNotification(R.string.connecting); 101 mHandler.sendEmptyMessage(R.string.connecting); 102 103 // Extract information from the shared preferences. 104 final SharedPreferences prefs = getSharedPreferences(ToyVpnClient.Prefs.NAME, MODE_PRIVATE); 105 final String server = prefs.getString(ToyVpnClient.Prefs.SERVER_ADDRESS, ""); 106 final byte[] secret = prefs.getString(ToyVpnClient.Prefs.SHARED_SECRET, "").getBytes(); 107 final boolean allow = prefs.getBoolean(ToyVpnClient.Prefs.ALLOW, true); 108 final Set<String> packages = 109 prefs.getStringSet(ToyVpnClient.Prefs.PACKAGES, Collections.emptySet()); 110 final int port = prefs.getInt(ToyVpnClient.Prefs.SERVER_PORT, 0); 111 final String proxyHost = prefs.getString(ToyVpnClient.Prefs.PROXY_HOSTNAME, ""); 112 final int proxyPort = prefs.getInt(ToyVpnClient.Prefs.PROXY_PORT, 0); 113 startConnection(new ToyVpnConnection( 114 this, mNextConnectionId.getAndIncrement(), server, port, secret, 115 proxyHost, proxyPort, allow, packages)); 116 } 117 startConnection(final ToyVpnConnection connection)118 private void startConnection(final ToyVpnConnection connection) { 119 // Replace any existing connecting thread with the new one. 120 final Thread thread = new Thread(connection, "ToyVpnThread"); 121 setConnectingThread(thread); 122 123 // Handler to mark as connected once onEstablish is called. 124 connection.setConfigureIntent(mConfigureIntent); 125 connection.setOnEstablishListener(tunInterface -> { 126 mHandler.sendEmptyMessage(R.string.connected); 127 128 mConnectingThread.compareAndSet(thread, null); 129 setConnection(new Connection(thread, tunInterface)); 130 }); 131 thread.start(); 132 } 133 setConnectingThread(final Thread thread)134 private void setConnectingThread(final Thread thread) { 135 final Thread oldThread = mConnectingThread.getAndSet(thread); 136 if (oldThread != null) { 137 oldThread.interrupt(); 138 } 139 } 140 setConnection(final Connection connection)141 private void setConnection(final Connection connection) { 142 final Connection oldConnection = mConnection.getAndSet(connection); 143 if (oldConnection != null) { 144 try { 145 oldConnection.first.interrupt(); 146 oldConnection.second.close(); 147 } catch (IOException e) { 148 Log.e(TAG, "Closing VPN interface", e); 149 } 150 } 151 } 152 disconnect()153 private void disconnect() { 154 mHandler.sendEmptyMessage(R.string.disconnected); 155 setConnectingThread(null); 156 setConnection(null); 157 stopForeground(true); 158 } 159 updateForegroundNotification(final int message)160 private void updateForegroundNotification(final int message) { 161 final String NOTIFICATION_CHANNEL_ID = "ToyVpn"; 162 NotificationManager mNotificationManager = (NotificationManager) getSystemService( 163 NOTIFICATION_SERVICE); 164 mNotificationManager.createNotificationChannel(new NotificationChannel( 165 NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, 166 NotificationManager.IMPORTANCE_DEFAULT)); 167 startForeground(1, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) 168 .setSmallIcon(R.drawable.ic_vpn) 169 .setContentText(getString(message)) 170 .setContentIntent(mConfigureIntent) 171 .build()); 172 } 173 } 174