2010-02-08 20:39:42 +00:00
|
|
|
/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
|
|
|
|
/* See LICENSE for licensing information */
|
|
|
|
|
|
|
|
package org.torproject.android;
|
|
|
|
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
|
|
|
|
import org.torproject.android.service.ITorService;
|
|
|
|
import org.torproject.android.service.ITorServiceCallback;
|
2010-02-27 23:56:46 +00:00
|
|
|
import org.torproject.android.service.TorTransProxy;
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
import android.app.Activity;
|
|
|
|
import android.app.AlertDialog;
|
2010-07-19 22:34:15 +00:00
|
|
|
import android.app.NotificationManager;
|
2010-02-08 20:39:42 +00:00
|
|
|
import android.app.ProgressDialog;
|
|
|
|
import android.content.ComponentName;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.DialogInterface;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.ServiceConnection;
|
|
|
|
import android.content.SharedPreferences;
|
2010-07-28 04:43:58 +00:00
|
|
|
import android.content.SharedPreferences.Editor;
|
2010-02-08 20:39:42 +00:00
|
|
|
import android.net.Uri;
|
|
|
|
import android.os.Bundle;
|
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.IBinder;
|
|
|
|
import android.os.Message;
|
|
|
|
import android.os.RemoteException;
|
|
|
|
import android.preference.PreferenceManager;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.view.KeyEvent;
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuItem;
|
|
|
|
import android.view.View;
|
|
|
|
import android.view.View.OnClickListener;
|
2010-07-23 11:10:00 +00:00
|
|
|
import android.view.animation.Animation;
|
|
|
|
import android.view.animation.AnimationUtils;
|
2010-07-28 04:43:58 +00:00
|
|
|
import android.widget.Button;
|
2010-02-27 23:56:46 +00:00
|
|
|
import android.widget.CheckBox;
|
2010-07-28 04:43:58 +00:00
|
|
|
import android.widget.CompoundButton;
|
|
|
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
2010-02-08 20:39:42 +00:00
|
|
|
import android.widget.ImageView;
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
public class Orbot extends Activity implements OnClickListener, TorConstants
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
/* Useful UI bits */
|
|
|
|
private TextView txtMessageLog = null; //the full screen log view of Tor control messages
|
|
|
|
private TextView lblStatus = null; //the main text display widget
|
|
|
|
private ImageView imgStatus = null; //the main touchable image for activating Orbot
|
2010-07-23 11:10:00 +00:00
|
|
|
// private ProgressDialog progressDialog;
|
2010-03-18 03:15:20 +00:00
|
|
|
private MenuItem mItemOnOff = null;
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
/* Some tracking bits */
|
2010-02-27 23:56:46 +00:00
|
|
|
private int torStatus = STATUS_READY; //latest status reported from the tor service
|
2010-02-08 20:39:42 +00:00
|
|
|
private int currentView = 0; //the currently displayed UI view
|
|
|
|
private StringBuffer logBuffer = new StringBuffer(); //the output of the service log messages
|
|
|
|
|
|
|
|
/* Tor Service interaction */
|
|
|
|
/* The primary interface we will be calling on the service. */
|
|
|
|
ITorService mService = null;
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
Orbot mOrbot = null;
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/** Called when the activity is first created. */
|
|
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
mOrbot = this;
|
|
|
|
|
2010-02-24 05:09:24 +00:00
|
|
|
setTheme(android.R.style.Theme_Black_NoTitleBar);
|
|
|
|
//setTitle(getString(R.string.app_name) + ' ' + getString(R.string.app_version));
|
2010-02-08 20:39:42 +00:00
|
|
|
showMain();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the UI Options Menu (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
|
|
|
|
*/
|
|
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
|
|
super.onCreateOptionsMenu(menu);
|
|
|
|
|
|
|
|
MenuItem mItem = null;
|
|
|
|
|
2010-03-18 03:15:20 +00:00
|
|
|
mItemOnOff = menu.add(0, 1, Menu.NONE, getString(R.string.menu_start));
|
|
|
|
mItemOnOff.setIcon(android.R.drawable.ic_menu_share);
|
2010-07-19 22:34:15 +00:00
|
|
|
mItemOnOff.setAlphabeticShortcut('t');
|
2010-02-08 20:39:42 +00:00
|
|
|
|
2010-02-27 23:56:46 +00:00
|
|
|
mItem = menu.add(0, 4, Menu.NONE, getString(R.string.menu_settings));
|
2010-02-08 20:39:42 +00:00
|
|
|
mItem.setIcon(R.drawable.ic_menu_register);
|
2010-02-27 23:56:46 +00:00
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
mItem = menu.add(0, 7, Menu.NONE, getString(R.string.menu_verify));
|
2010-07-23 11:10:00 +00:00
|
|
|
mItem.setIcon(R.drawable.ic_menu_check);
|
2010-07-19 22:34:15 +00:00
|
|
|
|
2010-02-27 23:56:46 +00:00
|
|
|
mItem = menu.add(0,6, Menu.NONE, getString(R.string.menu_log));
|
2010-02-08 20:39:42 +00:00
|
|
|
mItem.setIcon(R.drawable.ic_menu_reports);
|
2010-03-01 05:52:44 +00:00
|
|
|
|
|
|
|
mItem = menu.add(0, 3, Menu.NONE, getString(R.string.menu_info));
|
|
|
|
mItem.setIcon(R.drawable.ic_menu_about);
|
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
mItem = menu.add(0, 8, Menu.NONE, getString(R.string.menu_exit));
|
|
|
|
mItem.setIcon(R.drawable.ic_menu_exit);
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When a menu item is selected launch the appropriate view or activity
|
|
|
|
* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onMenuItemSelected(int, android.view.MenuItem)
|
|
|
|
*/
|
|
|
|
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
|
|
|
|
|
|
|
super.onMenuItemSelected(featureId, item);
|
|
|
|
|
|
|
|
if (item.getItemId() == 1)
|
|
|
|
{
|
2010-03-18 03:15:20 +00:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
|
|
|
|
if (mService == null)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (mService.getStatus() == STATUS_READY)
|
|
|
|
{
|
|
|
|
mItemOnOff.setTitle(R.string.menu_stop);
|
|
|
|
startTor();
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mItemOnOff.setTitle(R.string.menu_start);
|
|
|
|
stopTor();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
catch (RemoteException re)
|
|
|
|
{
|
|
|
|
Log.w(TAG, "Unable to start/top Tor from menu UI", re);
|
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
else if (item.getItemId() == 4)
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
this.showSettings();
|
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
else if (item.getItemId() == 6)
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
this.showMessageLog();
|
|
|
|
}
|
|
|
|
else if (item.getItemId() == 2)
|
|
|
|
{
|
|
|
|
openBrowser(URL_TOR_CHECK);
|
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
else if (item.getItemId() == 3)
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
showHelp();
|
|
|
|
}
|
2010-07-19 22:34:15 +00:00
|
|
|
else if (item.getItemId() == 7)
|
|
|
|
{
|
|
|
|
//launch check.torproject.org
|
|
|
|
openBrowser(URL_TOR_CHECK);
|
|
|
|
}
|
|
|
|
else if (item.getItemId() == 8)
|
|
|
|
{
|
|
|
|
//exit app
|
|
|
|
doExit();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
private void doExit ()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
stopTor();
|
|
|
|
|
|
|
|
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
mNotificationManager.cancelAll();
|
|
|
|
|
|
|
|
unbindService();
|
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
stopService(new Intent(ITorService.class.getName()));
|
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
finish();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/* Return to the main view when the back key is pressed
|
|
|
|
* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
|
|
|
|
*/
|
|
|
|
public boolean onKeyDown(int keyCode, KeyEvent event){
|
|
|
|
|
|
|
|
if(keyCode==KeyEvent.KEYCODE_BACK){
|
|
|
|
|
|
|
|
if(currentView != R.layout.layout_main){
|
|
|
|
|
|
|
|
showMain ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
return super.onKeyDown(keyCode, event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.onKeyDown(keyCode, event);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onPause()
|
|
|
|
*/
|
|
|
|
protected void onPause() {
|
|
|
|
super.onPause();
|
|
|
|
|
|
|
|
|
2010-02-27 23:56:46 +00:00
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
|
2010-03-06 14:56:39 +00:00
|
|
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
|
|
|
// Save UI state changes to the savedInstanceState.
|
|
|
|
// This bundle will be passed to onCreate if the process is
|
|
|
|
// killed and restarted.
|
|
|
|
savedInstanceState.putString("log", logBuffer.toString());
|
|
|
|
// etc.
|
|
|
|
super.onSaveInstanceState(savedInstanceState);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
|
|
super.onRestoreInstanceState(savedInstanceState);
|
|
|
|
// Restore UI state from the savedInstanceState.
|
|
|
|
// This bundle has also been passed to onCreate.
|
|
|
|
|
|
|
|
String logText = savedInstanceState.getString("log");
|
|
|
|
logBuffer.append(logText);
|
|
|
|
}
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onResume()
|
|
|
|
*/
|
|
|
|
protected void onResume() {
|
|
|
|
super.onResume();
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
mNotificationManager.cancelAll();
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
if (mService != null)
|
2010-02-27 23:56:46 +00:00
|
|
|
{
|
2010-07-23 11:10:00 +00:00
|
|
|
try {
|
|
|
|
processSettings();
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
// TODO Auto-generated catch block
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
}
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mOrbot);
|
|
|
|
|
|
|
|
boolean showWizard = prefs.getBoolean("show_wizard",true);
|
|
|
|
|
|
|
|
if (showWizard)
|
|
|
|
{
|
|
|
|
|
|
|
|
Editor pEdit = prefs.edit();
|
|
|
|
|
|
|
|
pEdit.putBoolean("show_wizard",false);
|
|
|
|
|
|
|
|
pEdit.commit();
|
|
|
|
|
|
|
|
showHelp();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onStart()
|
|
|
|
*/
|
|
|
|
protected void onStart() {
|
|
|
|
super.onStart();
|
|
|
|
|
|
|
|
//if Tor binary is not running, then start the service up
|
|
|
|
startService(new Intent(INTENT_TOR_SERVICE));
|
|
|
|
bindService ();
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
//updateStatus ("");
|
2010-02-08 20:39:42 +00:00
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see android.app.Activity#onStop()
|
|
|
|
*/
|
|
|
|
protected void onStop() {
|
|
|
|
super.onStop();
|
|
|
|
|
|
|
|
unbindService();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the main form UI
|
|
|
|
*/
|
|
|
|
private void showMain ()
|
|
|
|
{
|
|
|
|
bindService(); //connect the UI activity to the remote service
|
|
|
|
|
|
|
|
currentView = R.layout.layout_main;
|
|
|
|
setContentView(currentView);
|
|
|
|
|
|
|
|
//add touch listeners for the image and the text label
|
|
|
|
findViewById(R.id.imgStatus).setOnClickListener(this);
|
|
|
|
findViewById(R.id.lblStatus).setOnClickListener(this);
|
|
|
|
|
|
|
|
lblStatus = (TextView)findViewById(R.id.lblStatus);
|
|
|
|
imgStatus = (ImageView)findViewById(R.id.imgStatus);
|
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
//updateStatus("");
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Launch the system activity for Uri viewing with the provided url
|
|
|
|
*/
|
|
|
|
private void openBrowser(String url)
|
|
|
|
{
|
|
|
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-02-27 23:56:46 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the help view - a popup dialog
|
|
|
|
*/
|
|
|
|
private void showHelp ()
|
|
|
|
{
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
new WizardHelper(this).showWizard();
|
2010-02-27 23:56:46 +00:00
|
|
|
}
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/*
|
|
|
|
* Show the message log UI
|
|
|
|
*/
|
|
|
|
private void showMessageLog ()
|
|
|
|
{
|
|
|
|
currentView = R.layout.layout_log;
|
|
|
|
setContentView(currentView);
|
|
|
|
|
|
|
|
txtMessageLog = (TextView)findViewById(R.id.messageLog);
|
2010-07-19 22:34:15 +00:00
|
|
|
|
|
|
|
txtMessageLog.setClickable(true);
|
2010-02-08 20:39:42 +00:00
|
|
|
txtMessageLog.setText(logBuffer.toString());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load the basic settings application to display torrc
|
|
|
|
*/
|
|
|
|
private void showSettings ()
|
|
|
|
{
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
startActivity(new Intent(this, SettingsPreferences.class));
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/*
|
|
|
|
* Read in the Preferences and write then to the .torrc file
|
|
|
|
*/
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
/*
|
|
|
|
private void processSettingsOld ()
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
StringBuffer torrcText = new StringBuffer();
|
|
|
|
|
|
|
|
torrcText.append(TorConstants.TORRC_DEFAULT);
|
|
|
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
|
|
|
boolean useBridges = prefs.getBoolean(PREF_BRIDGES_ENABLED, false);
|
|
|
|
|
|
|
|
boolean autoUpdateBridges = prefs.getBoolean(PREF_BRIDGES_UPDATED, false);
|
2010-03-06 01:23:36 +00:00
|
|
|
|
|
|
|
boolean becomeRelay = prefs.getBoolean(PREF_OR, false);
|
|
|
|
|
2010-03-06 04:46:53 +00:00
|
|
|
boolean ReachableAddresses = prefs.getBoolean(PREF_REACHABLE_ADDRESSES,false);
|
2010-03-06 02:06:00 +00:00
|
|
|
|
2010-02-24 05:09:24 +00:00
|
|
|
enableTransparentProxy = prefs.getBoolean(PREF_TRANSPARENT, false);
|
|
|
|
|
2010-02-27 23:56:46 +00:00
|
|
|
if (hasRoot)
|
|
|
|
{
|
|
|
|
if (enableTransparentProxy)
|
|
|
|
{
|
|
|
|
TorTransProxy.setDNSProxying();
|
|
|
|
TorTransProxy.setTransparentProxying(this, TorServiceUtils.getApps(this));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TorTransProxy.purgeNatIptables();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2010-03-06 04:11:36 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
String bridgeList = prefs.getString(PREF_BRIDGES_LIST,"");
|
|
|
|
|
|
|
|
if (useBridges)
|
|
|
|
{
|
2010-03-06 14:56:39 +00:00
|
|
|
if (bridgeList == null || bridgeList.length() == 0)
|
|
|
|
{
|
|
|
|
|
2010-05-08 04:54:52 +00:00
|
|
|
showAlert("Bridge Error","In order to use the bridge feature, you must enter at least one bridge IP address." +
|
2010-03-06 14:56:39 +00:00
|
|
|
"Send an email to bridges@torproject.org with the line \"get bridges\" by itself in the body of the mail from a gmail account.");
|
|
|
|
|
|
|
|
showSettings();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
torrcText.append("UseBridges 1");
|
|
|
|
torrcText.append('\n');
|
|
|
|
|
|
|
|
torrcText.append("UpdateBridgesFromAuthority ");
|
|
|
|
|
|
|
|
if (autoUpdateBridges)
|
|
|
|
torrcText.append("1");
|
|
|
|
else
|
|
|
|
torrcText.append("0");
|
|
|
|
|
|
|
|
torrcText.append('\n');
|
|
|
|
|
|
|
|
String bridgeDelim = "\n";
|
|
|
|
|
|
|
|
if (bridgeList.indexOf(",") != -1)
|
|
|
|
{
|
|
|
|
bridgeDelim = ",";
|
|
|
|
}
|
|
|
|
|
|
|
|
StringTokenizer st = new StringTokenizer(bridgeList,bridgeDelim);
|
|
|
|
while (st.hasMoreTokens())
|
|
|
|
{
|
|
|
|
torrcText.append("bridge ");
|
|
|
|
torrcText.append(st.nextToken());
|
|
|
|
torrcText.append('\n');
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
torrcText.append("UseBridges 0");
|
2010-03-06 04:11:36 +00:00
|
|
|
torrcText.append('\n');
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
2010-03-06 01:23:36 +00:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2010-03-06 04:46:53 +00:00
|
|
|
if (ReachableAddresses)
|
2010-03-06 02:06:00 +00:00
|
|
|
{
|
2010-03-06 04:46:53 +00:00
|
|
|
String ReachableAddressesPorts =
|
|
|
|
prefs.getString(PREF_REACHABLE_ADDRESSES_PORTS, "*:80,*:443");
|
|
|
|
torrcText.append("ReachableAddresses ");
|
2010-03-06 02:06:00 +00:00
|
|
|
// We should verify this and someday, the Exception will matter :-)
|
2010-03-06 04:46:53 +00:00
|
|
|
torrcText.append(ReachableAddressesPorts);
|
2010-03-06 02:06:00 +00:00
|
|
|
torrcText.append('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
2010-05-08 04:54:52 +00:00
|
|
|
showAlert("Config Error","Your ReachableAddresses settings caused an exception!");
|
2010-03-06 02:06:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2010-03-06 04:50:18 +00:00
|
|
|
if (becomeRelay && (!useBridges) && (!ReachableAddresses))
|
2010-03-06 01:23:36 +00:00
|
|
|
{
|
|
|
|
int ORPort = Integer.parseInt(prefs.getString(PREF_OR_PORT, "9001"));
|
|
|
|
String nickname = prefs.getString(PREF_OR_NICKNAME, "Orbot");
|
|
|
|
|
|
|
|
torrcText.append("ORPort ");
|
|
|
|
torrcText.append(ORPort);
|
|
|
|
torrcText.append('\n');
|
|
|
|
|
|
|
|
torrcText.append("Nickname ");
|
|
|
|
torrcText.append(nickname);
|
|
|
|
torrcText.append('\n');
|
|
|
|
|
|
|
|
torrcText.append("ExitPolicy reject *:*");
|
|
|
|
torrcText.append('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
2010-05-08 04:54:52 +00:00
|
|
|
showAlert("Uh-oh!","Your relay settings caused an exception!");
|
2010-03-06 14:56:39 +00:00
|
|
|
showSettings();
|
|
|
|
return;
|
2010-03-06 01:23:36 +00:00
|
|
|
}
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
Utils.saveTextFile(TorServiceConstants.TORRC_INSTALL_PATH, torrcText.toString());
|
|
|
|
}
|
2010-07-23 11:10:00 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
private void processSettings () throws RemoteException
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
|
|
|
boolean useBridges = prefs.getBoolean(PREF_BRIDGES_ENABLED, false);
|
|
|
|
|
|
|
|
boolean autoUpdateBridges = prefs.getBoolean(PREF_BRIDGES_UPDATED, false);
|
|
|
|
|
|
|
|
boolean becomeRelay = prefs.getBoolean(PREF_OR, false);
|
|
|
|
|
|
|
|
boolean ReachableAddresses = prefs.getBoolean(PREF_REACHABLE_ADDRESSES,false);
|
|
|
|
|
|
|
|
boolean enableTransparentProxy = prefs.getBoolean(PREF_TRANSPARENT, false);
|
|
|
|
|
|
|
|
|
|
|
|
String bridgeList = prefs.getString(PREF_BRIDGES_LIST,"");
|
|
|
|
|
|
|
|
if (useBridges)
|
|
|
|
{
|
|
|
|
if (bridgeList == null || bridgeList.length() == 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
showAlert("Bridge Error","In order to use the bridge feature, you must enter at least one bridge IP address." +
|
|
|
|
"Send an email to bridges@torproject.org with the line \"get bridges\" by itself in the body of the mail from a gmail account.");
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
mService.updateConfiguration("UseBridges", "1", false);
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
String bridgeDelim = "\n";
|
|
|
|
|
|
|
|
if (bridgeList.indexOf(",") != -1)
|
|
|
|
{
|
|
|
|
bridgeDelim = ",";
|
|
|
|
}
|
|
|
|
|
|
|
|
StringTokenizer st = new StringTokenizer(bridgeList,bridgeDelim);
|
|
|
|
while (st.hasMoreTokens())
|
|
|
|
{
|
|
|
|
|
|
|
|
mService.updateConfiguration("bridge", st.nextToken(), false);
|
|
|
|
|
|
|
|
}
|
2010-07-28 04:43:58 +00:00
|
|
|
|
|
|
|
mService.updateConfiguration("UpdateBridgesFromAuthority", "0", false);
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mService.updateConfiguration("UseBridges", "0", false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (ReachableAddresses)
|
|
|
|
{
|
|
|
|
String ReachableAddressesPorts =
|
|
|
|
prefs.getString(PREF_REACHABLE_ADDRESSES_PORTS, "*:80,*:443");
|
|
|
|
|
|
|
|
mService.updateConfiguration("ReachableAddresses", ReachableAddressesPorts, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
showAlert("Config Error","Your ReachableAddresses settings caused an exception!");
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (becomeRelay && (!useBridges) && (!ReachableAddresses))
|
|
|
|
{
|
|
|
|
int ORPort = Integer.parseInt(prefs.getString(PREF_OR_PORT, "9001"));
|
|
|
|
String nickname = prefs.getString(PREF_OR_NICKNAME, "Orbot");
|
|
|
|
|
|
|
|
mService.updateConfiguration("ORPort", ORPort + "", false);
|
|
|
|
mService.updateConfiguration("Nickname", nickname, false);
|
|
|
|
mService.updateConfiguration("ExitPolicy", "reject *:*", false);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
showAlert("Uh-oh!","Your relay settings caused an exception!");
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mService.saveConfiguration();
|
|
|
|
|
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
|
2010-05-08 04:54:52 +00:00
|
|
|
private void showAlert(String title, String msg)
|
2010-03-06 14:56:39 +00:00
|
|
|
{
|
2010-05-08 04:19:06 +00:00
|
|
|
|
2010-05-08 04:54:52 +00:00
|
|
|
new AlertDialog.Builder(this)
|
2010-07-30 20:19:13 +00:00
|
|
|
.setIcon(R.drawable.icon)
|
2010-05-08 04:54:52 +00:00
|
|
|
.setTitle(title)
|
|
|
|
.setMessage(msg)
|
|
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
|
|
.show();
|
2010-03-06 14:56:39 +00:00
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
/*
|
|
|
|
* Set the state of the running/not running graphic and label
|
|
|
|
*/
|
|
|
|
public void updateStatus (String torServiceMsg)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
|
|
|
|
if (mService != null)
|
|
|
|
torStatus = mService.getStatus();
|
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
if (this.currentView == R.layout.layout_log)
|
|
|
|
{
|
|
|
|
txtMessageLog.append(torServiceMsg);
|
|
|
|
txtMessageLog.append("\n");
|
|
|
|
|
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
if (imgStatus != null)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (torStatus == STATUS_ON)
|
|
|
|
{
|
|
|
|
imgStatus.setImageResource(R.drawable.toron);
|
2010-07-23 11:10:00 +00:00
|
|
|
imgStatus.clearAnimation();
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
lblStatus.setText(getString(R.string.status_activated));
|
|
|
|
|
2010-07-28 04:43:58 +00:00
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mOrbot);
|
|
|
|
|
|
|
|
boolean showWizard = prefs.getBoolean("connect_first_time",true);
|
|
|
|
|
|
|
|
if (showWizard)
|
|
|
|
{
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
Editor pEdit = prefs.edit();
|
|
|
|
|
|
|
|
pEdit.putBoolean("connect_first_time",false);
|
|
|
|
|
|
|
|
pEdit.commit();
|
|
|
|
|
|
|
|
showAlert(getString(R.string.status_activated),getString(R.string.connect_first_time));
|
|
|
|
|
|
|
|
}
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
/*
|
2010-02-08 20:39:42 +00:00
|
|
|
if (progressDialog != null)
|
|
|
|
{
|
|
|
|
|
|
|
|
progressDialog.cancel();
|
|
|
|
progressDialog.hide();
|
|
|
|
progressDialog = null;
|
2010-03-06 14:56:39 +00:00
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
}*/
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
else if (torStatus == STATUS_CONNECTING)
|
|
|
|
{
|
|
|
|
|
|
|
|
imgStatus.setImageResource(R.drawable.torstarting);
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
if (imgStatus.getAnimation()==null)
|
|
|
|
{
|
|
|
|
|
|
|
|
imgStatus.setAnimation(AnimationUtils.loadAnimation(this, R.anim.starting));
|
|
|
|
imgStatus.getAnimation().setRepeatMode(Animation.INFINITE);
|
|
|
|
|
|
|
|
imgStatus.getAnimation().setRepeatCount(Animation.INFINITE);
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2010-02-08 20:39:42 +00:00
|
|
|
if (progressDialog == null)
|
|
|
|
{
|
|
|
|
progressDialog = new ProgressDialog(this);
|
|
|
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
|
|
progressDialog.setCancelable(true);
|
|
|
|
progressDialog.setMessage(getString(R.string.status_starting_up));
|
|
|
|
progressDialog.show();
|
|
|
|
|
|
|
|
progressDialog.setProgress(10);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
progressDialog.setMessage(torServiceMsg);
|
2010-07-23 11:10:00 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
lblStatus.setText(torServiceMsg);
|
|
|
|
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
int idx = torServiceMsg.indexOf("%");
|
|
|
|
|
|
|
|
if (idx != -1)
|
|
|
|
{
|
|
|
|
String pComp = torServiceMsg.substring(idx-2,idx).trim();
|
|
|
|
int ipComp = Integer.parseInt(pComp);
|
2010-07-23 11:10:00 +00:00
|
|
|
// progressDialog.setProgress(ipComp);
|
2010-03-06 14:56:39 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
else if (torStatus == STATUS_OFF)
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
|
|
|
imgStatus.setImageResource(R.drawable.torstopping);
|
2010-07-23 11:10:00 +00:00
|
|
|
imgStatus.clearAnimation();
|
2010-02-08 20:39:42 +00:00
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
lblStatus.setText(getString(R.string.status_shutting_down));
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
/*
|
2010-02-08 20:39:42 +00:00
|
|
|
if (progressDialog != null)
|
|
|
|
{
|
|
|
|
|
|
|
|
progressDialog.cancel();
|
|
|
|
progressDialog.hide();
|
|
|
|
progressDialog = null;
|
|
|
|
}
|
2010-07-23 11:10:00 +00:00
|
|
|
*/
|
|
|
|
imgStatus.clearAnimation();
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
imgStatus.setImageResource(R.drawable.toroff);
|
|
|
|
lblStatus.setText(getString(R.string.status_disabled));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
catch (RemoteException e)
|
|
|
|
{
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:20 +00:00
|
|
|
private void startTor () throws RemoteException
|
|
|
|
{
|
|
|
|
mService.setProfile(PROFILE_ON); //this means turn on
|
|
|
|
|
|
|
|
imgStatus.setImageResource(R.drawable.torstarting);
|
|
|
|
lblStatus.setText(getString(R.string.status_starting_up));
|
|
|
|
|
|
|
|
Message msg = mHandler.obtainMessage(ENABLE_TOR_MSG);
|
|
|
|
mHandler.sendMessage(msg);
|
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
// updateStatus("");
|
2010-03-18 03:15:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void stopTor () throws RemoteException
|
|
|
|
{
|
|
|
|
mService.setProfile(PROFILE_ONDEMAND); //these means turn off
|
|
|
|
|
|
|
|
Message msg = mHandler.obtainMessage(DISABLE_TOR_MSG);
|
|
|
|
mHandler.sendMessage(msg);
|
|
|
|
|
2010-07-30 20:19:13 +00:00
|
|
|
//updateStatus("");
|
2010-07-19 22:34:15 +00:00
|
|
|
|
2010-03-18 03:15:20 +00:00
|
|
|
}
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/*
|
|
|
|
* (non-Javadoc)
|
|
|
|
* @see android.view.View.OnClickListener#onClick(android.view.View)
|
|
|
|
*/
|
|
|
|
public void onClick(View view) {
|
|
|
|
|
|
|
|
// the start button
|
|
|
|
if (view.getId()==R.id.imgStatus || view.getId()==R.id.lblStatus)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2010-03-01 06:15:35 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
if (mService == null)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2010-02-27 23:56:46 +00:00
|
|
|
else if (mService.getStatus() == STATUS_READY)
|
2010-02-08 20:39:42 +00:00
|
|
|
{
|
2010-03-01 05:52:44 +00:00
|
|
|
|
2010-03-18 03:15:20 +00:00
|
|
|
startTor();
|
2010-03-01 05:52:44 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
2010-03-18 03:15:20 +00:00
|
|
|
stopTor();
|
2010-03-01 06:15:35 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
2010-03-01 06:15:35 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
Log.i(TAG,"error onclick",e);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
/**
|
|
|
|
* This implementation is used to receive callbacks from the remote
|
|
|
|
* service.
|
|
|
|
*/
|
|
|
|
private ITorServiceCallback mCallback = new ITorServiceCallback.Stub() {
|
|
|
|
/**
|
|
|
|
* This is called by the remote service regularly to tell us about
|
|
|
|
* new values. Note that IPC calls are dispatched through a thread
|
|
|
|
* pool running in each process, so the code executing here will
|
|
|
|
* NOT be running in our main thread like most other things -- so,
|
|
|
|
* to update the UI, we need to use a Handler to hop over there.
|
|
|
|
*/
|
|
|
|
public void statusChanged(String value) {
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
Message msg = mHandler.obtainMessage(STATUS_MSG);
|
2010-02-08 20:39:42 +00:00
|
|
|
msg.getData().putString(HANDLER_TOR_MSG, value);
|
|
|
|
mHandler.sendMessage(msg);
|
|
|
|
}
|
2010-07-23 11:10:00 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void logMessage(String value) throws RemoteException {
|
|
|
|
|
|
|
|
Message msg = mHandler.obtainMessage(LOG_MSG);
|
|
|
|
msg.getData().putString(HANDLER_TOR_MSG, value);
|
|
|
|
mHandler.sendMessage(msg);
|
|
|
|
|
|
|
|
}
|
2010-02-08 20:39:42 +00:00
|
|
|
};
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
private static final int STATUS_MSG = 1;
|
2010-03-01 06:15:35 +00:00
|
|
|
private static final int ENABLE_TOR_MSG = 2;
|
|
|
|
private static final int DISABLE_TOR_MSG = 3;
|
2010-07-23 11:10:00 +00:00
|
|
|
private static final int LOG_MSG = 4;
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
private Handler mHandler = new Handler() {
|
2010-05-29 07:11:29 +00:00
|
|
|
public void handleMessage(Message msg) {
|
2010-02-08 20:39:42 +00:00
|
|
|
switch (msg.what) {
|
2010-07-23 11:10:00 +00:00
|
|
|
case STATUS_MSG:
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
String torServiceMsg = (String)msg.getData().getString(HANDLER_TOR_MSG);
|
|
|
|
|
|
|
|
logBuffer.append(torServiceMsg);
|
|
|
|
logBuffer.append('\n');
|
|
|
|
|
2010-07-19 22:34:15 +00:00
|
|
|
|
|
|
|
if (torServiceMsg.length() > 0 && torServiceMsg.charAt(0)!='>')
|
2010-07-23 11:10:00 +00:00
|
|
|
updateStatus(torServiceMsg);
|
|
|
|
|
|
|
|
break;
|
|
|
|
case LOG_MSG:
|
|
|
|
|
|
|
|
String torLogMsg = (String)msg.getData().getString(HANDLER_TOR_MSG);
|
|
|
|
|
|
|
|
logBuffer.append(torLogMsg);
|
|
|
|
logBuffer.append('\n');
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
break;
|
2010-03-01 06:15:35 +00:00
|
|
|
case ENABLE_TOR_MSG:
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-03-01 06:15:35 +00:00
|
|
|
break;
|
|
|
|
case DISABLE_TOR_MSG:
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
default:
|
|
|
|
super.handleMessage(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class for interacting with the main interface of the service.
|
|
|
|
*/
|
|
|
|
private ServiceConnection mConnection = new ServiceConnection() {
|
|
|
|
public void onServiceConnected(ComponentName className,
|
|
|
|
IBinder service) {
|
|
|
|
// This is called when the connection with the service has been
|
|
|
|
// established, giving us the service object we can use to
|
|
|
|
// interact with the service. We are communicating with our
|
|
|
|
// service through an IDL interface, so get a client-side
|
|
|
|
// representation of that from the raw service object.
|
|
|
|
mService = ITorService.Stub.asInterface(service);
|
|
|
|
|
|
|
|
updateStatus ("");
|
|
|
|
|
|
|
|
// We want to monitor the service for as long as we are
|
|
|
|
// connected to it.
|
|
|
|
try {
|
|
|
|
mService.registerCallback(mCallback);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
// In this case the service has crashed before we could even
|
|
|
|
// do anything with it; we can count on soon being
|
|
|
|
// disconnected (and then reconnected if it can be restarted)
|
|
|
|
// so there is no need to do anything here.
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onServiceDisconnected(ComponentName className) {
|
|
|
|
// This is called when the connection with the service has been
|
|
|
|
// unexpectedly disconnected -- that is, its process crashed.
|
|
|
|
mService = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
boolean mIsBound = false;
|
|
|
|
|
|
|
|
private void bindService ()
|
|
|
|
{
|
|
|
|
bindService(new Intent(ITorService.class.getName()),
|
|
|
|
mConnection, Context.BIND_AUTO_CREATE);
|
|
|
|
|
|
|
|
mIsBound = true;
|
|
|
|
|
2010-07-23 11:10:00 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void unbindService ()
|
|
|
|
{
|
|
|
|
if (mIsBound) {
|
|
|
|
// If we have received the service, and hence registered with
|
|
|
|
// it, then now is the time to unregister.
|
|
|
|
if (mService != null) {
|
|
|
|
try {
|
|
|
|
mService.unregisterCallback(mCallback);
|
2010-07-30 20:19:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
} catch (RemoteException e) {
|
|
|
|
// There is nothing special we need to do if the service
|
|
|
|
// has crashed.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detach our existing connection.
|
|
|
|
unbindService(mConnection);
|
|
|
|
mIsBound = false;
|
2010-07-30 20:19:13 +00:00
|
|
|
|
2010-02-08 20:39:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-06 01:23:36 +00:00
|
|
|
}
|