bc5030ea5cc8942ea93a7caf975018b15024f1b8
[strongswan.git] / src / frontends / android / src / org / strongswan / android / ui / MainActivity.java
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.ui;
19
20 import org.strongswan.android.R;
21 import org.strongswan.android.data.VpnProfile;
22 import org.strongswan.android.data.VpnProfileDataSource;
23 import org.strongswan.android.logic.CharonVpnService;
24 import org.strongswan.android.logic.TrustedCertificateManager;
25 import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
26
27 import android.app.ActionBar;
28 import android.app.Activity;
29 import android.app.AlertDialog;
30 import android.app.AlertDialog.Builder;
31 import android.app.Dialog;
32 import android.app.DialogFragment;
33 import android.content.ActivityNotFoundException;
34 import android.content.Context;
35 import android.content.DialogInterface;
36 import android.content.Intent;
37 import android.net.VpnService;
38 import android.os.AsyncTask;
39 import android.os.Bundle;
40 import android.view.LayoutInflater;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.Window;
45 import android.widget.EditText;
46
47 public class MainActivity extends Activity implements OnVpnProfileSelectedListener
48 {
49 public static final String CONTACT_EMAIL = "android@strongswan.org";
50 private static final String SHOW_ERROR_DIALOG = "errordialog";
51 private static final int PREPARE_VPN_SERVICE = 0;
52
53 private VpnProfile activeProfile;
54 private AlertDialog mErrorDialog;
55
56 @Override
57 public void onCreate(Bundle savedInstanceState)
58 {
59 super.onCreate(savedInstanceState);
60 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
61 setContentView(R.layout.main);
62
63 ActionBar bar = getActionBar();
64 bar.setDisplayShowTitleEnabled(false);
65
66 if (savedInstanceState != null && savedInstanceState.getBoolean(SHOW_ERROR_DIALOG))
67 {
68 showVpnNotSupportedError();
69 }
70
71 /* load CA certificates in a background task */
72 new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false);
73 }
74
75 @Override
76 protected void onSaveInstanceState(Bundle outState)
77 {
78 super.onSaveInstanceState(outState);
79 outState.putBoolean(SHOW_ERROR_DIALOG, mErrorDialog != null);
80 }
81
82 @Override
83 protected void onDestroy()
84 {
85 super.onDestroy();
86 if (mErrorDialog != null)
87 { /* avoid any errors about leaked windows */
88 mErrorDialog.dismiss();
89 }
90 }
91
92 @Override
93 public boolean onCreateOptionsMenu(Menu menu)
94 {
95 getMenuInflater().inflate(R.menu.main, menu);
96 return true;
97 }
98
99 @Override
100 public boolean onOptionsItemSelected(MenuItem item)
101 {
102 switch (item.getItemId())
103 {
104 case R.id.menu_reload_certs:
105 new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
106 return true;
107 case R.id.menu_show_log:
108 Intent logIntent = new Intent(this, LogActivity.class);
109 startActivity(logIntent);
110 return true;
111 default:
112 return super.onOptionsItemSelected(item);
113 }
114 }
115
116 /**
117 * Prepare the VpnService. If this succeeds the current VPN profile is
118 * started.
119 */
120 protected void prepareVpnService()
121 {
122 Intent intent = VpnService.prepare(this);
123 if (intent != null)
124 {
125 try
126 {
127 startActivityForResult(intent, PREPARE_VPN_SERVICE);
128 }
129 catch (ActivityNotFoundException ex)
130 {
131 /* it seems some devices, even though they come with Android 4,
132 * don't have the VPN components built into the system image.
133 * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
134 * will not be found then */
135 showVpnNotSupportedError();
136 }
137 }
138 else
139 {
140 onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
141 }
142 }
143
144 @Override
145 protected void onActivityResult(int requestCode, int resultCode, Intent data)
146 {
147 switch (requestCode)
148 {
149 case PREPARE_VPN_SERVICE:
150 if (resultCode == RESULT_OK && activeProfile != null)
151 {
152 Intent intent = new Intent(this, CharonVpnService.class);
153 intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId());
154 /* submit the password as the profile might not store one */
155 intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword());
156 this.startService(intent);
157 }
158 break;
159 default:
160 super.onActivityResult(requestCode, resultCode, data);
161 }
162 }
163
164 @Override
165 public void onVpnProfileSelected(VpnProfile profile)
166 {
167 activeProfile = profile;
168 if (activeProfile.getPassword() == null)
169 {
170 new LoginDialog().show(getFragmentManager(), "LoginDialog");
171 }
172 else
173 {
174 prepareVpnService();
175 }
176 }
177
178 /**
179 * Show an error dialog if case the device lacks VPN support.
180 */
181 private void showVpnNotSupportedError()
182 {
183 mErrorDialog = new AlertDialog.Builder(this)
184 .setTitle(R.string.vpn_not_supported_title)
185 .setMessage(getString(R.string.vpn_not_supported))
186 .setCancelable(false)
187 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
188 @Override
189 public void onClick(DialogInterface dialog, int id)
190 {
191 mErrorDialog = null;
192 dialog.dismiss();
193 }
194 }).show();
195 }
196
197 /**
198 * Class that loads or reloads the cached CA certificates.
199 */
200 private class CertificateLoadTask extends AsyncTask<Boolean, Void, TrustedCertificateManager>
201 {
202 @Override
203 protected void onPreExecute()
204 {
205 setProgressBarIndeterminateVisibility(true);
206 }
207 @Override
208 protected TrustedCertificateManager doInBackground(Boolean... params)
209 {
210 if (params.length > 0 && params[0])
211 { /* force a reload of the certificates */
212 return TrustedCertificateManager.getInstance().reload();
213 }
214 return TrustedCertificateManager.getInstance().load();
215 }
216 @Override
217 protected void onPostExecute(TrustedCertificateManager result)
218 {
219 setProgressBarIndeterminateVisibility(false);
220 }
221 }
222
223 private class LoginDialog extends DialogFragment
224 {
225 @Override
226 public Dialog onCreateDialog(Bundle savedInstanceState)
227 {
228 LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
229 View view = inflater.inflate(R.layout.login_dialog, null);
230 EditText username = (EditText)view.findViewById(R.id.username);
231 username.setText(activeProfile.getUsername());
232 final EditText password = (EditText)view.findViewById(R.id.password);
233
234 Builder adb = new AlertDialog.Builder(MainActivity.this);
235 adb.setView(view);
236 adb.setTitle(getString(R.string.login_title));
237 adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() {
238 @Override
239 public void onClick(DialogInterface dialog, int whichButton)
240 {
241 /* let's work on a clone of the profile when updating the password */
242 activeProfile = activeProfile.clone();
243 activeProfile.setPassword(password.getText().toString().trim());
244 prepareVpnService();
245 }
246 });
247 adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
248 @Override
249 public void onClick(DialogInterface dialog, int which)
250 {
251 dismiss();
252 }
253 });
254 return adb.create();
255 }
256 }
257 }