From 1295b8f844f914feeeef245229ca166b93ca37cd Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Sun, 13 Oct 2013 17:53:59 +0200 Subject: Working LogFragment --HG-- rename : res/layout-w720dp/logwindow.xml => res/layout-w720dp/log_fragment.xml rename : res/layout/logwindow.xml => res/layout/log_fragment.xml rename : src/de/blinkt/openvpn/LogWindow.java => src/de/blinkt/openvpn/fragments/LogFragment.java --- src/de/blinkt/openvpn/fragments/LogFragment.java | 655 +++++++++++++++++++++++ 1 file changed, 655 insertions(+) create mode 100644 src/de/blinkt/openvpn/fragments/LogFragment.java (limited to 'src/de/blinkt/openvpn/fragments') diff --git a/src/de/blinkt/openvpn/fragments/LogFragment.java b/src/de/blinkt/openvpn/fragments/LogFragment.java new file mode 100644 index 00000000..ce63d166 --- /dev/null +++ b/src/de/blinkt/openvpn/fragments/LogFragment.java @@ -0,0 +1,655 @@ +package de.blinkt.openvpn.fragments; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.app.*; +import android.content.*; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.IBinder; +import android.os.Message; +import android.text.SpannableString; +import android.text.format.DateFormat; +import android.text.style.ImageSpan; +import android.view.*; +import android.widget.*; +import android.widget.AdapterView.OnItemLongClickListener; +import de.blinkt.openvpn.*; +import de.blinkt.openvpn.core.OpenVPNManagement; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus.LogItem; +import de.blinkt.openvpn.core.VpnStatus.LogListener; +import de.blinkt.openvpn.core.VpnStatus.StateListener; +import de.blinkt.openvpn.core.OpenVpnService; +import de.blinkt.openvpn.core.OpenVpnService.LocalBinder; +import de.blinkt.openvpn.core.ProfileManager; +import org.jetbrains.annotations.Nullable; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; +import java.util.Vector; + +import static de.blinkt.openvpn.core.OpenVpnService.humanReadableByteCount; + +public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener { + private static final String LOGTIMEFORMAT = "logtimeformat"; + private static final int START_VPN_CONFIG = 0; + private static final String VERBOSITYLEVEL = "verbositylevel"; + protected OpenVpnService mService; + private ServiceConnection mConnection = new ServiceConnection() { + + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mService =null; + } + + }; + private SeekBar mLogLevelSlider; + private LinearLayout mOptionsLayout; + private RadioGroup mTimeRadioGroup; + private TextView mUpStatus; + private TextView mDownStatus; + private TextView mConnectStatus; + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + ladapter.setLogLevel(progress+1); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.radioISO: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_ISO); + break; + case R.id.radioNone: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_NONE); + break; + case R.id.radioShort: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_SHORT); + break; + + } + } + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + //%2$s/s %1$s - ↑%4$s/s %3$s + final String down = String.format("%2$s/s %1$s", humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true)); + final String up = String.format("%2$s/s %1$s", humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); + + if(mUpStatus!=null && mDownStatus!=null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + mUpStatus.setText(up); + mDownStatus.setText(down); + } + }); + } + + } + + + class LogWindowListAdapter implements ListAdapter, LogListener, Callback { + + private static final int MESSAGE_NEWLOG = 0; + + private static final int MESSAGE_CLEARLOG = 1; + + private static final int MESSAGE_NEWTS = 2; + private static final int MESSAGE_NEWLOGLEVEL = 3; + + public static final int TIME_FORMAT_NONE = 0; + public static final int TIME_FORMAT_SHORT = 1; + public static final int TIME_FORMAT_ISO = 2; + + private Vector allEntries=new Vector(); + + private Vector currentLevelEntries=new Vector(); + + private Handler mHandler; + + private Vector observers=new Vector(); + + private int mTimeFormat=0; + private int mLogLevel=3; + + + public LogWindowListAdapter() { + initLogBuffer(); + if (mHandler == null) { + mHandler = new Handler(this); + } + + VpnStatus.addLogListener(this); + } + + + + private void initLogBuffer() { + allEntries.clear(); + Collections.addAll(allEntries, VpnStatus.getlogbuffer()); + initCurrentMessages(); + } + + String getLogStr() { + String str = ""; + for(LogItem entry:allEntries) { + str+=entry.getString(getActivity()) + '\n'; + } + return str; + } + + + private void shareLog() { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file)); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Send Logfile")); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + + @Override + public int getCount() { + return currentLevelEntries.size(); + } + + @Override + public Object getItem(int position) { + return currentLevelEntries.get(position); + } + + @Override + public long getItemId(int position) { + return ((Object)currentLevelEntries.get(position)).hashCode(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v; + if(convertView==null) + v = new TextView(getActivity()); + else + v = (TextView) convertView; + + LogItem le = currentLevelEntries.get(position); + String msg = le.getString(getActivity()); + String time =""; + if (mTimeFormat != TIME_FORMAT_NONE) { + Date d = new Date(le.getLogtime()); + java.text.DateFormat timeformat; + if (mTimeFormat== TIME_FORMAT_ISO) + timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.getDefault()); + else + timeformat = DateFormat.getTimeFormat(getActivity()); + time = timeformat.format(d); + + } + msg = time + " " + le.getVerbosityLevel() + " " + msg; + + int spanStart = time.length(); + + SpannableString t = new SpannableString(msg); + + //t.setSpan(getSpanImage(le,(int)v.getTextSize()),spanStart,spanStart+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + v.setText(t); + return v; + } + + private ImageSpan getSpanImage(LogItem li, int imageSize) { + int imageRes = android.R.drawable.ic_menu_call; + + switch (li.getLogLevel()) { + case ERROR: + imageRes = android.R.drawable.ic_notification_clear_all; + break; + case INFO: + imageRes = android.R.drawable.ic_menu_compass; + break; + case VERBOSE: + imageRes = android.R.drawable.ic_menu_info_details; + break; + case WARNING: + imageRes = android.R.drawable.ic_menu_camera; + break; + } + + Drawable d = getResources().getDrawable(imageRes); + + + //d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + d.setBounds(0, 0, imageSize, imageSize); + ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM); + + return span; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return currentLevelEntries.isEmpty(); + + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void newLog(LogItem logMessage) { + Message msg = Message.obtain(); + assert (msg!=null); + msg.what=MESSAGE_NEWLOG; + Bundle bundle=new Bundle(); + bundle.putParcelable("logmessage", logMessage); + msg.setData(bundle); + mHandler.sendMessage(msg); + } + + @Override + public boolean handleMessage(Message msg) { + // We have been called + if(msg.what==MESSAGE_NEWLOG) { + + LogItem logMessage = msg.getData().getParcelable("logmessage"); + if(addLogMessage(logMessage)) + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } else if (msg.what == MESSAGE_CLEARLOG) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + initLogBuffer(); + } else if (msg.what == MESSAGE_NEWTS) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } else if (msg.what == MESSAGE_NEWLOGLEVEL) { + initCurrentMessages(); + + for (DataSetObserver observer: observers) { + observer.onChanged(); + } + + } + + return true; + } + + private void initCurrentMessages() { + currentLevelEntries.clear(); + for(LogItem li: allEntries) { + if (li.getVerbosityLevel() <= mLogLevel) + currentLevelEntries.add(li); + } + } + + /** + * + * @param logmessage + * @return True if the current entries have changed + */ + private boolean addLogMessage(LogItem logmessage) { + allEntries.add(logmessage); + if (logmessage.getVerbosityLevel() <= mLogLevel) { + currentLevelEntries.add(logmessage); + return true; + } else { + return false; + } + } + + void clearLog() { + // Actually is probably called from GUI Thread as result of the user + // pressing a button. But better safe than sorry + VpnStatus.clearLog(); + VpnStatus.logInfo(R.string.logCleared); + mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); + } + + + + public void setTimeFormat(int newTimeFormat) { + mTimeFormat= newTimeFormat; + mHandler.sendEmptyMessage(MESSAGE_NEWTS); + } + + public void setLogLevel(int logLevel) { + mLogLevel = logLevel; + mHandler.sendEmptyMessage(MESSAGE_NEWLOGLEVEL); + } + + } + + + + private LogWindowListAdapter ladapter; + private TextView mSpeedView; + + private void showDisconnectDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.title_cancel); + builder.setMessage(R.string.cancel_connection_query); + builder.setNegativeButton(android.R.string.no, null); + builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ProfileManager.setConntectedVpnProfileDisconnected(getActivity()); + if (mService != null && mService.getManagement() != null) + mService.getManagement().stopVPN(); + } + }); + + builder.show(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==R.id.clearlog) { + ladapter.clearLog(); + return true; + } else if(item.getItemId()==R.id.cancel){ + showDisconnectDialog(); + return true; + } else if(item.getItemId()==R.id.send) { + ladapter.shareLog(); + } else if(item.getItemId()==R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.getLastConnectedVpn(); + + if(lastConnectedprofile!=null) { + Intent vprefintent = new Intent(getActivity(),VPNPreferences.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID,lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent,START_VPN_CONFIG); + } else { + Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } + } else if(item.getItemId() == R.id.toggle_time) { + showHideOptionsPanel(); + } else if(item.getItemId() == android.R.id.home) { + // This is called when the Home (Up) button is pressed + // in the Action Bar. + Intent parentActivityIntent = new Intent(getActivity(), MainActivity.class); + parentActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(parentActivityIntent); + getActivity().finish(); + return true; + + } + return super.onOptionsItemSelected(item); + + } + + private void showHideOptionsPanel() { + boolean optionsVisible = (mOptionsLayout.getVisibility() != View.GONE); + + ObjectAnimator anim; + if (optionsVisible) { + anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha",1.0f, 0f); + anim.addListener(collapseListener); + + } else { + mOptionsLayout.setVisibility(View.VISIBLE); + anim = ObjectAnimator.ofFloat(mOptionsLayout,"alpha", 0f, 1.0f); + //anim = new TranslateAnimation(0.0f, 0.0f, mOptionsLayout.getHeight(), 0.0f); + + } + + //anim.setInterpolator(new AccelerateInterpolator(1.0f)); + //anim.setDuration(300); + //mOptionsLayout.startAnimation(anim); + anim.start(); + + } + + AnimatorListenerAdapter collapseListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mOptionsLayout.setVisibility(View.GONE); + } + + }; + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.logmenu, menu); + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + menu.removeItem(R.id.toggle_time); + } + + + @Override + public void onResume() { + super.onResume(); + VpnStatus.addStateListener(this); + VpnStatus.addByteCountListener(this); + Intent intent = new Intent(getActivity(), OpenVpnService.class); + intent.setAction(OpenVpnService.START_SERVICE); + + getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + + + // TODO: FIXME: Restore disconnect ability, own Activity?! + /* + if (getIntent() !=null && OpenVpnService.DISCONNECT_VPN.equals(getIntent().getAction())) + showDisconnectDialog(); + + setIntent(null); + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + */ + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == START_VPN_CONFIG && resultCode== Activity.RESULT_OK) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + final VpnProfile profile = ProfileManager.get(getActivity(),configuredVPN); + ProfileManager.getInstance(getActivity()).saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); + dialog.setTitle(R.string.configuration_changed); + dialog.setMessage(R.string.restart_vpn_after_change); + + + dialog.setPositiveButton(R.string.restart, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(getActivity(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } + + + }); + dialog.setNegativeButton(R.string.ignore, null); + dialog.create().show(); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onStop() { + super.onStop(); + VpnStatus.removeStateListener(this); + VpnStatus.removeByteCountListener(this); + getActivity().unbindService(mConnection); + getActivity().getPreferences(0).edit().putInt(LOGTIMEFORMAT, ladapter.mTimeFormat) + .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply(); + + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ListView lv = getListView(); + + lv.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView parent, View view, + int position, long id) { + ClipboardManager clipboard = (ClipboardManager) + getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); + clipboard.setPrimaryClip(clip); + Toast.makeText(getActivity(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.log_fragment,container,false); + + setHasOptionsMenu(true); + + ladapter = new LogWindowListAdapter(); + ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 0); + int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 0); + ladapter.setLogLevel(logLevel); + + setListAdapter(ladapter); + + mTimeRadioGroup = (RadioGroup) v.findViewById(R.id.timeFormatRadioGroup); + mTimeRadioGroup.setOnCheckedChangeListener(this); + + if(ladapter.mTimeFormat== LogWindowListAdapter.TIME_FORMAT_ISO) { + mTimeRadioGroup.check(R.id.radioISO); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_NONE) { + mTimeRadioGroup.check(R.id.radioNone); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_SHORT) { + mTimeRadioGroup.check(R.id.radioShort); + } + + mSpeedView = (TextView) v.findViewById(R.id.speed); + + mOptionsLayout = (LinearLayout) v.findViewById(R.id.logOptionsLayout); + mLogLevelSlider = (SeekBar) v.findViewById(R.id.LogLevelSlider); + mLogLevelSlider.setMax(VpnProfile.MAXLOGLEVEL-1); + mLogLevelSlider.setProgress(logLevel-1); + + mLogLevelSlider.setOnSeekBarChangeListener(this); + + if(getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + mOptionsLayout.setVisibility(View.VISIBLE); + + mUpStatus = (TextView) v.findViewById(R.id.speedUp); + mDownStatus = (TextView) v.findViewById(R.id.speedDown); + mConnectStatus = (TextView) v.findViewById(R.id.speedStatus); + return v; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //getActionBar().setDisplayHomeAsUpEnabled(true); + + } + + + @Override + public void updateState(final String status,final String logMessage, final int resId, final ConnectionStatus level) { + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + String prefix = getString(resId) + ":"; + if (status.equals("BYTECOUNT") || status.equals("NOPROCESS")) + prefix = ""; + if (resId == R.string.unknown_state) + prefix += status; + if (mSpeedView != null) + mSpeedView.setText(prefix + logMessage); + + if (mConnectStatus != null) + mConnectStatus.setText(getString(resId)); + } + }); + + } + + + @Override + public void onDestroy() { + VpnStatus.removeLogListener(ladapter); + super.onDestroy(); + } + +} -- cgit v1.2.3