de.flyingsnail.ipv6droid.android.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.flyingsnail.ipv6droid.android.MainActivity.java

Source

/*
 * 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&lt;TicTunnel&gt; 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();
        }
    }
}