android: Show an error if VPN fails due to lock down mode in Android 4.2
[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.DialogInterface;
35 import android.content.Intent;
36 import android.net.VpnService;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.view.LayoutInflater;
40 import android.view.Menu;
41 import android.view.MenuItem;
42 import android.view.View;
43 import android.view.Window;
44 import android.widget.EditText;
45
46 public class MainActivity extends Activity implements OnVpnProfileSelectedListener
47 {
48 public static final String CONTACT_EMAIL = "android@strongswan.org";
49 private static final int PREPARE_VPN_SERVICE = 0;
50
51 private Bundle mProfileInfo;
52
53 @Override
54 public void onCreate(Bundle savedInstanceState)
55 {
56 super.onCreate(savedInstanceState);
57 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
58 setContentView(R.layout.main);
59
60 ActionBar bar = getActionBar();
61 bar.setDisplayShowTitleEnabled(false);
62
63 /* load CA certificates in a background task */
64 new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false);
65 }
66
67 @Override
68 public boolean onCreateOptionsMenu(Menu menu)
69 {
70 getMenuInflater().inflate(R.menu.main, menu);
71 return true;
72 }
73
74 @Override
75 public boolean onOptionsItemSelected(MenuItem item)
76 {
77 switch (item.getItemId())
78 {
79 case R.id.menu_reload_certs:
80 new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
81 return true;
82 case R.id.menu_show_log:
83 Intent logIntent = new Intent(this, LogActivity.class);
84 startActivity(logIntent);
85 return true;
86 default:
87 return super.onOptionsItemSelected(item);
88 }
89 }
90
91 /**
92 * Prepare the VpnService. If this succeeds the current VPN profile is
93 * started.
94 * @param profileInfo a bundle containing the information about the profile to be started
95 */
96 protected void prepareVpnService(Bundle profileInfo)
97 {
98 Intent intent;
99 try
100 {
101 intent = VpnService.prepare(this);
102 }
103 catch (IllegalStateException ex)
104 {
105 /* this happens if the always-on VPN feature (Android 4.2+) is activated */
106 VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown);
107 return;
108 }
109 /* store profile info until the user grants us permission */
110 mProfileInfo = profileInfo;
111 if (intent != null)
112 {
113 try
114 {
115 startActivityForResult(intent, PREPARE_VPN_SERVICE);
116 }
117 catch (ActivityNotFoundException ex)
118 {
119 /* it seems some devices, even though they come with Android 4,
120 * don't have the VPN components built into the system image.
121 * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
122 * will not be found then */
123 VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported);
124 }
125 }
126 else
127 { /* user already granted permission to use VpnService */
128 onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
129 }
130 }
131
132 @Override
133 protected void onActivityResult(int requestCode, int resultCode, Intent data)
134 {
135 switch (requestCode)
136 {
137 case PREPARE_VPN_SERVICE:
138 if (resultCode == RESULT_OK && mProfileInfo != null)
139 {
140 Intent intent = new Intent(this, CharonVpnService.class);
141 intent.putExtras(mProfileInfo);
142 this.startService(intent);
143 }
144 break;
145 default:
146 super.onActivityResult(requestCode, resultCode, data);
147 }
148 }
149
150 @Override
151 public void onVpnProfileSelected(VpnProfile profile)
152 {
153 Bundle profileInfo = new Bundle();
154 profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
155 profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
156 if (profile.getVpnType().getRequiresUsernamePassword() &&
157 profile.getPassword() == null)
158 {
159 LoginDialog login = new LoginDialog();
160 login.setArguments(profileInfo);
161 login.show(getFragmentManager(), "LoginDialog");
162 }
163 else
164 {
165 profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
166 prepareVpnService(profileInfo);
167 }
168 }
169
170 /**
171 * Class that loads or reloads the cached CA certificates.
172 */
173 private class CertificateLoadTask extends AsyncTask<Boolean, Void, TrustedCertificateManager>
174 {
175 @Override
176 protected void onPreExecute()
177 {
178 setProgressBarIndeterminateVisibility(true);
179 }
180 @Override
181 protected TrustedCertificateManager doInBackground(Boolean... params)
182 {
183 if (params.length > 0 && params[0])
184 { /* force a reload of the certificates */
185 return TrustedCertificateManager.getInstance().reload();
186 }
187 return TrustedCertificateManager.getInstance().load();
188 }
189 @Override
190 protected void onPostExecute(TrustedCertificateManager result)
191 {
192 setProgressBarIndeterminateVisibility(false);
193 }
194 }
195
196 /**
197 * Class that displays a login dialog and initiates the selected VPN
198 * profile if the user confirms the dialog.
199 */
200 public static class LoginDialog extends DialogFragment
201 {
202 @Override
203 public Dialog onCreateDialog(Bundle savedInstanceState)
204 {
205 final Bundle profileInfo = getArguments();
206 LayoutInflater inflater = getActivity().getLayoutInflater();
207 View view = inflater.inflate(R.layout.login_dialog, null);
208 EditText username = (EditText)view.findViewById(R.id.username);
209 username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
210 final EditText password = (EditText)view.findViewById(R.id.password);
211
212 Builder adb = new AlertDialog.Builder(getActivity());
213 adb.setView(view);
214 adb.setTitle(getString(R.string.login_title));
215 adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() {
216 @Override
217 public void onClick(DialogInterface dialog, int whichButton)
218 {
219 MainActivity activity = (MainActivity)getActivity();
220 profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
221 activity.prepareVpnService(profileInfo);
222 }
223 });
224 adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
225 @Override
226 public void onClick(DialogInterface dialog, int which)
227 {
228 dismiss();
229 }
230 });
231 return adb.create();
232 }
233 }
234
235 /**
236 * Class representing an error message which is displayed if VpnService is
237 * not supported on the current device.
238 */
239 public static class VpnNotSupportedError extends DialogFragment
240 {
241 static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId";
242
243 public static void showWithMessage(Activity activity, int messageId)
244 {
245 Bundle bundle = new Bundle();
246 bundle.putInt(ERROR_MESSAGE_ID, messageId);
247 VpnNotSupportedError dialog = new VpnNotSupportedError();
248 dialog.setArguments(bundle);
249 dialog.show(activity.getFragmentManager(), "ErrorDialog");
250 }
251
252 @Override
253 public Dialog onCreateDialog(Bundle savedInstanceState)
254 {
255 final Bundle arguments = getArguments();
256 final int messageId = arguments.getInt(ERROR_MESSAGE_ID);
257 return new AlertDialog.Builder(getActivity())
258 .setTitle(R.string.vpn_not_supported_title)
259 .setMessage(messageId)
260 .setCancelable(false)
261 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
262 @Override
263 public void onClick(DialogInterface dialog, int id)
264 {
265 dialog.dismiss();
266 }
267 }).create();
268 }
269 }
270 }