diff --git a/weechat-android/build.gradle b/weechat-android/build.gradle index a700dc188..1b1dcbcb3 100644 --- a/weechat-android/build.gradle +++ b/weechat-android/build.gradle @@ -47,13 +47,16 @@ android { buildTypes { // Debug version, only ever used locally debug { - packageNameSuffix ".debug" + applicationIdSuffix ".debug" versionNameSuffix "-debug" buildConfigField "String", "VERSION_BANNER", "\""+versionBanner()+"\"" } // These are the real releases in the Google Play Store release { + runProguard true + proguardFile file('proguard.txt') + proguardFile getDefaultProguardFile('proguard-android-optimize.txt') buildConfigField "String", "VERSION_BANNER", "\"" + versionBanner() + "\"" signingConfig signingConfigs.releasePlayConfig } @@ -61,7 +64,10 @@ android { // Development releases in the Google Play Store(signed same as the cloudbees site) devrelease.initWith(buildTypes.release) devrelease { - packageNameSuffix ".dev" + runProguard true + proguardFile file('proguard.txt') + proguardFile getDefaultProguardFile('proguard-android-optimize.txt') + applicationIdSuffix ".dev" versionNameSuffix "-dev" buildConfigField "String", "VERSION_BANNER", "\""+versionBanner()+"\"" signingConfig signingConfigs.devPlayConfig diff --git a/weechat-android/proguard.txt b/weechat-android/proguard.txt new file mode 100644 index 000000000..2ecaaaf67 --- /dev/null +++ b/weechat-android/proguard.txt @@ -0,0 +1,26 @@ +# warnings prevent build from continuing +-ignorewarnings + +# if we do not use obfuscation, everything will fail +# see http://stackoverflow.com/questions/5701126/compile-with-proguard-gives-exception-local-variable-type-mismatch +#-dontobfuscate + +-dontskipnonpubliclibraryclasses +-forceprocessing +-optimizationpasses 5 + +# actionbarsherlock stuff +-keep class android.support.v4.app.** { *; } +-keep interface android.support.v4.app.** { *; } +-keep class com.actionbarsherlock.** { *; } +-keep interface com.actionbarsherlock.** { *; } +-keepattributes *Annotation* + +# strip all logger calls +# i hope the if (DEBUG) checks will get stripped too +-assumenosideeffects class org.slf4j.Logger { + public void error(...); + public void warn(...); + public void debug(...); + public void trace(...); +} \ No newline at end of file diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/MainPagerAdapter.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/MainPagerAdapter.java index fc5a5d4a1..3d7586814 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/MainPagerAdapter.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/MainPagerAdapter.java @@ -1,149 +1,201 @@ package com.ubergeek42.WeechatAndroid; -import java.util.ArrayList; -import java.util.Iterator; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import android.os.Bundle; +import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.view.View; +import android.view.ViewGroup; import com.ubergeek42.WeechatAndroid.fragments.BufferFragment; import com.ubergeek42.WeechatAndroid.fragments.BufferListFragment; -public class MainPagerAdapter extends FragmentStatePagerAdapter { - private static Logger logger = LoggerFactory.getLogger(MainPagerAdapter.class); - private BufferListFragment bufferListFragment = null; - private ArrayList buffers = new ArrayList(); +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +public class MainPagerAdapter extends PagerAdapter { + + private static Logger logger = LoggerFactory.getLogger("MainPagerAdapter"); + final private static boolean DEBUG = BuildConfig.DEBUG && true; + + private boolean phone_mode = false; + private ArrayList names = new ArrayList(); + private ArrayList fragments = new ArrayList(); private ViewPager pager; - - public MainPagerAdapter(FragmentManager fm) throws Exception { - super(fm); - throw new Exception("Should not ever be used!"); - } - - public MainPagerAdapter(FragmentManager fm, ViewPager p) { - super(fm); - pager = p; - } - public void setBuffers(ArrayList frags) { - buffers = frags; + private FragmentManager manager; + private FragmentTransaction transaction = null; + + + public MainPagerAdapter(FragmentManager manager, ViewPager pager) { + super(); + this.manager = manager; + this.pager = pager; } - public void setBufferList(BufferListFragment blf) { - bufferListFragment = blf; + public void firstTimeInit(boolean phone_mode) { + this.phone_mode = phone_mode; + if (phone_mode) { + names.add(""); + fragments.add(new BufferListFragment()); + } } + /////////////////////// + /////////////////////// + /////////////////////// + @Override public int getCount() { - if (bufferListFragment==null) - return buffers.size(); - else - return 1+buffers.size(); + return names.size(); } + /** this can be called either when a new fragment is being added or the old one is being + ** shown. in both cases the fragment will be in this.fragments, but in the latter case it + ** will not have been added to the fragment manager */ @Override - public Fragment getItem(int pos) { - // Tablet view - if (bufferListFragment==null) - return buffers.get(pos); - - // Not tablet view - if (pos==0) { - return bufferListFragment; + public Object instantiateItem(ViewGroup container, int i) { + if (DEBUG) logger.info("instantiateItem(..., {})", i); + if (transaction == null) + transaction = manager.beginTransaction(); + Fragment f = manager.findFragmentByTag(names.get(i)); + if (f != null) { + if (DEBUG) logger.info("instantiateItem(): attach"); // show can be used instead + transaction.attach(f); } else { - return buffers.get(pos-1); + f = fragments.get(i); + if (DEBUG) logger.info("instantiateItem(): add"); + transaction.add(container.getId(), f, names.get(i)); } + return f; } + + /** this can be called either when a fragment has been removed by closeBuffer or when it's + ** getting off-screen. in the first case the fragment will still be in this.fragments */ @Override - public CharSequence getPageTitle(int pos) { - // Tablet view - if (bufferListFragment==null) { - String title = buffers.get(pos).getShortBufferName(); - if (title==null || title.equals("")){ - return buffers.get(pos).getBufferName(); - } - return title; - } - - // Phone view - if (pos==0) { - return "Buffer List"; + public void destroyItem(ViewGroup container, int i, Object object) { + if (DEBUG) logger.info("destroyItem(..., {}, {})", i, object); + if (transaction == null) + transaction = manager.beginTransaction(); + if (fragments.size() > i && fragments.get(i) == object) { + if (DEBUG) logger.info("destroyItem(): detach"); // hide can be used instead + transaction.detach((Fragment) object); } else { - String title = buffers.get(pos-1).getShortBufferName(); - if (title==null || title.equals("")){ - return buffers.get(pos-1).getBufferName(); - } - return title; + if (DEBUG) logger.info("destroyItem(): remove"); + transaction.remove((Fragment) object); } } - - public void closeBuffer(String buffer) { - int i=0; - Iterator iter = buffers.iterator(); - while(iter.hasNext()) { - BufferFragment bf = iter.next(); - if (bf.getBufferName().equals(buffer)) { - // TODO: wrong thread error for pager.setCurrentItem - pager.setCurrentItem(i); - iter.remove(); - } - i++; - } - notifyDataSetChanged(); + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; } - public void openBuffer(String buffer) { - // Find the appropriate buffer in our list - for (int i=0;i {}", (idx >= 0) ? idx : POSITION_NONE); + return (idx >= 0) ? idx : POSITION_NONE; + } + + /** this one's empty because instantiateItem and destroyItem create transactions as needed + ** this function is called too frequently to create a transaction inside it*/ + @Override + public void startUpdate(ViewGroup container) {} + + /** this function, too, is called way too frequently */ + @Override + public void finishUpdate(ViewGroup container) { + if (transaction == null) + return; + transaction.commitAllowingStateLoss(); + transaction = null; + manager.executePendingTransactions(); + } + @Override + public CharSequence getPageTitle(int i) { + return (phone_mode && i == 0) ? "Buffer List" : ((BufferFragment) fragments.get(i)).getShortBufferName(); } - public BufferFragment getCurrentBuffer() { - int pos = pager.getCurrentItem(); - - if (bufferListFragment == null) { - // Tablet view - if (pos>=0 && pos < buffers.size()) { - return buffers.get(pos); - } + /** switch to already open buffer OR create a new buffer, putting it into BOTH names and fragments, + ** run notifyDataSetChanged() which will in turn call instantiateItem(), and set new buffer as the current one */ + public void openBuffer(String name) { + if (DEBUG) logger.info("openBuffer({})", name); + int idx = names.indexOf(name); + if (idx >= 0) { + // found buffer by name, switch to it + pager.setCurrentItem(idx); } else { - // Phone view - if (pos>=1 && pos <= buffers.size()) { - return buffers.get(pos-1); - } + // create a new one + Fragment f = new BufferFragment(); + Bundle args = new Bundle(); + args.putString("buffer", name); + f.setArguments(args); + fragments.add(f); + names.add(name); + notifyDataSetChanged(); + pager.setCurrentItem(names.size()); } - return null; } - + + /** close buffer if open, removing it from BOTH names and fragments. + ** destroyItem() checks the lists to see if it has to remove the item for good */ + public void closeBuffer(String name) { + if (DEBUG) logger.info("closeBuffer({})", name); + int idx = names.indexOf(name); + if (idx >= 0) { + names.remove(idx); + fragments.remove(idx); + notifyDataSetChanged(); + } + } + + /** returns BufferFragment that is currently focused + ** or null if nothing or BufferListFragment is focused */ + public BufferFragment getCurrentBuffer() { + int i = pager.getCurrentItem(); + if ((phone_mode && i == 0) || fragments.size() == 0) + return null; + else + return (BufferFragment) fragments.get(i); + } + + /** the following two methods magically get called on application recreation, + ** so put all our save/restore state here */ @Override - public int getItemPosition(Object object) { - return POSITION_NONE; + public Parcelable saveState() { + if (DEBUG) logger.info("saveState()"); + if (fragments.size() == 0) + return null; + Bundle state = new Bundle(); + state.putStringArrayList("\0", names); + for (int i = 0, size = names.size(); i < size; i++) + manager.putFragment(state, names.get(i), fragments.get(i)); + return state; + } + + @Override + public void restoreState(Parcelable parcel, ClassLoader loader) { + if (DEBUG) logger.info("restoreState()"); + if (parcel == null) + return; + Bundle state = (Bundle) parcel; + state.setClassLoader(loader); + names = state.getStringArrayList("\0"); + if (names.size() > 0) { + for (String name : names) + fragments.add(manager.getFragment(state, name)); + phone_mode = names.get(0).equals(""); + notifyDataSetChanged(); + } } - } diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.java index 119a3d661..0dc648d98 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.java @@ -36,9 +36,9 @@ import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; +import android.support.v4.app.FragmentManager; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.widget.Toast; @@ -47,79 +47,57 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.ubergeek42.WeechatAndroid.fragments.BufferFragment; -import com.ubergeek42.WeechatAndroid.fragments.BufferListFragment; -import com.ubergeek42.WeechatAndroid.fragments.BufferListFragment.OnBufferSelectedListener; import com.ubergeek42.WeechatAndroid.service.RelayService; import com.ubergeek42.WeechatAndroid.service.RelayServiceBinder; import com.ubergeek42.weechat.HotlistItem; import com.ubergeek42.weechat.relay.RelayConnectionHandler; import com.viewpagerindicator.TitlePageIndicator; -public class WeechatActivity extends SherlockFragmentActivity implements RelayConnectionHandler, OnBufferSelectedListener, OnPageChangeListener { +public class WeechatActivity extends SherlockFragmentActivity implements RelayConnectionHandler, OnPageChangeListener { - private static Logger logger = LoggerFactory.getLogger(WeechatActivity.class); - private boolean mBound = false; - private RelayServiceBinder rsb; - - private SocketToggleConnection taskToggleConnection; - private HotlistListAdapter hotlistListAdapter; + private static Logger logger = LoggerFactory.getLogger("WA"); + final private static boolean DEBUG = BuildConfig.DEBUG && true; + + private RelayServiceBinder relay; + private SocketToggleConnection connection_state_toggler; private Menu actionBarMenu; - private ViewPager viewPager; private MainPagerAdapter mainPagerAdapter; private TitlePageIndicator titleIndicator; + private InputMethodManager imm; - private boolean tabletMode = false; + private boolean phone_mode; + + ///////////////////////// + ///////////////////////// lifecycle + ///////////////////////// - /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) logger.debug("onCreate(...)"); super.onCreate(savedInstanceState); - // Start the background service(if necessary) + // Start the background service (if necessary) startService(new Intent(this, RelayService.class)); // Load the layout setContentView(R.layout.main_screen); - - BufferListFragment blf = (BufferListFragment) getSupportFragmentManager().findFragmentById(R.id.bufferlist_fragment); - if (blf != null) { - tabletMode = true; - } - + + FragmentManager manager = getSupportFragmentManager(); viewPager = (ViewPager) findViewById(R.id.main_viewpager); - - // Restore state if we have it - if (savedInstanceState != null) { - // Load the previous BufferListFragment, and the Buffers that were open - int numpages = savedInstanceState.getInt("numpages"); - - ArrayList frags = new ArrayList(); - for(int i=0;i { + @Override + protected Boolean doInBackground(Void... voids) { + if (relay.isConnection(RelayService.CONNECTED)) { + relay.shutdown(); + return true; + } else { + return relay.connect(); + } + } + + @Override + protected void onPostExecute(Boolean success) { + supportInvalidateOptionsMenu(); + } + } + + ///////////////////////// + ///////////////////////// OnPageChangeListener + ///////////////////////// + + @Override + public void onPageScrollStateChanged(int state) {} + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} + + // change the title of the window when the user switches tabs + @Override + public void onPageSelected(int position) { + if (DEBUG) logger.debug("onPageSelected({})", position); + invalidateOptionsMenu(); + hideSoftwareKeyboard(); + } + + ///////////////////////// + ///////////////////////// other fragment methods + ///////////////////////// + + @Override + protected void onNewIntent(Intent intent) { + if (DEBUG) logger.debug("onNewIntent({})", intent); + super.onNewIntent(intent); + setIntent(intent); + handleIntent(); + } + + /** Can safely hold on to this according to docs + ** http://developer.android.com/reference/android/app/Activity.html#onCreateOptionsMenu(android.view.Menu) **/ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (false && DEBUG) logger.debug("onCreateOptionsMenu(...)"); + MenuInflater menuInflater = getSupportMenuInflater(); + menuInflater.inflate(R.menu.menu_actionbar, menu); + actionBarMenu = menu; + makeMenuReflectConnectionStatus(); + return super.onCreateOptionsMenu(menu); + } @Override public boolean onPrepareOptionsMenu(Menu menu) { + if (false && DEBUG) logger.debug("onPrepareOptionsMenu(...)"); super.onPrepareOptionsMenu(menu); - if (viewPager.getCurrentItem()==0 && !tabletMode) { - menu.findItem(R.id.menu_nicklist).setVisible(false); - menu.findItem(R.id.menu_close).setVisible(false); - } else { - menu.findItem(R.id.menu_nicklist).setVisible(true); - menu.findItem(R.id.menu_close).setVisible(true); - } + boolean buffer_visible = !(phone_mode && viewPager.getCurrentItem() == 0); + menu.findItem(R.id.menu_nicklist).setVisible(buffer_visible); + menu.findItem(R.id.menu_close).setVisible(buffer_visible); return true; } - + /** handle the options when the user presses the menu button */ @Override - // Handle the options when the user presses the Menu key public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: { - if (viewPager != null) - viewPager.setCurrentItem(0); - break; - } - case R.id.menu_connection_state: { - if (rsb != null) { - taskToggleConnection = new SocketToggleConnection(); - taskToggleConnection.execute(); + case android.R.id.home: { + if (viewPager != null) + viewPager.setCurrentItem(0); + break; } - break; - } - case R.id.menu_preferences: { - Intent i = new Intent(this, WeechatPreferencesActivity.class); - startActivity(i); - break; - } - case R.id.menu_close: { - if (viewPager.getCurrentItem()>0 || tabletMode) { + case R.id.menu_connection_state: { + if (relay != null) { + connection_state_toggler = new SocketToggleConnection(); + connection_state_toggler.execute(); + } + break; + } + case R.id.menu_preferences: { + Intent i = new Intent(this, WeechatPreferencesActivity.class); + startActivity(i); + break; + } + case R.id.menu_close: { BufferFragment currentBuffer = mainPagerAdapter.getCurrentBuffer(); if (currentBuffer != null) { currentBuffer.onBufferClosed(); } + break; } - break; - } - case R.id.menu_about: { - Intent i = new Intent(this, WeechatAboutActivity.class); - startActivity(i); - break; - } - case R.id.menu_quit: { - if (rsb != null) { - rsb.shutdown(); + case R.id.menu_about: { + Intent i = new Intent(this, WeechatAboutActivity.class); + startActivity(i); + break; } - unbindService(mConnection); - mBound = false; - stopService(new Intent(this, RelayService.class)); - finish(); - break; - } - case R.id.menu_hotlist: { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.hotlist); - builder.setAdapter(hotlistListAdapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int position) { - HotlistItem hotlistItem = hotlistListAdapter.getItem(position); - String name = hotlistItem.getFullName(); - onBufferSelected(name); + case R.id.menu_quit: { + if (relay != null) { + relay.shutdown(); } - }); - builder.create().show(); - break; - } - case R.id.menu_nicklist: { - // No nicklist if they aren't looking at a buffer - if (viewPager.getCurrentItem()==0 && !tabletMode) { + unbindService(service_connection); + relay = null; + stopService(new Intent(this, RelayService.class)); + finish(); break; } - - // TODO: check for null(should be covered by previous if statement - BufferFragment currentBuffer = mainPagerAdapter.getCurrentBuffer(); - if (currentBuffer == null) break; - ArrayList nicks = currentBuffer.getNicklist(); - if (nicks == null) { + case R.id.menu_hotlist: { + if (relay != null && relay.isConnection(RelayService.CONNECTED)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.hotlist); + final HotlistListAdapter hla = new HotlistListAdapter(WeechatActivity.this, relay); + builder.setAdapter(hla, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int position) { + HotlistItem hotlistItem = hla.getItem(position); + String name = hotlistItem.getFullName(); + openBuffer(name); + } + }); + builder.create().show(); + } + break; + } + case R.id.menu_nicklist: { + BufferFragment currentBuffer = mainPagerAdapter.getCurrentBuffer(); + if (currentBuffer == null) + break; + ArrayList nicks = currentBuffer.getNicklist(); + if (nicks == null) + break; + + NickListAdapter nicklistAdapter = new NickListAdapter(WeechatActivity.this, nicks); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.nicklist_menu) + " (" + nicks.size() + ")"); + builder.setAdapter(nicklistAdapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int position) { + // TODO define something to happen here + } + }); + builder.create().show(); break; } - - NickListAdapter nicklistAdapter = new NickListAdapter(WeechatActivity.this, nicks); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.nicklist_menu) + " (" + nicks.size() + ")"); - builder.setAdapter(nicklistAdapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int position) { - // TODO define something to happen here - } - }); - builder.create().show(); - break; - } } return super.onOptionsItemSelected(item); } + ///////////////////////// + ///////////////////////// private stuff + ///////////////////////// - - /** - * Replacement method for onPrepareOptionsMenu due to rsb might be null on the event of clicking - * the menu button. - * - * Hence our activity stores the menu references in onCreateOptionsMenu and we can update menu - * items underway from events like onConnect. - * - * @param menu - * actionBarMenu to update context on - */ - public void updateMenuContext(Menu menu) { - if (menu == null) { - return; - } - - // Swap the text from connect to disconnect depending on connection status - MenuItem connectionStatus = menu.findItem(R.id.menu_connection_state); - if (rsb != null && rsb.isConnected()) { - connectionStatus.setTitle(R.string.disconnect); + /** when we get an intent, do something with it */ + private void handleIntent() { + Bundle extras = getIntent().getExtras(); + if (extras != null) { + if (DEBUG) logger.debug("handleIntent(): opening a buffer from extras"); + openBuffer(extras.getString("buffer")); } else { - connectionStatus.setTitle(R.string.connect); + // TODO: Load the bufferlist } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater menuInflater = getSupportMenuInflater(); - menuInflater.inflate(R.menu.menu_actionbar, menu); - - updateMenuContext(menu); - - // Can safely hold on to this according to docs - // http://developer.android.com/reference/android/app/Activity.html#onCreateOptionsMenu(android.view.Menu) - actionBarMenu = menu; - return super.onCreateOptionsMenu(menu); - } - - public void closeBuffer(final String bufferName) { - // In own thread to prevent things from breaking + /** change first menu item from connect to disconnect or back depending on connection status */ + private void makeMenuReflectConnectionStatus() { + if (false && DEBUG) logger.debug("makeMenuReflectConnectionStatus()"); runOnUiThread(new Runnable() { @Override public void run() { - mainPagerAdapter.closeBuffer(bufferName); - titleIndicator.setCurrentItem(viewPager.getCurrentItem()); + if (WeechatActivity.this.actionBarMenu != null) { + MenuItem connectionStatus = WeechatActivity.this.actionBarMenu.findItem(R.id.menu_connection_state); + if (relay != null && (relay.isConnection(RelayService.CONNECTED))) + connectionStatus.setTitle(R.string.disconnect); + else + connectionStatus.setTitle(R.string.connect); + } } }); } - /** - * Used to toggle the connection - */ - private class SocketToggleConnection extends AsyncTask { - @Override - protected Boolean doInBackground(Void... voids) { - if (rsb.isConnected()) { - rsb.shutdown(); - } else { - return rsb.connect(); - } - return true; - } - - @Override - protected void onPostExecute(Boolean success) { - supportInvalidateOptionsMenu(); - } - } - - /** - * Service connection management... - */ - - @Override - protected void onStart() { - super.onStart(); - // Bind to the Relay Service - bindService(new Intent(this, RelayService.class), mConnection, Context.BIND_AUTO_CREATE); - } + ///////////////////////// + ///////////////////////// misc + ///////////////////////// - @Override - protected void onStop() { - super.onStop(); - - if (taskToggleConnection != null - && taskToggleConnection.getStatus() != AsyncTask.Status.FINISHED) { - taskToggleConnection.cancel(true); - taskToggleConnection = null; - } - - if (mBound) { - rsb.removeRelayConnectionHandler(WeechatActivity.this); - unbindService(mConnection); - mBound = false; + public void openBuffer(final String name) { + if (DEBUG) logger.debug("openBuffer({})", name); + if (relay != null && relay.getBufferByName(name) != null) { + mainPagerAdapter.openBuffer(name); + titleIndicator.setCurrentItem(viewPager.getCurrentItem()); } } - ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - rsb = (RelayServiceBinder) service; - rsb.addRelayConnectionHandler(WeechatActivity.this); - - mBound = true; - // Check if the service is already connected to the weechat relay, and if so load it up - if (rsb.isConnected()) { - WeechatActivity.this.onConnect(); + // In own thread to prevent things from breaking + public void closeBuffer(final String name) { + if (DEBUG) logger.debug("closeBuffer({})", name); + runOnUiThread(new Runnable() { + @Override + public void run() { + mainPagerAdapter.closeBuffer(name); + titleIndicator.setCurrentItem(viewPager.getCurrentItem()); } - - handleIntent(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mBound = false; - rsb = null; - } - }; - - /** - * Part of OnPageChangeListener - * Used to change the title of the window when the user switches tabs - */ - @Override - public void onPageScrollStateChanged(int state) { - } - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + }); } - @Override - public void onPageSelected(int position) { - invalidateOptionsMenu(); - final InputMethodManager imm = (InputMethodManager)getSystemService( - Context.INPUT_METHOD_SERVICE); + + /** hides the software keyboard, if any */ + public void hideSoftwareKeyboard() { imm.hideSoftInputFromWindow(viewPager.getWindowToken(), 0); } - - } diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferFragment.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferFragment.java index dfe2bdbde..27a9d068f 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferFragment.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferFragment.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -31,13 +32,13 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; +import com.ubergeek42.WeechatAndroid.BuildConfig; import com.ubergeek42.WeechatAndroid.ChatLinesAdapter; import com.ubergeek42.WeechatAndroid.R; import com.ubergeek42.WeechatAndroid.WeechatActivity; @@ -45,387 +46,464 @@ import com.ubergeek42.WeechatAndroid.service.RelayServiceBinder; import com.ubergeek42.weechat.Buffer; import com.ubergeek42.weechat.BufferObserver; +import com.ubergeek42.weechat.relay.RelayConnectionHandler; public class BufferFragment extends SherlockFragment implements BufferObserver, OnKeyListener, - OnSharedPreferenceChangeListener, OnClickListener, TextWatcher { + OnSharedPreferenceChangeListener, OnClickListener, TextWatcher, RelayConnectionHandler, + TextView.OnEditorActionListener { - private static Logger logger = LoggerFactory.getLogger(BufferFragment.class); + private static Logger logger = LoggerFactory.getLogger("buffer"); + final private static boolean DEBUG = BuildConfig.DEBUG && true; - private ListView chatlines; + private ListView chatLines; private EditText inputBox; private Button sendButton; private Button tabButton; - private boolean mBound; - private RelayServiceBinder rsb; + private RelayServiceBinder relay; - private String fragmentTitle = ""; - private String bufferName = ""; + private String name = ""; + private String shortname = "Unknown"; private Buffer buffer; - private ChatLinesAdapter chatlineAdapter; + private ChatLinesAdapter chatlines_adapter; - private ArrayList nickCache; - private final String[] message = { "Please wait, loading content." }; + private ArrayList nicks; // Settings for keeping track of the current tab completion stuff - private boolean tabCompletingInProgress; - private Vector tabCompleteMatches; - private int tabCompleteIndex; - private int tabCompleteWordStart; - private int tabCompleteWordEnd; + private boolean tc_inprogress; + private Vector tc_matches; + private int tc_index; + private int tc_wordstart; + private int tc_wordend; // Preference things private SharedPreferences prefs; private boolean enableTabComplete = true; + ///////////////////////// + ///////////////////////// lifecycle + ///////////////////////// + + @Override + public void onAttach(Activity activity) { + if (DEBUG) logger.warn("{} onAttach(...)", name); + super.onAttach(activity); + } + @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) logger.warn("{} onCreate()", name); super.onCreate(savedInstanceState); + if (savedInstanceState != null) shortname = savedInstanceState.getString("shortname"); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.chatview_main, container, false); + if (DEBUG) logger.warn("{} onCreateView()", name); + View v = inflater.inflate(R.layout.chatview_main, container, false); + + chatLines = (ListView) v.findViewById(R.id.chatview_lines); + inputBox = (EditText) v.findViewById(R.id.chatview_input); + sendButton = (Button) v.findViewById(R.id.chatview_send); + tabButton = (Button) v.findViewById(R.id.chatview_tab); + + sendButton.setOnClickListener(this); + tabButton.setOnClickListener(this); + inputBox.setOnKeyListener(this); // listen for hardware keyboard + inputBox.addTextChangedListener(this); // listen for software keyboard through watching input box text + inputBox.setOnEditorActionListener(this); // listen for software keyboard's “send” click. see onEditorAction() + + return v; } - /** Called when the activity is first created. */ + /** this method is run on “activation” of the buffer, although it may be invisible + ** we want to be disconnected from all things before the method runs + ** we connect to the service, which results in: + ** service_connection.onServiceConnected() which: + ** binds to RelayConnectionHandler, and, + ** if connected, + ** calls onBuffersListed() + ** else + ** calls onDisconnect(), which sets the “please connect” message */ @Override public void onStart() { + if (DEBUG) logger.warn("{} onStart()", name); super.onStart(); prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getBaseContext()); prefs.registerOnSharedPreferenceChangeListener(this); enableTabComplete = prefs.getBoolean("tab_completion", true); - // During startup, check if there are arguments passed to the fragment. - // onStart is a good place to do this because the layout has already been - // applied to the fragment at this point so we can safely call the method - // below that sets the Buffer text. - Bundle args = getArguments(); - if (args != null) { - // Set Buffer based on argument passed in - this.bufferName = args.getString("buffer"); - // might need a refreshView() here? - } + this.name = getArguments().getString("buffer"); + if (BuildConfig.DEBUG && (name.equals("") || relay != null || buffer != null)) // sanity check + throw new AssertionError("names shouldn't be ''"); - // Bind to the Relay Service - if (mBound == false) { - getActivity().bindService(new Intent(getActivity(), RelayService.class), mConnection, - Context.BIND_AUTO_CREATE); - } + getActivity().bindService(new Intent(getActivity(), RelayService.class), service_connection, + Context.BIND_AUTO_CREATE); } + @Override + public void onResume() { + if (DEBUG) logger.warn("{} onResume()", name); + super.onResume(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("shortname", shortname); + } + + @Override + public void onPause() { + if (DEBUG) logger.warn("{} onPause()", name); + super.onPause(); + } + + /** this is the “deactivation” of the buffer, not necessarily called when it gets destroyed + ** we want to disconnect from all things here */ @Override public void onStop() { + if (DEBUG) logger.warn("{} onStop()", name); super.onStop(); - - if (mBound) { - getActivity().unbindService(mConnection); - mBound = false; + if (relay != null) { + if (buffer != null) relay.unsubscribeBuffer(buffer.getPointer()); // unsubscribe (???) + relay.removeRelayConnectionHandler(BufferFragment.this); // remove connect / disconnect watcher + relay = null; + } + if (buffer != null) { + buffer.removeObserver(this); // remove buffer watcher + buffer = null; } + getActivity().unbindService(service_connection); } - public String getBufferName() { - return bufferName; + @Override + public void onDestroyView() { + if (DEBUG) logger.warn("{} onDestroyView()", name); + super.onDestroyView(); } - public String getShortBufferName() { - if (buffer!=null) - return buffer.getShortName(); - else - return "Unknown"; + + @Override + public void onDestroy() { + if (DEBUG) logger.warn("{} onDestroy()", name); + super.onDestroy(); } - private ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - logger.debug("Bufferfragment onserviceconnected"); - rsb = (RelayServiceBinder) service; - mBound = true; + @Override + public void onDetach() { + if (DEBUG) logger.warn("{} onDetach()", name); + super.onDetach(); + } + + ///////////////////////// + ///////////////////////// service connection + ///////////////////////// - initView(); + private ServiceConnection service_connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder + service) { + if (DEBUG) logger.warn("{} onServiceConnected() <{}>", BufferFragment.this.name, BufferFragment.this); + relay = (RelayServiceBinder) service; + if (relay.isConnection(RelayService.BUFFERS_LISTED)) + BufferFragment.this.onBuffersListed(); // TODO: run this in a thread + else + BufferFragment.this.onDisconnect(); + relay.addRelayConnectionHandler(BufferFragment.this); // connect/disconnect watcher } @Override - public void onServiceDisconnected(ComponentName name) { + public void onServiceDisconnected(ComponentName name) { // TODO: wut + if (DEBUG) logger.warn("{} onServiceDisconnected() <- should not happen!", BufferFragment.this.name); if (buffer != null) { - buffer.removeObserver(BufferFragment.this); + buffer.removeObserver(BufferFragment.this); // buffer watcher + buffer = null; } - mBound = false; - rsb = null; + relay = null; } }; - private void loadEmptyView() { - ViewGroup vg = (ViewGroup) getView().findViewById(R.id.chatview_layout); - vg.removeAllViews(); - View empty = getActivity().getLayoutInflater().inflate(R.layout.buffer_not_loaded, vg, - false); - vg.addView(empty); - } + ///////////////////////// + ///////////////////////// RelayConnectionHandler stuff + ///////////////////////// - private void initView() { - // Called without bufferName, can't do anything. - if (bufferName.equals("")) { - loadEmptyView(); - return; - } + @Override + public void onConnecting() {} - // Remove buffer from hotlist - if (rsb.getHotlistManager()!= null) { - rsb.getHotlistManager().removeHotlistItem(bufferName); - } + @Override + public void onConnect() {if (DEBUG) logger.warn("{} onConnect()", name);} - chatlines = (ListView) getView().findViewById(R.id.chatview_lines); - inputBox = (EditText) getView().findViewById(R.id.chatview_input); - sendButton = (Button) getView().findViewById(R.id.chatview_send); - tabButton = (Button) getView().findViewById(R.id.chatview_tab); - - if (prefs.getBoolean("sendbtn_show", true)) { - sendButton.setVisibility(View.VISIBLE); - } else { - sendButton.setVisibility(View.GONE); - } - if (prefs.getBoolean("tabbtn_show", false)) { - tabButton.setVisibility(View.VISIBLE); + @Override + public void onAuthenticated() {} + + /** this function is called when the buffers have been listed, i.e. when we can TRY + ** attaching to the buffer and fetching lines and sending messages and whatnot + ** it's not necessary that the buffers have been listed just now, though */ + public void onBuffersListed() { + if (DEBUG) logger.warn("{} onBuffersListed() <{}>", name, this); + if (DEBUG && name.equals("")) throw new AssertionError("name can't be empty in onBuffersListed"); + + // check if the buffer is still there + // it should be there at ALL times EXCEPT when we RE-connect to the service and find it missing + buffer = relay.getBufferByName(name); + if (buffer == null) { + showEmpty(); } else { - tabButton.setVisibility(View.GONE); - } - - chatlines.setAdapter(new ArrayAdapter(getActivity(), R.layout.tips_list_item, message)); - // chatlines.setEmptyView(getView().findViewById(android.R.id.empty)); - - buffer = rsb.getBufferByName(bufferName); + // set short name. we set it here because it's the buffer won't change + // and the name should be accessible between calls to this function + shortname = buffer.getShortName(); + + // remove this from hotlist + if (relay.getHotlistManager() != null) + relay.getHotlistManager().removeHotlistItem(name); // remove from hotlist + + chatlines_adapter = new ChatLinesAdapter(getActivity(), buffer); + registerForContextMenu(chatLines); + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + inputBox.setFocusable(true); + inputBox.setFocusableInTouchMode(true); + sendButton.setEnabled(true); + tabButton.setEnabled(true); + sendButton.setVisibility(prefs.getBoolean("sendbtn_show", true) ? View.VISIBLE : View.GONE); + tabButton.setVisibility(prefs.getBoolean("tabbtn_show", false) ? View.VISIBLE : View.GONE); + chatLines.setAdapter(chatlines_adapter); + } + }); - // The buffer is no longer open... - if (buffer == null) { - bufferName = ""; - loadEmptyView(); - return; - } - // TODO this could be settings defined by user - StringBuilder tsb = new StringBuilder(); - String buffername = buffer.getShortName(); - String title = buffer.getTitle(); - if (buffername != null) { - tsb.append(buffername); - tsb.append(" "); - } - if (title != null) { - tsb.append(title); + // attach this fragment to buffer and + // subscribe to the buffer (gets the lines for it, and gets nicklist) + buffer.addObserver(this); // buffer watcher + relay.subscribeBuffer(buffer.getPointer()); // subscription } - fragmentTitle = tsb.toString(); - - buffer.addObserver(this); - - // Subscribe to the buffer(gets the lines for it, and gets nicklist) - rsb.subscribeBuffer(buffer.getPointer()); + } - chatlineAdapter = new ChatLinesAdapter(getActivity(), buffer); - chatlines.setAdapter(chatlineAdapter); - registerForContextMenu(chatlines); - onLineAdded(); + /** loads the “no buffer, choose from list” thing */ + private void showEmpty() { // TODO: replace with a notification that the buffer's been closed (?) in weechat? + if (DEBUG) logger.warn("{} showEmpty() <{}>", name, this); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + ViewGroup vg = (ViewGroup) getView().findViewById(R.id.chatview_layout); + vg.removeAllViews(); + vg.addView(getActivity().getLayoutInflater().inflate(R.layout.buffer_not_loaded, vg, false)); + } + }); + } - sendButton.setOnClickListener(this); - tabButton.setOnClickListener(this); - inputBox.setOnKeyListener(this); // listen for hardware keyboard - inputBox.addTextChangedListener(this); // listen for software keyboard through watching input box text - inputBox.setOnEditorActionListener(new TextView.OnEditorActionListener() { + /** on disconnect, restore chat lines if any + ** remove the bottom bar to indicate that we are offline + ** also remove the keyboard, if any */ + @Override + public void onDisconnect() { + if (DEBUG) logger.warn("{} onDisconnect()", name); + getActivity().runOnUiThread(new Runnable() { @Override - public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { - boolean handled = false; - if (actionId == EditorInfo.IME_ACTION_SEND) { - getActivity().runOnUiThread(messageSender); - handled = true; - } - return handled; + public void run() { + if (chatlines_adapter != null) chatLines.setAdapter(chatlines_adapter); + inputBox.setFocusable(false); + sendButton.setEnabled(false); + tabButton.setEnabled(false); + ((WeechatActivity) getActivity()).hideSoftwareKeyboard(); } }); } + @Override + public void onError(String err, Object extraInfo) {} + + ///////////////////////// + ///////////////////////// BufferObserver stuff + ///////////////////////// + + /** */ @Override public void onLineAdded() { - rsb.resetNotifications(); + if (false && DEBUG) logger.warn("{} onLineAdded()", name); + if (DEBUG && relay == null) {throw new AssertionError("relay can't be null in onLineAdded()");} + relay.resetNotifications(); buffer.resetHighlight(); buffer.resetUnread(); - chatlineAdapter.notifyChanged(); + chatlines_adapter.notifyChanged(); } @Override public void onBufferClosed() { - WeechatActivity act = (WeechatActivity) getActivity(); - act.closeBuffer(bufferName); - if (buffer != null) { - rsb.unsubscribeBuffer(buffer.getPointer()); - } - } - - public ArrayList getNicklist() { - return nickCache; + if (DEBUG) logger.warn("{} onBufferClosed()", name); + ((WeechatActivity) getActivity()).closeBuffer(name); } @Override public void onNicklistChanged() { - nickCache = buffer.getNicks(); + nicks = buffer.getNicks(); } - // User pressed some key in the input box, check for what it was - // NOTE: this only applies to HARDWARE buttons + ///////////////////////// + ///////////////////////// misc + ///////////////////////// + + /** the only OnKeyListener's method + ** User pressed some key in the input box, check for what it was + ** NOTE: this only applies to HARDWARE buttons */ @Override public boolean onKey(View v, int keycode, KeyEvent event) { - + if (DEBUG) logger.warn("{} onKey(..., {}, ...)", name, keycode); + int action = event.getAction(); // Enter key sends the message - if (keycode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { - getActivity().runOnUiThread(messageSender); + if (keycode == KeyEvent.KEYCODE_ENTER && action == KeyEvent.ACTION_UP) { + getActivity().runOnUiThread(message_sender); return true; } - // Check for text resizing keys(volume buttons) - else if (keycode == KeyEvent.KEYCODE_VOLUME_UP && event.getAction() == KeyEvent.ACTION_DOWN) { - float text_size = Float.parseFloat(prefs.getString("text_size", "10")) + 1; - // Max text_size of 30 - if (text_size > 30) { - text_size = 30; + // Check for text resizing keys (volume buttons) + if (keycode == KeyEvent.KEYCODE_VOLUME_DOWN || keycode == KeyEvent.KEYCODE_VOLUME_UP) { + if (action == KeyEvent.ACTION_UP) { + float text_size = Float.parseFloat(prefs.getString("text_size", "10")); + if (keycode == KeyEvent.KEYCODE_VOLUME_UP) { + if (text_size < 30) text_size += 1; + } else { + if (text_size > 5) text_size -= 1; + } + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("text_size", Float.toString(text_size)); + editor.commit(); } - SharedPreferences.Editor editor = prefs.edit(); - editor.putString("text_size", Float.toString(text_size)); - editor.commit(); return true; - } else if (keycode == KeyEvent.KEYCODE_VOLUME_DOWN && event.getAction() == KeyEvent.ACTION_DOWN) { - float text_size = Float.parseFloat(prefs.getString("text_size", "10")) - 1; - // Enforce a minimum text size of 5 - if (text_size < 5) { - text_size = 5; - } - SharedPreferences.Editor editor = prefs.edit(); - editor.putString("text_size", Float.toString(text_size)); - editor.commit(); + } + // try tab completion if we press tab or search + if ((keycode == KeyEvent.KEYCODE_TAB || keycode == KeyEvent.KEYCODE_SEARCH) && + action == KeyEvent.ACTION_DOWN) { + tryTabComplete(); return true; - } else if (keycode == KeyEvent.KEYCODE_VOLUME_DOWN || keycode == KeyEvent.KEYCODE_VOLUME_UP) { - return true;// Eat these keys - } // Try tab completion(using tab key, or search key) - else if ((keycode == KeyEvent.KEYCODE_TAB || keycode == KeyEvent.KEYCODE_SEARCH) - && event.getAction() == KeyEvent.ACTION_DOWN) { + } + return false; + } + + /** the only OnClickListener's method + ** our own send button or tab button pressed */ + @Override + public void onClick(View v) { + if (v.getId() == sendButton.getId()) + getActivity().runOnUiThread(message_sender); + else if (v.getId() == tabButton.getId()) tryTabComplete(); + } + + /** the only OnEditorActionListener's method + ** listens to keyboard's “send” press (NOT our button) */ + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_SEND) { + getActivity().runOnUiThread(message_sender); return true; - } else if (KeyEvent.isModifierKey(keycode) || keycode == KeyEvent.KEYCODE_TAB - || keycode == KeyEvent.KEYCODE_SEARCH) { - // If it was a modifier key(or tab/search), don't kill tabCompletingInProgress - return false; } - tabCompletingInProgress = false; return false; } + /** the only OnSharedPreferenceChangeListener's method */ @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals("tab_completion")) { + if (key.equals("tab_completion")) enableTabComplete = prefs.getBoolean("tab_completion", true); - } else if(key.equals("sendbtn_show") && sendButton != null) { - if (prefs.getBoolean("sendbtn_show", true)) { - sendButton.setVisibility(View.VISIBLE); - } else { - sendButton.setVisibility(View.GONE); - } - } else if(key.equals("tabbtn_show") && tabButton != null) { - if (prefs.getBoolean("tabbtn_show", false)) { - tabButton.setVisibility(View.VISIBLE); - } else { - tabButton.setVisibility(View.GONE); - } - } + else if (key.equals("sendbtn_show") && sendButton != null) + sendButton.setVisibility(prefs.getBoolean("sendbtn_show", true) ? View.VISIBLE : View.GONE); + else if (key.equals("tabbtn_show") && tabButton != null) + tabButton.setVisibility(prefs.getBoolean("tabbtn_show", true) ? View.VISIBLE : View.GONE); } - // Send button pressed - @Override - public void onClick(View v) { - if (sendButton.getId() == v.getId()) { - getActivity().runOnUiThread(messageSender); - } else if (tabButton.getId() == v.getId()) { - // do tab completion - tryTabComplete(); + ///////////////////////// + ///////////////////////// send + ///////////////////////// + + /** ends the message if there's anything to send + ** if user entered /buffer clear or /CL command, then clear the lines */ + private Runnable message_sender = new Runnable() { + @Override + public void run() { + String input = inputBox.getText().toString(); + if (input.length() > 0) { + if (input.equals("/CL") || input.equals("/buffer clear")) + chatlines_adapter.clearLines(); + relay.sendMessage("input " + name + " " + input + "\n"); + inputBox.setText(""); // this will reset tab completion + } } - } + }; + + ///////////////////////// + ///////////////////////// tab complete + ///////////////////////// - // Attempts to perform tab completion on the current input + /** attempts to perform tab completion on the current input */ private void tryTabComplete() { - if (!enableTabComplete || nickCache == null) + if (!enableTabComplete || nicks == null) return; String txt = inputBox.getText().toString(); - if (!tabCompletingInProgress) { + if (!tc_inprogress) { // find the end of the word to be completed // blabla nick| - tabCompleteWordEnd = inputBox.getSelectionStart(); - if (tabCompleteWordEnd <= 0) + tc_wordend = inputBox.getSelectionStart(); + if (tc_wordend <= 0) return; // find the beginning of the word to be completed // blabla |nick - tabCompleteWordStart = tabCompleteWordEnd; - while (tabCompleteWordStart > 0 && txt.charAt(tabCompleteWordStart - 1) != ' ') - tabCompleteWordStart--; + tc_wordstart = tc_wordend; + while (tc_wordstart > 0 && txt.charAt(tc_wordstart - 1) != ' ') + tc_wordstart--; // get the word to be completed, lowercase - if (tabCompleteWordStart == tabCompleteWordEnd) + if (tc_wordstart == tc_wordend) return; - String prefix = txt.substring(tabCompleteWordStart, tabCompleteWordEnd).toLowerCase(); + String prefix = txt.substring(tc_wordstart, tc_wordend).toLowerCase(); // compute a list of possible matches - // nickCache is ordered in last used comes first way, so we just pick whatever comes first + // nicks is ordered in last used comes first way, so we just pick whatever comes first // if computed list is empty, abort - tabCompleteMatches = new Vector(); - for (String possible : nickCache) - if (possible.toLowerCase().trim().startsWith(prefix)) - tabCompleteMatches.add(possible.trim()); - if (tabCompleteMatches.size() == 0) + tc_matches = new Vector(); + for (String nick : nicks) + if (nick.toLowerCase().trim().startsWith(prefix)) + tc_matches.add(nick.trim()); + if (tc_matches.size() == 0) return; - tabCompleteIndex = 0; + tc_index = 0; } else { - tabCompleteIndex = (tabCompleteIndex + 1) % tabCompleteMatches.size(); + tc_index = (tc_index + 1) % tc_matches.size(); } // get new nickname, adjust the end of the word marker // and finally set the text and place the cursor on the end of completed word - String nick = tabCompleteMatches.get(tabCompleteIndex); - if (tabCompleteWordStart == 0) + String nick = tc_matches.get(tc_index); + if (tc_wordstart == 0) nick += ": "; - inputBox.setText(txt.substring(0, tabCompleteWordStart) + nick + txt.substring(tabCompleteWordEnd)); - tabCompleteWordEnd = tabCompleteWordStart + nick.length(); - inputBox.setSelection(tabCompleteWordEnd); - // altering text in the input box sets tabCompletingInProgress to false, + inputBox.setText(txt.substring(0, tc_wordstart) + nick + txt.substring(tc_wordend)); + tc_wordend = tc_wordstart + nick.length(); + inputBox.setSelection(tc_wordend); + // altering text in the input box sets tc_inprogress to false, // so this is the last thing we do in this function: - tabCompletingInProgress = true; + tc_inprogress = true; } - // Sends the message if necessary - private Runnable messageSender = new Runnable() { - @Override - public void run() { - tabCompletingInProgress = false; - - String input = inputBox.getText().toString(); - if (input.length() == 0) { - return; // Ignore empty input box - } - - // Check if it was a /buffer clear, /CL command, then clear the lines - if (input.equals("/CL") || input.equals("/buffer clear")) { - chatlineAdapter.clearLines(); - } + public ArrayList getNicklist() { + return nicks; + } - String message = "input " + bufferName + " " + input; - inputBox.setText(""); - rsb.sendMessage(message + "\n"); - } - }; - - /* - * This is related to the tap and hold menu that appears when clicking on a message - */ + public String getShortBufferName() { + return shortname; + } + + ///////////////////////// + ///////////////////////// context menu + ///////////////////////// + + /** This is related to the tap and hold menu that appears when clicking on a message */ private static final int CONTEXT_MENU_COPY_TXT = Menu.FIRST; private static final int CONTEXT_MENU_COPY_URL = CONTEXT_MENU_COPY_TXT+1; private TextView contextMenuView = null; @@ -436,7 +514,7 @@ public boolean onContextItemSelected(MenuItem item) { if (item.getItemId() == CONTEXT_MENU_COPY_TXT) { CharSequence txt = contextMenuView.getText(); cm.setText(txt.toString()); - } else if (item.getItemId() >= CONTEXT_MENU_COPY_URL) { + } else if (item.getItemId() >= CONTEXT_MENU_COPY_URL) { // TODO: don't follow the url immediately URLSpan[] urls = contextMenuView.getUrls(); cm.setText(urls[item.getItemId() - CONTEXT_MENU_COPY_URL].getURL()); } @@ -449,13 +527,11 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn super.onCreateContextMenu(menu, v, menuInfo); if (!(v instanceof ListView)) return; - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - View selected = info.targetView; + View selected = ((AdapterView.AdapterContextMenuInfo) menuInfo).targetView; if (selected == null) return; - - TextView msg = (TextView)selected.findViewById(R.id.chatline_message); - if (msg==null) return; + TextView msg = (TextView) selected.findViewById(R.id.chatline_message); + if (msg == null) return; contextMenuView = msg; @@ -463,19 +539,28 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn menu.add(0, CONTEXT_MENU_COPY_TXT, 0, "Copy message text"); URLSpan[] urls = contextMenuView.getUrls(); - int i=0; - for(URLSpan url: urls) { - menu.add(0, CONTEXT_MENU_COPY_URL+i, 1, "URL: " + url.getURL()); + int i = 0; + for (URLSpan url: urls) { + menu.add(0, CONTEXT_MENU_COPY_URL + i, 1, "URL: " + url.getURL()); i++; } } + ///////////////////////// + ///////////////////////// TextWatcher stuff + ///////////////////////// + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} + /** invalidate tab completion progress on input box text change + ** tryTabComplete() will set it back if it modified the text causing this function to run */ @Override - public void afterTextChanged(Editable s) {tabCompletingInProgress = false;} + public void afterTextChanged(Editable s) { + if (false && DEBUG) logger.warn("{} afterTextChanged(...)", name); + tc_inprogress = false; + } } diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.java index 1214b8133..c5f022399 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.java @@ -1,6 +1,7 @@ package com.ubergeek42.WeechatAndroid.fragments; import java.util.ArrayList; +import java.util.Iterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,240 +27,241 @@ import com.actionbarsherlock.app.SherlockListFragment; import com.ubergeek42.WeechatAndroid.BufferListAdapter; +import com.ubergeek42.WeechatAndroid.BuildConfig; import com.ubergeek42.WeechatAndroid.R; +import com.ubergeek42.WeechatAndroid.WeechatActivity; import com.ubergeek42.WeechatAndroid.service.RelayService; import com.ubergeek42.WeechatAndroid.service.RelayServiceBinder; import com.ubergeek42.weechat.Buffer; import com.ubergeek42.weechat.relay.RelayConnectionHandler; import com.ubergeek42.weechat.relay.messagehandler.BufferManager; -import com.ubergeek42.weechat.relay.messagehandler.BufferManagerObserver; +import com.ubergeek42.weechat.relay.messagehandler.BuffersChangedObserver; import com.ubergeek42.weechat.relay.protocol.RelayObject; public class BufferListFragment extends SherlockListFragment implements RelayConnectionHandler, - BufferManagerObserver, OnSharedPreferenceChangeListener { - private static Logger logger = LoggerFactory.getLogger(BufferListFragment.class); - private static final String[] message = { "Press Menu->Connect to get started" }; + BuffersChangedObserver, OnSharedPreferenceChangeListener { - private boolean mBound = false; - private RelayServiceBinder rsb; + private static Logger logger = LoggerFactory.getLogger("list"); + final private static boolean DEBUG = BuildConfig.DEBUG && true; - private BufferListAdapter m_adapter; + private static final String[] empty_list = { "Press Menu->Connect to get started" }; - OnBufferSelectedListener mCallback; - private BufferManager bufferManager; + private RelayServiceBinder relay; + private BufferListAdapter adapter; + private BufferManager buffer_manager; - // Used for filtering the list of buffers displayed private EditText bufferlistFilter; - private SharedPreferences prefs; - private boolean enableBufferSorting; - private boolean hideServerBuffers; - - // Are we attached to an activity? - private boolean attached; - - - // The container Activity must implement this interface so the frag can deliver messages - public interface OnBufferSelectedListener { - /** - * Called by BufferlistFragment when a list item is selected - * - * @param fullBufferName - */ - public void onBufferSelected(String fullBufferName); - } + private boolean hide_server_buffers; // a preference + + ///////////////////////// + ///////////////////////// lifecycle + ///////////////////////// + /** This makes sure that the container activity has implemented + ** the callback interface. If not, it throws an exception. */ @Override public void onAttach(Activity activity) { + if (DEBUG) logger.warn("onAttach()"); super.onAttach(activity); - - logger.debug("BufferListFragment onAttach called"); - - // This makes sure that the container activity has implemented - // the callback interface. If not, it throws an exception. - try { - mCallback = (OnBufferSelectedListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement OnBufferSelectedListener"); - } - } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.bufferlist, null); - bufferlistFilter = (EditText) v.findViewById(R.id.bufferlist_filter); - bufferlistFilter.addTextChangedListener(filterTextWatcher); - if (prefs.getBoolean("show_buffer_filter", false)) { - bufferlistFilter.setVisibility(View.VISIBLE); - } else { - bufferlistFilter.setVisibility(View.GONE); - } - return v; + if (!(activity instanceof WeechatActivity)) + throw new ClassCastException(activity.toString() + " must be WeechatActivity"); } @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) logger.warn("onCreate()"); super.onCreate(savedInstanceState); - setRetainInstance(true); - - setListAdapter(new ArrayAdapter(getActivity(), R.layout.tips_list_item, message)); prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getBaseContext()); prefs.registerOnSharedPreferenceChangeListener(this); - enableBufferSorting = prefs.getBoolean("sort_buffers", true); - hideServerBuffers = prefs.getBoolean("hide_server_buffers", true); - - - // TODO ondestroy: bufferlistFilter.removeTextChangedListener(filterTextWatcher); + hide_server_buffers = prefs.getBoolean("hide_server_buffers", true); } @Override - public void onStart() { - super.onStart(); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (DEBUG) logger.warn("onCreateView()"); + View v = inflater.inflate(R.layout.bufferlist, null); + bufferlistFilter = (EditText) v.findViewById(R.id.bufferlist_filter); + bufferlistFilter.addTextChangedListener(filterTextWatcher); + bufferlistFilter.setVisibility(prefs.getBoolean("show_buffer_filter", false) ? View.VISIBLE : View.GONE); + return v; + } - // Bind to the Relay Service - if (mBound == false) { - getActivity().bindService(new Intent(getActivity(), RelayService.class), mConnection, - Context.BIND_AUTO_CREATE); - } + @Override + public void onDestroyView() { + if (DEBUG) logger.warn("onDestroyView()"); + super.onDestroyView(); + bufferlistFilter.removeTextChangedListener(filterTextWatcher); + } - attached = true; + /** relay is ALWAYS null on start + ** binding to relay service results in: + ** service_connection.onServiceConnected() which: + ** binds to RelayConnectionHandler, and, + ** if connected, + ** calls on Connect() + ** which results in buffer_manager.setOnChangedHandler() + ** else + ** calls onDisconnect(), which sets the “please connect” message */ + @Override + public void onStart() { + if (DEBUG) logger.warn("onStart()"); + super.onStart(); + getActivity().bindService(new Intent(getActivity(), RelayService.class), service_connection, + Context.BIND_AUTO_CREATE); } + /** here we remove RelayConnectionHandler & buffer_manager's onchange handler + ** it should be safe to call all unbinding functions */ @Override public void onStop() { + if (DEBUG) logger.warn("onStop()"); super.onStop(); - attached = false; - if (mBound) { - rsb.removeRelayConnectionHandler(BufferListFragment.this); - getActivity().unbindService(mConnection); - mBound = false; + if (buffer_manager != null) { + buffer_manager.clearOnChangedHandler(); // buffer change watcher (safe to call) + buffer_manager = null; + } + if (relay != null) { + relay.removeRelayConnectionHandler(BufferListFragment.this); // connect/disconnect watcher (safe to call) + relay = null; } + getActivity().unbindService(service_connection); // TODO safe to call? } - ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - rsb = (RelayServiceBinder) service; - rsb.addRelayConnectionHandler(BufferListFragment.this); + ///////////////////////// + ///////////////////////// + ///////////////////////// - mBound = true; - if (rsb.isConnected()) { + ServiceConnection service_connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { // TODO can this be called after Fragment.onStop()? + if (DEBUG) logger.warn("onServiceConnected()"); + relay = (RelayServiceBinder) service; + if (relay.isConnection(RelayService.CONNECTED)) BufferListFragment.this.onConnect(); - } + else + BufferListFragment.this.onDisconnect(); + relay.addRelayConnectionHandler(BufferListFragment.this); // connect/disconnect watcher } @Override public void onServiceDisconnected(ComponentName name) { - bufferManager.clearOnChangedHandler(); - - mBound = false; - rsb = null; + if (DEBUG) logger.error("onServiceDisconnected() <- should not happen!"); + relay = null; + buffer_manager = null; } }; + ///////////////////////// + ///////////////////////// method of android.support.v4.app.ListFragment + ///////////////////////// + + /** this is the mother method, it actually opens buffers */ @Override public void onListItemClick(ListView l, View v, int position, long id) { - Object obj = getListView().getItemAtPosition(position); - if (obj instanceof Buffer) { - // Get the buffer they clicked - Buffer b = (Buffer) obj; - - // Tell our parent to load the buffer - mCallback.onBufferSelected(b.getFullName()); - } + if (DEBUG) logger.warn("onListItemClick(..., ..., {}, ...)", position); + Object obj = getListView().getItemAtPosition(position); + if (obj instanceof Buffer) { + Buffer b = (Buffer) obj; + ((WeechatActivity) getActivity()).openBuffer(b.getFullName()); + } } - @Override - public void onConnecting() { + ///////////////////////// + ///////////////////////// methods of RelayConnectionHandler + ///////////////////////// - } + @Override + public void onConnecting() {} + /** called on actual connection event and from other methods + ** creates and updates the buffer list */ @Override public void onConnect() { - if (rsb != null && rsb.isConnected()) { - // Create and update the buffer list when we connect to the service - m_adapter = new BufferListAdapter(getActivity()); - bufferManager = rsb.getBufferManager(); - m_adapter.setBuffers(bufferManager.getBuffers()); - bufferManager.setOnChangedHandler(BufferListFragment.this); - - m_adapter.enableSorting(prefs.getBoolean("sort_buffers", true)); - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - setListAdapter(m_adapter); - } - }); - - onBuffersChanged(); - } + if (DEBUG) logger.warn("onConnect()"); + adapter = new BufferListAdapter(getActivity()); + buffer_manager = relay.getBufferManager(); + buffer_manager.setOnChangedHandler(this); // buffer change watcher + adapter.enableSorting(prefs.getBoolean("sort_buffers", true)); + adapter.filterBuffers(bufferlistFilter.getText().toString()); // resume sorting + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + setListAdapter(adapter); + } + }); + onBuffersChanged(); } @Override - public void onAuthenticated() { + public void onAuthenticated() {} + /** this is called when the list of buffers has been finalised + ** technically buffers should be listening to this but let's do it here for now... */ + @Override + public void onBuffersListed() { + logger.warn("onBuffersListed()"); } + /** called on actual disconnect even and from other methods + ** displays empty list */ @Override public void onDisconnect() { + if (DEBUG) logger.warn("onDisconnect()"); // Create and update the buffer list when we connect to the service - Activity act = getActivity(); - if (act != null) { - act.runOnUiThread(new Runnable() { + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { @Override public void run() { setListAdapter(new ArrayAdapter(getActivity(), R.layout.tips_list_item, - message)); + empty_list)); } }); } } + + /** do nothing: activity is handling errors */ @Override - public void onError(String err, Object extraInfo) { - // We don't do anything with the error message(the activity/service does though) - } + public void onError(String err, Object extraInfo) {} + + ///////////////////////// + ///////////////////////// BuffersChangedObserver + ///////////////////////// + /** the only method and the only implementer of BuffersChangedObserver */ @Override public void onBuffersChanged() { - // Need to make sure we are attached to an activity, otherwise getActivity can be null - if (!attached) { - return; + if (false && DEBUG) logger.warn("onBuffersChanged()"); + final ArrayList buffers = buffer_manager.getBuffersCopy(); + if (hide_server_buffers) { + Iterator iter = buffers.iterator(); + while (iter.hasNext()) { + RelayObject relayobj = iter.next().getLocalVar("type"); + if (relayobj != null && relayobj.asString().equals("server")) + iter.remove(); + } } - getActivity().runOnUiThread(new Runnable() { @Override public void run() { - ArrayList buffers; - - buffers = bufferManager.getBuffers(); - - // Remove server buffers(if unwanted) - if (hideServerBuffers) { - ArrayList newBuffers = new ArrayList(); - for (Buffer b : buffers) { - RelayObject relayobj = b.getLocalVar("type"); - if (relayobj != null && relayobj.asString().equals("server")) { - continue; - } - newBuffers.add(b); - } - buffers = newBuffers; - } - - m_adapter.setBuffers(buffers); + if (buffer_manager != null) adapter.setBuffers(buffers); // are we still online? } }); } + ///////////////////////// + ///////////////////////// other + ///////////////////////// + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (DEBUG) logger.warn("onSharedPreferenceChanged()"); if (key.equals("sort_buffers")) { - if (m_adapter != null) - m_adapter.enableSorting(prefs.getBoolean("sort_buffers", true)); + if (adapter != null) + adapter.enableSorting(prefs.getBoolean("sort_buffers", true)); } else if (key.equals("hide_server_buffers")) { - hideServerBuffers = prefs.getBoolean("hide_server_buffers", true); - onBuffersChanged(); + hide_server_buffers = prefs.getBoolean("hide_server_buffers", true); } else if(key.equals("show_buffer_filter") && bufferlistFilter != null) { if (prefs.getBoolean("show_buffer_filter", false)) { bufferlistFilter.setVisibility(View.VISIBLE); @@ -268,18 +270,22 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } } } - - // TextWatcher object for filtering the buffer list + + /** TextWatcher object used for filtering the buffer list */ private TextWatcher filterTextWatcher = new TextWatcher() { @Override - public void afterTextChanged(Editable a) { } + public void afterTextChanged(Editable a) {} + @Override public void beforeTextChanged(CharSequence arg0, int a, int b, int c) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - if (m_adapter!=null) { - m_adapter.filterBuffers(s.toString()); - } + if (false && DEBUG) logger.warn("onTextChanged({}, ...)", s); + if (adapter != null) + adapter.filterBuffers(s.toString()); } }; + + } diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayService.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayService.java index 214339b21..8416ef936 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayService.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayService.java @@ -65,6 +65,7 @@ import com.ubergeek42.weechat.relay.messagehandler.NicklistHandler; import com.ubergeek42.weechat.relay.messagehandler.UpgradeHandler; import com.ubergeek42.weechat.relay.messagehandler.UpgradeObserver; +import com.ubergeek42.weechat.relay.protocol.RelayObject; public class RelayService extends Service implements RelayConnectionHandler, OnSharedPreferenceChangeListener, HotlistObserver, UpgradeObserver { @@ -105,6 +106,21 @@ public class RelayService extends Service implements RelayConnectionHandler, SSLHandler certmanager; X509Certificate untrustedCert; + // for some reason, this java can't have binary literals... + public final static int DISCONNECTED = Integer.parseInt("00001", 2); + public final static int CONNECTING = Integer.parseInt("00010", 2); + public final static int CONNECTED = Integer.parseInt("00100", 2); + public final static int AUTHENTICATED = Integer.parseInt("01000", 2); + public final static int BUFFERS_LISTED = Integer.parseInt("10000", 2); + int connection_status = DISCONNECTED; + + /** check status of connection + ** @param status one of DISCONNECTED, CONNECTING, CONNECTED, AUTHENTICATED, BUFFERS_LISTED + ** @return true if connection corresponds to one of these */ + public boolean isConnection(int status) { + return (connection_status & status) != 0; + } + @Override public IBinder onBind(Intent arg0) { return new RelayServiceBinder(this); @@ -299,17 +315,15 @@ public void run() { } @Override - public void onConnecting() { - - } + public void onConnecting() {connection_status = CONNECTING;} @Override - public void onConnect() { - - } + public void onConnect() {connection_status = CONNECTED;} @Override public void onAuthenticated() { + connection_status = CONNECTED | AUTHENTICATED; + if (disconnected == true) { showNotification(getString(R.string.notification_reconnected_to) + host, getString(R.string.notification_connected_to) + host); } else { @@ -325,6 +339,9 @@ public void onAuthenticated() { // Handle us getting a listing of the buffers relayConnection.addHandler("listbuffers", bufferManager); + // this will call onBuffersListed + // ORDER OF ADDITION IS IMPORTANT, since LinkedHashSet is used in RelayConnection + relayConnection.addHandler("listbuffers", new BuffersListedObserver()); // Handle weechat event messages regarding buffers relayConnection.addHandler("_buffer_opened", bufferManager); @@ -367,8 +384,17 @@ public void onAuthenticated() { } } + @Override + public void onBuffersListed() { + connection_status = CONNECTED | AUTHENTICATED | BUFFERS_LISTED; + for (RelayConnectionHandler rch : connectionHandlers) { + rch.onBuffersListed(); + } + } + @Override public void onDisconnect() { + connection_status = DISCONNECTED; if (disconnected) { return; // Only do the disconnect handler once } @@ -469,4 +495,11 @@ public void run() { }); upgrading.start(); } + + private class BuffersListedObserver implements RelayMessageHandler { + @Override + public void handleMessage(RelayObject obj, String id) { + RelayService.this.onBuffersListed(); + } + } } diff --git a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayServiceBinder.java b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayServiceBinder.java index c387e91eb..dcd087768 100644 --- a/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayServiceBinder.java +++ b/weechat-android/src/main/java/com/ubergeek42/WeechatAndroid/service/RelayServiceBinder.java @@ -61,11 +61,9 @@ public String getPassword() { return relayService.pass; } - public boolean isConnected() { - if (relayService.relayConnection == null) { - return false; - } - return relayService.relayConnection.isConnected(); + /** returns true if connection status corresponds to given connection */ + public boolean isConnection(int status) { + return relayService.isConnection(status); } public boolean connect() { @@ -95,7 +93,7 @@ public void shutdown() { * @return a Buffer object for the given buffer */ public Buffer getBufferByName(String bufferName) { - if (isConnected()) + if (isConnection(RelayService.CONNECTED)) return relayService.bufferManager.findByName(bufferName); return null; } @@ -140,7 +138,7 @@ public void resetNotifications() { public void subscribeBuffer(String bufferPointer) { Buffer buf = relayService.bufferManager.findByPointer(bufferPointer); // Get the last MAXLINES for each buffer(only if we don't already have at least MAXLINES) - if (buf.getLines().size() < Buffer.MAXLINES) { + if (buf.getLines().size() < Buffer.MAXLINES) { // TODO: don't request a copy (getLines()) relayService.relayConnection.sendMsg("(listlines_reverse) hdata buffer:" + bufferPointer + "/own_lines/last_line(-" + Buffer.MAXLINES + ")/data date,displayed,prefix,message,highlight,tags_array"); diff --git a/weechat-relay-example/src/main/java/com/ubergeek42/relayexample/RelayExample.java b/weechat-relay-example/src/main/java/com/ubergeek42/relayexample/RelayExample.java index 41793471e..6fa0f4991 100644 --- a/weechat-relay-example/src/main/java/com/ubergeek42/relayexample/RelayExample.java +++ b/weechat-relay-example/src/main/java/com/ubergeek42/relayexample/RelayExample.java @@ -16,20 +16,15 @@ package com.ubergeek42.relayexample; import java.io.IOException; -import java.util.LinkedList; import com.ubergeek42.weechat.Buffer; -import com.ubergeek42.weechat.BufferLine; import com.ubergeek42.weechat.relay.RelayConnection; import com.ubergeek42.weechat.relay.RelayConnectionHandler; -import com.ubergeek42.weechat.relay.connection.PlainConnection; import com.ubergeek42.weechat.relay.connection.SSLConnection; import com.ubergeek42.weechat.relay.messagehandler.BufferManager; -import com.ubergeek42.weechat.relay.messagehandler.BufferManagerObserver; -import com.ubergeek42.weechat.relay.messagehandler.LineHandler; -import com.ubergeek42.weechat.relay.messagehandler.NicklistHandler; +import com.ubergeek42.weechat.relay.messagehandler.BuffersChangedObserver; -public class RelayExample implements BufferManagerObserver, RelayConnectionHandler { +public class RelayExample implements BuffersChangedObserver, RelayConnectionHandler { static BufferManager bufferManager = new BufferManager(); private RelayConnection relay; public static void main(String[] args) throws IOException { @@ -119,6 +114,9 @@ public void onAuthenticated() { relay.disconnect(); } + @Override + public void onBuffersListed() {} + @Override public void onDisconnect() { System.out.println("Disconnected..."); diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnection.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnection.java index 6d6333b35..555c8483b 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnection.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnection.java @@ -16,32 +16,16 @@ package com.ubergeek42.weechat.relay; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; - -import javax.net.SocketFactory; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManagerFactory; +import java.util.LinkedHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.Session; import com.ubergeek42.weechat.Helper; import com.ubergeek42.weechat.relay.connection.IConnection; -import com.ubergeek42.weechat.relay.connection.PlainConnection; import com.ubergeek42.weechat.relay.protocol.Data; import com.ubergeek42.weechat.relay.protocol.RelayObject; @@ -51,10 +35,11 @@ * @author ubergeek42 */ public class RelayConnection implements RelayConnectionHandler { - private static Logger logger = LoggerFactory.getLogger(RelayConnection.class); + final private static boolean DEBUG = false; + private static Logger logger = LoggerFactory.getLogger("RelayConnection"); private String password; - private HashMap> messageHandlers = new HashMap>(); + private HashMap> messageHandlers = new HashMap>(); IConnection conn; @@ -200,7 +185,7 @@ public void run() { } } } - logger.debug("disconnected - socketreader thread stopping"); + if (DEBUG) logger.debug("socketReader: disconnected, thread stopping"); conn.disconnect(); } }); @@ -214,9 +199,9 @@ public void run() { * - The object to receive the callback */ public void addHandler(String id, RelayMessageHandler wmh) { - HashSet currentHandlers = messageHandlers.get(id); + LinkedHashSet currentHandlers = messageHandlers.get(id); if (currentHandlers == null) { - currentHandlers = new HashSet(); + currentHandlers = new LinkedHashSet(); } currentHandlers.add(wmh); messageHandlers.put(id, currentHandlers); @@ -230,7 +215,7 @@ public void addHandler(String id, RelayMessageHandler wmh) { */ private void handleMessage(RelayMessage msg) { String id = msg.getID(); - logger.debug("handling message " + id); + if (DEBUG) logger.debug("handling message {}", id); if (messageHandlers.containsKey(id)) { HashSet handlers = messageHandlers.get(id); for (RelayMessageHandler rmh : handlers) { @@ -243,34 +228,34 @@ private void handleMessage(RelayMessage msg) { } } } else { - logger.debug("Unhandled message: " + id); + if (DEBUG) logger.debug("Unhandled message: {}", id); } } @Override public void onConnecting() { - System.out.println("RelayConnection.onConnecting"); - logger.debug("RelayConnection.onConnecting"); + System.out.println("onConnecting()"); + if (DEBUG) logger.debug("RelayConnection.onConnecting"); } @Override public void onConnect() { - System.out.println("RelayConnection.onConnect"); - logger.debug("RelayConnection.onConnect"); + if (DEBUG) logger.debug("onConnect()"); postConnectionSetup(); } @Override public void onAuthenticated() { - System.out.println("RelayConnection.onAuthenticated"); - logger.debug("RelayConnection.onAuthenticated"); + if (DEBUG) logger.debug("onAuthenticated()"); } + @Override + public void onBuffersListed() {} + @Override public void onDisconnect() { - System.out.println("RelayConnection.onDisconnect"); - logger.debug("RelayConnection.onDisconnect"); + if (DEBUG) logger.debug("onDisconnect()"); // If the thread is still reading data if (socketReader.isAlive()) { @@ -280,8 +265,7 @@ public void onDisconnect() { @Override public void onError(String err, Object extraInfo) { - System.out.println("RelayConnection.onError"); - logger.debug("RelayConnection.onError"); + if (DEBUG) logger.debug("onError()"); } diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnectionHandler.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnectionHandler.java index 5fa71de79..14eaa2ef0 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnectionHandler.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/RelayConnectionHandler.java @@ -40,6 +40,12 @@ public interface RelayConnectionHandler { */ public void onAuthenticated(); + /** + * Called when the initial list of buffers has been passed to the relay client. After this + * method call client can assume normal workflow follows. + */ + public void onBuffersListed(); + /** * Called when the server is disconnected, either through error, timeout, or because the client * requested a disconnect. diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/connection/AbstractConnection.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/connection/AbstractConnection.java index 96baea520..ad22bc58e 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/connection/AbstractConnection.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/connection/AbstractConnection.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import java.net.SocketException; import java.util.ArrayList; public abstract class AbstractConnection implements IConnection { @@ -67,10 +68,10 @@ public void disconnect() { //connector.stop(); // FIXME: deprecated, should probably find a better way to do this } - // If we're connected, tell weechat we're going away - if (connected) { + // Try telling weechat we're going away + try { out_stream.write("quit\n".getBytes()); - } + } catch (SocketException e) {} // Close all of our streams/sockets connected = false; diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManager.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManager.java index 16a56e7a7..addc789a5 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManager.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManager.java @@ -34,10 +34,11 @@ * */ public class BufferManager implements RelayMessageHandler { - private static Logger logger = LoggerFactory.getLogger(BufferManager.class); + private static Logger logger = LoggerFactory.getLogger("BufferManager"); + final private static boolean DEBUG = false; ArrayList buffers = new ArrayList(); - private BufferManagerObserver onChangeObserver; + private BuffersChangedObserver onChangeObserver; /** * Locate and returns a Buffer object based on it's pointer @@ -75,7 +76,7 @@ public Buffer findByName(String bufferName) { * Get the list of buffers */ @SuppressWarnings("unchecked") - public ArrayList getBuffers() { + public ArrayList getBuffersCopy() { return (ArrayList) buffers.clone(); } @@ -105,7 +106,8 @@ public int getNumBuffers() { * @param bo * - The observer to receive notifications */ - public void setOnChangedHandler(BufferManagerObserver bo) { + public void setOnChangedHandler(BuffersChangedObserver bo) { + if (DEBUG) logger.warn("setOnChangedHandler(...)"); this.onChangeObserver = bo; } @@ -113,6 +115,7 @@ public void setOnChangedHandler(BufferManagerObserver bo) { * Clears the buffer change observer */ public void clearOnChangedHandler() { + if (DEBUG) logger.warn("clearOnChangedHandler()"); this.onChangeObserver = null; } @@ -121,6 +124,7 @@ public void clearOnChangedHandler() { * notifying about unread lines/highlights */ public void buffersChanged() { + if (DEBUG) logger.warn("buffersChanged()"); if (onChangeObserver != null) { onChangeObserver.onBuffersChanged(); } @@ -129,7 +133,7 @@ public void buffersChanged() { @Override public void handleMessage(RelayObject obj, String id) { if (!(obj instanceof Hdata)) { - logger.debug("Expected hdata, got " + obj.getClass()); + if (DEBUG) logger.error("Expected hdata, got {}", obj.getClass()); return; } Hdata whdata = (Hdata) obj; @@ -169,7 +173,7 @@ public void handleMessage(RelayObject obj, String id) { } else { Buffer wb = findByPointer(hde.getPointer(0)); if (wb == null) { - logger.debug("Unable to find buffer to update"); + if (DEBUG) logger.error("Unable to find buffer to update"); return; } if (id.equals("_buffer_type_changed")) { @@ -211,7 +215,7 @@ public void handleMessage(RelayObject obj, String id) { } } } else { - logger.debug("Unknown message ID: \"" + id + "\""); + if (DEBUG) logger.warn("Unknown message ID: '{}'", id); } } } diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManagerObserver.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BuffersChangedObserver.java similarity index 96% rename from weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManagerObserver.java rename to weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BuffersChangedObserver.java index 08a32c563..7a131b43a 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BufferManagerObserver.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/BuffersChangedObserver.java @@ -21,7 +21,7 @@ * @author ubergeek42 * */ -public interface BufferManagerObserver { +public interface BuffersChangedObserver { /** * Called whenever a buffer changes(or is added/removed) */ diff --git a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/HotlistManager.java b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/HotlistManager.java index 28eb94453..1d9f735da 100644 --- a/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/HotlistManager.java +++ b/weechat-relay/src/main/java/com/ubergeek42/weechat/relay/messagehandler/HotlistManager.java @@ -40,7 +40,8 @@ * */ public class HotlistManager implements RelayMessageHandler { - private static Logger logger = LoggerFactory.getLogger(HotlistManager.class); + final private static boolean DEBUG = false; + private static Logger logger = LoggerFactory.getLogger("HotlistManager"); ArrayList hotlist = new ArrayList(); private HotlistManagerObserver onChangeObserver; @@ -129,8 +130,7 @@ public void handleMessage(RelayObject obj, String id) { int tagCount = tagsArray.length(); if (tagCount == 0) { // All important messages have tags - logger.debug("Found no tags in buffer:" + b.getFullName() - + ",skipping line."); + if (DEBUG) logger.debug("handleMessage(): found no tags in buffer '{}', ,skipping line.", b.getFullName()); continue; } for (int ai = 0; ai < tagCount; ai++) { @@ -138,7 +138,7 @@ public void handleMessage(RelayObject obj, String id) { if (tag.equals("irc_smart_filter") || tag.equals("irc_mode") || tag.equals("irc_quit") || tag.equals("irc_join") || tag.equals("notify_none")) { - logger.debug("Found tag:" + tag + ",skipping line."); + if (DEBUG) logger.debug("handleMessage(): found tag: '{}', skipping line.", tag); continue outer; } }