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.Activity;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.net.VpnService;
23 import android.os.Bundle;
24 import android.widget.RadioButton;
25 import android.widget.TextView;
26 import android.widget.Toast;
27 
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Set;
31 import java.util.stream.Collectors;
32 
33 public class ToyVpnClient extends Activity {
34     public interface Prefs {
35         String NAME = "connection";
36         String SERVER_ADDRESS = "server.address";
37         String SERVER_PORT = "server.port";
38         String SHARED_SECRET = "shared.secret";
39         String PROXY_HOSTNAME = "proxyhost";
40         String PROXY_PORT = "proxyport";
41         String ALLOW = "allow";
42         String PACKAGES = "packages";
43     }
44 
45     @Override
onCreate(Bundle savedInstanceState)46     public void onCreate(Bundle savedInstanceState) {
47         super.onCreate(savedInstanceState);
48         setContentView(R.layout.form);
49 
50         final TextView serverAddress = findViewById(R.id.address);
51         final TextView serverPort = findViewById(R.id.port);
52         final TextView sharedSecret = findViewById(R.id.secret);
53         final TextView proxyHost = findViewById(R.id.proxyhost);
54         final TextView proxyPort = findViewById(R.id.proxyport);
55 
56         final RadioButton allowed = findViewById(R.id.allowed);
57         final TextView packages = findViewById(R.id.packages);
58 
59         final SharedPreferences prefs = getSharedPreferences(Prefs.NAME, MODE_PRIVATE);
60         serverAddress.setText(prefs.getString(Prefs.SERVER_ADDRESS, ""));
61         int serverPortPrefValue = prefs.getInt(Prefs.SERVER_PORT, 0);
62         serverPort.setText(String.valueOf(serverPortPrefValue == 0 ? "" : serverPortPrefValue));
63         sharedSecret.setText(prefs.getString(Prefs.SHARED_SECRET, ""));
64         proxyHost.setText(prefs.getString(Prefs.PROXY_HOSTNAME, ""));
65         int proxyPortPrefValue = prefs.getInt(Prefs.PROXY_PORT, 0);
66         proxyPort.setText(proxyPortPrefValue == 0 ? "" : String.valueOf(proxyPortPrefValue));
67 
68         allowed.setChecked(prefs.getBoolean(Prefs.ALLOW, true));
69         packages.setText(String.join(", ", prefs.getStringSet(
70                 Prefs.PACKAGES, Collections.emptySet())));
71 
72         findViewById(R.id.connect).setOnClickListener(v -> {
73             if (!checkProxyConfigs(proxyHost.getText().toString(),
74                     proxyPort.getText().toString())) {
75                 return;
76             }
77 
78             final Set<String> packageSet =
79                     Arrays.stream(packages.getText().toString().split(","))
80                             .map(String::trim)
81                             .filter(s -> !s.isEmpty())
82                             .collect(Collectors.toSet());
83             if (!checkPackages(packageSet)) {
84                 return;
85             }
86 
87             int serverPortNum;
88             try {
89                 serverPortNum = Integer.parseInt(serverPort.getText().toString());
90             } catch (NumberFormatException e) {
91                 serverPortNum = 0;
92             }
93             int proxyPortNum;
94             try {
95                 proxyPortNum = Integer.parseInt(proxyPort.getText().toString());
96             } catch (NumberFormatException e) {
97                 proxyPortNum = 0;
98             }
99             prefs.edit()
100                     .putString(Prefs.SERVER_ADDRESS, serverAddress.getText().toString())
101                     .putInt(Prefs.SERVER_PORT, serverPortNum)
102                     .putString(Prefs.SHARED_SECRET, sharedSecret.getText().toString())
103                     .putString(Prefs.PROXY_HOSTNAME, proxyHost.getText().toString())
104                     .putInt(Prefs.PROXY_PORT, proxyPortNum)
105                     .putBoolean(Prefs.ALLOW, allowed.isChecked())
106                     .putStringSet(Prefs.PACKAGES, packageSet)
107                     .commit();
108             Intent intent = VpnService.prepare(ToyVpnClient.this);
109             if (intent != null) {
110                 startActivityForResult(intent, 0);
111             } else {
112                 onActivityResult(0, RESULT_OK, null);
113             }
114         });
115         findViewById(R.id.disconnect).setOnClickListener(v -> {
116             startService(getServiceIntent().setAction(ToyVpnService.ACTION_DISCONNECT));
117         });
118     }
119 
checkProxyConfigs(String proxyHost, String proxyPort)120     private boolean checkProxyConfigs(String proxyHost, String proxyPort) {
121         final boolean hasIncompleteProxyConfigs = proxyHost.isEmpty() != proxyPort.isEmpty();
122         if (hasIncompleteProxyConfigs) {
123             Toast.makeText(this, R.string.incomplete_proxy_settings, Toast.LENGTH_SHORT).show();
124         }
125         return !hasIncompleteProxyConfigs;
126     }
127 
checkPackages(Set<String> packageNames)128     private boolean checkPackages(Set<String> packageNames) {
129         final boolean hasCorrectPackageNames = packageNames.isEmpty() ||
130                 getPackageManager().getInstalledPackages(0).stream()
131                         .map(pi -> pi.packageName)
132                         .collect(Collectors.toSet())
133                         .containsAll(packageNames);
134         if (!hasCorrectPackageNames) {
135             Toast.makeText(this, R.string.unknown_package_names, Toast.LENGTH_SHORT).show();
136         }
137         return hasCorrectPackageNames;
138     }
139 
140     @Override
onActivityResult(int request, int result, Intent data)141     protected void onActivityResult(int request, int result, Intent data) {
142         if (result == RESULT_OK) {
143             startService(getServiceIntent().setAction(ToyVpnService.ACTION_CONNECT));
144         }
145     }
146 
getServiceIntent()147     private Intent getServiceIntent() {
148         return new Intent(this, ToyVpnService.class);
149     }
150 }
151