Java tutorial
/* * Copyright (c) 2013 Dr. Andreas Feldner. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Contact information and current version at http://www.flying-snail.de/IPv6Droid */ package de.flyingsnail.ipv6droid.android; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; import android.net.VpnService; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import de.flyingsnail.ipv6droid.R; import de.flyingsnail.ipv6droid.ayiya.TicTunnel; /** * Main activity as generated by Android Studio, plus Code to start the service when user clicks. */ public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getName(); private static final int REQUEST_START_VPN = 1; private static final int REQUEST_SETTINGS = 2; private static final String FILE_LAST_TUNNEL = "last_tunnel"; private TextView activity; private ProgressBar progress; private ImageView status; private Button redundantStartButton; private ListView tunnelList; private SharedPreferences myPreferences; private TicTunnel selectedTunnel; /** * The Action name for a vpn stop broadcast intent. */ public static final String BC_STOP = MainActivity.class.getName() + ".STOP"; /** * The Action name for a status update request broadcast. */ public static final String BC_STATUS_UPDATE = MainActivity.class.getName() + ".STATUS_REQUEST"; private StatusReceiver statusReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myPreferences = PreferenceManager.getDefaultSharedPreferences(this); // init handles to GUI elements activity = (TextView) findViewById(R.id.statusText); progress = (ProgressBar) findViewById(R.id.progressBar); status = (ImageView) findViewById(R.id.statusImage); redundantStartButton = (Button) findViewById(R.id.redundant_start_button); tunnelList = (ListView) findViewById(R.id.tunnelList); // setup the intent filter for status broadcasts // The filter's action is BROADCAST_ACTION IntentFilter statusIntentFilter = new IntentFilter(VpnThread.BC_STATUS); if (statusReceiver == null) statusReceiver = new StatusReceiver(); // Registers the StatusReceiver and its intent filter LocalBroadcastManager.getInstance(this).registerReceiver(statusReceiver, statusIntentFilter); // check login configuration and start Settings if not yet set. if (myPreferences.getString("tic_username", "").isEmpty() || myPreferences.getString("tic_password", "").isEmpty() || myPreferences.getString("tic_host", "").isEmpty()) { openSettings(); } selectedTunnel = loadPersistedTunnel(); requestStatus(); } /** * Return the tunnel specifications from available tunnels that are suitable for this app. * @param tunnelIds the List of Strings containing tunnel IDs each * @param tic the connected Tic object * @return a List<TicTunnel> specifying the tunnel to build up * @throws IOException in case of a communication problem * private static List<TicTunnel> getSuitables(List<String> tunnelIds, Tic tic) throws IOException { List<TicTunnel> retval = new ArrayList<TicTunnel>(tunnelIds.size()); for (String id: tunnelIds) { TicTunnel desc = null; try { desc = tic.describeTunnel(id); } catch (TunnelNotAcceptedException e) { continue; } if (desc.isValid() && desc.isEnabled() && "ayiya".equals(desc.getType())){ Log.i(TAG, "Tunnel " + id + " is suitable"); retval.add(desc); } } return retval; }*/ @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** * Start the system-managed setup of VPN * @param view supplied by GUI invocation */ public void startVPN(View view) { // Start system-managed intent for VPN Intent systemVpnIntent = VpnService.prepare(getApplicationContext()); if (systemVpnIntent != null) { startActivityForResult(systemVpnIntent, REQUEST_START_VPN); } else { onActivityResult(REQUEST_START_VPN, RESULT_OK, null); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); requestStatus(); } @Override protected void onPause() { super.onPause(); TicTunnel tunnel = statusReceiver.getTunnel(); if (tunnel != null && statusReceiver.isTunnelProven()) { // we have a tunnel that should work writePersistedTunnel(tunnel); } } /** * Start the system-managed setup of VPN */ public void openSettings() { // Start system-managed intent for VPN Intent settingsIntent = new Intent(this, SettingsActivity.class); startActivityForResult(settingsIntent, REQUEST_SETTINGS); } /** * Stop the VPN service/thread. */ public void stopVPN() { onPause(); // functionally equivalent to a pause of our activity. Intent statusBroadcast = new Intent(BC_STOP); // Broadcast locally LocalBroadcastManager.getInstance(this).sendBroadcast(statusBroadcast); } /** * Broadcast a status update request. If there's a AyiyaVpnService out there, it will respond * with a standard status message. */ private void requestStatus() { Intent statusBroadcast = new Intent(BC_STATUS_UPDATE); // Broadcast locally LocalBroadcastManager.getInstance(this).sendBroadcast(statusBroadcast); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_START_VPN: if (resultCode == RESULT_OK) { Intent intent = new Intent(this, AyiyaVpnService.class); if (selectedTunnel != null) intent.putExtra(AyiyaVpnService.EXTRA_CACHED_TUNNEL, selectedTunnel); startService(intent); } break; } } /** Read from a private file. If no such file exists, fall back to currently active tunnel. */ private TicTunnel loadPersistedTunnel() { TicTunnel tunnel = statusReceiver.getTunnel(); try { // open private file InputStream is = openFileInput(FILE_LAST_TUNNEL); ObjectInputStream os = new ObjectInputStream(is); List<TicTunnel> tunnels = (List<TicTunnel>) os.readObject(); int selected = os.readInt(); tunnel = tunnels.get(selected); } catch (Exception e) { Log.e(TAG, "Could not retrieve saved state of TicTunnel", e); } return tunnel; } /** Write to a private file. Format is: ArrayList<TicTunnel> tunnels; int selected */ private void writePersistedTunnel(TicTunnel tunnel) { try { OutputStream fs = openFileOutput(FILE_LAST_TUNNEL, MODE_PRIVATE); ObjectOutputStream os = new ObjectOutputStream(fs); List<TicTunnel> tunnelList = new ArrayList<TicTunnel>(1); tunnelList.add(tunnel); os.writeObject(tunnelList); os.writeInt(0); os.close(); fs.close(); } catch (IOException e) { Log.e(TAG, "Could not write last working tunnel to preferences", e); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_connect: startVPN(item.getActionView()); return true; case R.id.action_disconnect: stopVPN(); return true; case R.id.action_settings: openSettings(); return true; case R.id.action_help: openHelp(); return true; default: return false; } } private void openHelp() { Intent helpIntent = new Intent(Intent.ACTION_VIEW); helpIntent.setDataAndType(Uri.parse("https://sourceforge.net/p/ipv6droid/wiki/Home/"), "text/html"); helpIntent.addCategory(Intent.CATEGORY_BROWSABLE); startActivity(helpIntent); } /** Inner class to handle status updates */ private class StatusReceiver extends BroadcastReceiver { private VpnStatusReport statusReport = new VpnStatusReport(); public TicTunnel getTunnel() { return statusReport.getActiveTunnel(); } private StatusReceiver() { updateUi(); } public boolean isTunnelProven() { return statusReport.isTunnelProvedWorking(); } /* @todo move this to a new class public void setTunnel(TicTunnel tunnel) { this.tunnel = tunnel; if (tunnel == null) return; if (availableTunnels == null) availableTunnels = new ArrayList<TicTunnel>(1); if (!availableTunnels.contains(tunnel)) { availableTunnels.add(0, tunnel); tunnelList.setSelection(0); } else { tunnelList.setSelection(availableTunnels.indexOf(tunnel)); } } public void setAvailableTunnels(List<TicTunnel> availableTunnels) { this.availableTunnels = availableTunnels; if (tunnel != null && !availableTunnels.contains(tunnel)) { this.availableTunnels.add(0, tunnel); } } private class TunnelListAdapter extends ArrayAdapter<TicConfiguration> { public TunnelListAdapter(Context context, int textViewResourceId, TicConfiguration[] objects) { super(context, textViewResourceId, objects); } }*/ private void updateUi() { int imageRes = R.drawable.off; VpnStatusReport.Status status = statusReport.getStatus(); if (status != null) { switch (status) { case Connected: imageRes = R.drawable.transmitting; break; case Idle: imageRes = R.drawable.off; break; case Connecting: imageRes = R.drawable.pending; break; case Disturbed: imageRes = R.drawable.disturbed; break; } MainActivity.this.status.setImageResource(imageRes); } if (statusReport.getProgressPerCent() > 0) { MainActivity.this.progress.setIndeterminate(false); MainActivity.this.progress.setProgress(statusReport.getProgressPerCent()); } else MainActivity.this.progress.setIndeterminate(true); if (status == null || status == VpnStatusReport.Status.Idle) { redundantStartButton.setVisibility(View.VISIBLE); MainActivity.this.progress.setVisibility(View.INVISIBLE); MainActivity.this.activity.setVisibility(View.INVISIBLE); } else { redundantStartButton.setVisibility(View.INVISIBLE); MainActivity.this.progress.setVisibility(View.VISIBLE); MainActivity.this.activity.setVisibility(View.VISIBLE); } // show activity text if (statusReport.getActivity() != 0) MainActivity.this.activity.setText(getResources().getString(statusReport.getActivity())); // show tunnel information // @todo implementation is too cheap - no internationalization, etc. Necessary to generate custom Adapter. // @todo extend to an actual list as soon as we support that. if (getTunnel() != null) { tunnelList.setVisibility(View.VISIBLE); TicTunnel[] tunnelArray = new TicTunnel[] { getTunnel() }; tunnelList.setAdapter(new ArrayAdapter<TicTunnel>(MainActivity.this, R.layout.tunnellist_template, R.id.listEntry, tunnelArray)); tunnelList.setSelection(0); } } @Override public void onReceive(Context context, Intent intent) { statusReport = (VpnStatusReport) intent.getSerializableExtra(VpnThread.EDATA_STATUS_REPORT); updateUi(); } } }