From 71d5127782c1d2a81a4f8c8d050375bb38263269 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Oct 2020 12:14:46 -0700 Subject: [PATCH 1/2] cleanup bt code --- .../java/org/eehouse/android/xw4/BTUtils.java | 286 ++++++++---------- 1 file changed, 122 insertions(+), 164 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java index a3de80b2f..d2800a432 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java @@ -61,6 +61,9 @@ public class BTUtils { private static final String BOGUS_MARSHMALLOW_ADDR = "02:00:00:00:00:00"; private static final int MAX_PACKET_LEN = 4 * 1024; private static int CONNECT_SLEEP_MS = 2500; + private static Set sListeners = new HashSet<>(); + private static Map sSenders = new HashMap<>(); + private static Map s_namesToAddrs; private enum BTCmd { BAD_PROTO, @@ -69,14 +72,14 @@ public class BTUtils { SCAN, INVITE, INVITE_ACCPT, - INVITE_DECL, // unused + _INVITE_DECL, // unused INVITE_DUPID, - INVITE_FAILED, // generic error + _INVITE_FAILED, // generic error, and unused MESG_SEND, MESG_ACCPT, - MESG_DECL, // unused + _MESG_DECL, // unused MESG_GAMEGONE, - REMOVE_FOR, // unused + _REMOVE_FOR, // unused INVITE_DUP_INVITE, }; @@ -115,6 +118,18 @@ public class BTUtils { } } + static BluetoothAdapter getAdapterIf() + { + BluetoothAdapter result = null; + // Later this will change to include at least a test whether we're + // running as background user account, a situation in which BT crashes + // a lot inside the OS. + if ( !sBackUser.get() ) { + result = BluetoothAdapter.getDefaultAdapter(); + } + return result; + } + static void init( Context context, String appName, UUID uuid ) { Log.d( TAG, "init()" ); @@ -151,7 +166,7 @@ public class BTUtils { public static boolean isBogusAddr( String addr ) { boolean result = BOGUS_MARSHMALLOW_ADDR.equals( addr ); - Log.d( TAG, "isBogusAddr(%s) => %b", addr, result ); + // Log.d( TAG, "isBogusAddr(%s) => %b", addr, result ); return result; } @@ -177,7 +192,7 @@ public class BTUtils { public static void setAmForeground() { - sBackUser.set(false); + sBackUser.set( false ); } private static String nameForAddr( BluetoothAdapter adapter, String btAddr ) @@ -197,8 +212,7 @@ public class BTUtils { int nSent = -1; String name = targetAddr.bt_hostName; if ( isActivePeer( name ) ) { - String btAddr = getSafeAddr(targetAddr); - getPA( btAddr ).addMsg( gameID, buf, msgID ); + getPA( getSafeAddr(targetAddr) ).addMsg( gameID, buf, msgID ); } else { Log.d( TAG, "sendPacket(): addressee %s unknown so dropping", name ); } @@ -221,7 +235,6 @@ public class BTUtils { getPA( btAddr ).addInvite( nli ); } - private static Set sListeners = new HashSet<>(); public static void addScanListener( ScanListener listener ) { synchronized ( sListeners ) { @@ -238,7 +251,6 @@ public class BTUtils { private static void callListeners( BluetoothDevice dev ) { - Log.d( TAG, "callListeners(%s)", dev.getName() ); synchronized ( sListeners ) { for ( ScanListener listener : sListeners ) { listener.onDeviceScanned( dev ); @@ -248,7 +260,6 @@ public class BTUtils { public static int scan( Context context, int timeoutMS ) { - Log.e( TAG, "scan(): not implemented" ); Set devs = getCandidates(); int count = devs.size(); if ( 0 < count ) { @@ -257,18 +268,6 @@ public class BTUtils { return count; } - static BluetoothAdapter getAdapterIf() - { - BluetoothAdapter result = null; - // Later this will change to include at least a test whether we're - // running as background user account, a situation in which BT crashes - // a lot inside the OS. - if ( !sBackUser.get() ) { - result = BluetoothAdapter.getDefaultAdapter(); - } - return result; - } - private static boolean isActivePeer( String devName ) { boolean result = false; @@ -280,10 +279,13 @@ public class BTUtils { break; } } - Log.d( TAG, "isActivePeer(%s) => %b", devName, result ); + if ( !result ) { + Log.d( TAG, "isActivePeer(%s) => FALSE", devName ); + } return result; } + private static boolean sHaveLogged = false; static Set getCandidates() { Set result = new HashSet<>(); @@ -299,10 +301,15 @@ public class BTUtils { case Major.PERIPHERAL: break; default: + if ( ! sHaveLogged ) { + Log.d( TAG, "getCandidates(): adding %s of type %d", + dev.getName(), clazz ); + } result.add( dev ); break; } } + sHaveLogged = true; } return result; } @@ -325,7 +332,7 @@ public class BTUtils { { Assert.assertTrue( !TextUtils.isEmpty(addr) ); PacketAccumulator pa = getSenderFor( addr ).wake(); - return pa; // .setService( this ); + return pa; } private static void removeSenderFor( PacketAccumulator pa ) @@ -341,7 +348,6 @@ public class BTUtils { } } - private static Map sSenders = new HashMap<>(); private static PacketAccumulator getSenderFor( String addr ) { return getSenderFor( addr, true ); @@ -361,8 +367,54 @@ public class BTUtils { return result; } + private static String getSafeAddr( CommsAddrRec addr ) + { + String btAddr = addr.bt_btAddr; + if ( TextUtils.isEmpty(btAddr) || BOGUS_MARSHMALLOW_ADDR.equals( btAddr ) ) { + final String original = btAddr; + String btName = addr.bt_hostName; + if ( null == s_namesToAddrs ) { + s_namesToAddrs = new HashMap<>(); + } + + if ( s_namesToAddrs.containsKey( btName ) ) { + btAddr = s_namesToAddrs.get( btName ); + } else { + btAddr = null; + } + if ( null == btAddr ) { + Set devs = getCandidates(); + for ( BluetoothDevice dev : devs ) { + // Log.d( TAG, "%s => %s", dev.getName(), dev.getAddress() ); + if ( btName.equals( dev.getName() ) ) { + btAddr = dev.getAddress(); + s_namesToAddrs.put( btName, btAddr ); + break; + } + } + } + Log.d( TAG, "getSafeAddr(\"%s\") => %s", original, btAddr ); + } + return btAddr; + } + + private static void clearInstance( Thread[] holder, Thread instance ) + { + synchronized ( holder ) { + if ( holder[0] == null ) { + // nothing to do + } else if ( holder[0] == instance ) { + holder[0] = null; + } else { + Log.e( TAG, "clearInstance(): cur instance %s not == %s", + holder[0], instance ); + } + } + + } + private static class ScanThread extends Thread { - static ScanThread[] sInstance = {null}; + private static Thread[] sInstance = {null}; private int mTimeoutMS; private Set mDevs; @@ -377,20 +429,6 @@ public class BTUtils { } } - private static void clearInstance( ScanThread instance ) - { - synchronized (sInstance) { - if ( sInstance[0] == null ) { - // nothing to do - } else if ( sInstance[0] == instance ) { - sInstance[0] = null; - } else { - Log.e( TAG, "ScanThread.clearInstance(): cur instance %s not == %s", - sInstance[0], instance ); - } - } - } - private ScanThread( int timeoutMS, Set devs ) { mTimeoutMS = timeoutMS; @@ -414,6 +452,8 @@ public class BTUtils { pas.put( dev, pa ); } + // PENDING: figure out how to let these send results the minute + // they have one!!! for ( BluetoothDevice dev : pas.keySet() ) { PacketAccumulator pa = pas.get( dev ); try { @@ -426,12 +466,29 @@ public class BTUtils { } } - clearInstance( this ); + synchronized ( sListeners ) { + for ( ScanListener listener : sListeners ) { + listener.onScanDone(); + } + } + + clearInstance( sInstance, this ); } } private static class PacketAccumulator extends Thread { + private static class OutputPair { + ByteArrayOutputStream bos; + DataOutputStream dos; + OutputPair() { + bos = new ByteArrayOutputStream(); + dos = new DataOutputStream( bos ); + } + + int length() { return bos.toByteArray().length; } + } + private static class MsgElem { BTCmd mCmd; String mMsgID; @@ -545,16 +602,6 @@ public class BTUtils { return this; } - // synchronized PacketAccumulator setService( BTService service ) - // { - // Assert.assertNotNull( service ); - // mService = service; - - // notifyAll(); - - // return this; - // } - PacketAccumulator setExitWhenEmpty() { mExitWhenEmpty = true; @@ -757,7 +804,7 @@ public class BTUtils { } } } - Log.d( TAG, "%s.figureWait() => %dms", this, result ); + // Log.d( TAG, "%s.figureWait() => %dms", this, result ); return result; } @@ -775,7 +822,7 @@ public class BTUtils { updateStatusOut( false ); } else { Log.d( TAG, "PacketAccumulator.run(): connect(%s) => %s", - getBTName(), dos ); + mName, dos ); nDone += writeAndCheck( socket, dos ); updateStatusOut( true ); } @@ -875,7 +922,7 @@ public class BTUtils { updateStatusOut( true ); } return nDone; - } + } // writeAndCheck() private void handleReply( DataInputStream inStream, BTCmd cmd, int gameID, BTCmd reply ) throws IOException @@ -965,27 +1012,7 @@ public class BTUtils { return dos; } - // private long figureWait() - // { - // long waitFromNow; - // try ( DeadlockWatch dw = new DeadlockWatch( this ) ) { - // synchronized ( this ) { - // if ( 0 == mElems.size() ) { // nothing to send - // waitFromNow = Long.MAX_VALUE; - // } else if ( 0 == mFailCount ) { - // waitFromNow = 0; - // } else { - // // If we're failing, use a backoff. - // long wait = 1000 * (long)Math.pow( mFailCount, 2 ); - // waitFromNow = wait - (System.currentTimeMillis() - mLastFailTime); - // } - // } - // } - // Log.d( TAG, "%s.figureWait() => %dms", this, waitFromNow ); - // return waitFromNow; - // } - - void setNoHost() + private void setNoHost() { try ( DeadlockWatch dw = new DeadlockWatch( this ) ) { synchronized ( this ) { @@ -1021,53 +1048,10 @@ public class BTUtils { return sb.append('}').toString(); } - } - - private static class OutputPair { - ByteArrayOutputStream bos; - DataOutputStream dos; - OutputPair() { - bos = new ByteArrayOutputStream(); - dos = new DataOutputStream( bos ); - } - - int length() { return bos.toByteArray().length; } - } - - private static Map s_namesToAddrs; - private static String getSafeAddr( CommsAddrRec addr ) - { - String btAddr = addr.bt_btAddr; - if ( TextUtils.isEmpty(btAddr) || BOGUS_MARSHMALLOW_ADDR.equals( btAddr ) ) { - final String original = btAddr; - String btName = addr.bt_hostName; - if ( null == s_namesToAddrs ) { - s_namesToAddrs = new HashMap<>(); - } - - if ( s_namesToAddrs.containsKey( btName ) ) { - btAddr = s_namesToAddrs.get( btName ); - } else { - btAddr = null; - } - if ( null == btAddr ) { - Set devs = getCandidates(); - for ( BluetoothDevice dev : devs ) { - // Log.d( TAG, "%s => %s", dev.getName(), dev.getAddress() ); - if ( btName.equals( dev.getName() ) ) { - btAddr = dev.getAddress(); - s_namesToAddrs.put( btName, btAddr ); - break; - } - } - } - Log.d( TAG, "getSafeAddr(\"%s\") => %s", original, btAddr ); - } - return btAddr; - } + } // class PacketAccumulator private static class ListenThread extends Thread { - private static ListenThread[] sInstance = {null}; + private static Thread[] sInstance = {null}; private BluetoothAdapter mAdapter; private ListenThread( BluetoothAdapter adapter ) @@ -1082,6 +1066,7 @@ public class BTUtils { BluetoothServerSocket serverSocket; try { + Assert.assertTrueNR( null != sAppName && null != sUUID ); serverSocket = mAdapter .listenUsingRfcommWithServiceRecord( sAppName, sUUID ); } catch ( IOException ioe ) { @@ -1101,14 +1086,14 @@ public class BTUtils { try { BluetoothSocket socket = serverSocket.accept(); // blocks Log.d( TAG, "%s.run(): accept() returned", this ); - ReadThread.read( socket ); + ReadThread.handle( socket ); } catch ( IOException ioe ) { Log.ex( TAG, ioe ); - break; + serverSocket = null; } } - clearInstance( this ); + clearInstance( sInstance, this ); Log.d( TAG, "ListenThread: %s.run() exiting", this ); } @@ -1118,7 +1103,7 @@ public class BTUtils { BluetoothAdapter adapter = getAdapterIf(); if ( null != adapter ) { synchronized (sInstance) { - result = sInstance[0]; + result = (ListenThread)sInstance[0]; if ( null == result ) { sInstance[0] = result = new ListenThread( adapter ); result.start(); @@ -1127,27 +1112,20 @@ public class BTUtils { } return result; } - - private static void clearInstance( ListenThread instance ) - { - synchronized (sInstance) { - if ( sInstance[0] == null ) { - // nothing to do - } else if ( sInstance[0] == instance ) { - sInstance[0] = null; - } else { - Log.e( TAG, "clearInstance(): cur instance %s not == %s", - sInstance[0], instance ); - } - } - } } private static class ReadThread extends Thread { - private static ReadThread[] sInstance = {null}; + private static Thread[] sInstance = {null}; private LinkedBlockingQueue mQueue; private BTMsgSink mBTMsgSink; + static void handle( BluetoothSocket incoming ) + { + Log.d( TAG, "read(%s)", incoming ); + ReadThread self = getOrStart(); + self.enqueue( incoming ); + } + private ReadThread() { mQueue = new LinkedBlockingQueue<>(); @@ -1181,7 +1159,8 @@ public class BTUtils { Log.ex( TAG, ioe ); } } - clearInstance( this ); + + clearInstance( sInstance, this ); Log.d( TAG, "ReadThread: %s.run() exiting", this ); } @@ -1333,18 +1312,11 @@ public class BTUtils { mQueue.add( socket ); } - static void read( BluetoothSocket incoming ) - { - Log.d( TAG, "read(%s)", incoming ); - ReadThread self = getOrStart(); - self.enqueue( incoming ); - } - private static ReadThread getOrStart() { ReadThread result; - synchronized (sInstance) { - result = sInstance[0]; + synchronized ( sInstance ) { + result = (ReadThread)sInstance[0]; if ( null == result ) { sInstance[0] = result = new ReadThread(); result.start(); @@ -1352,20 +1324,6 @@ public class BTUtils { } return result; } - - private static void clearInstance( ReadThread instance ) - { - synchronized (sInstance) { - if ( sInstance[0] == null ) { - // nothing to do - } else if ( sInstance[0] == instance ) { - sInstance[0] = null; - } else { - Log.e( TAG, "clearInstance(): cur instance %s not == %s", - sInstance[0], instance ); - } - } - } } private static class BTMsgSink extends MultiMsgSink { From 57d93273c9ca2c66f5f0c906ffc0e63a18fed7ae Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 25 Oct 2020 14:58:19 -0700 Subject: [PATCH 2/2] show Known Player name in button Got tired of it not being clear what the button was going to do --- .../java/org/eehouse/android/xw4/BTUtils.java | 14 +++---- .../android/xw4/GamesListDelegate.java | 24 ++++++++++-- .../eehouse/android/xw4/NewWithKnowns.java | 39 +++++++++++++++---- .../app/src/main/res/values/strings.xml | 2 + 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java index d2800a432..ecb7939ef 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTUtils.java @@ -458,9 +458,6 @@ public class BTUtils { PacketAccumulator pa = pas.get( dev ); try { pa.join(); - if ( 0 < pa.getResponseCount() ) { - callListeners( dev ); - } } catch ( InterruptedException ex ) { Assert.failDbg(); } @@ -560,9 +557,11 @@ public class BTUtils { private volatile boolean mExitWhenEmpty = false; private BluetoothAdapter mAdapter; private BTHelper mHelper; + private boolean mPostOnResponse; PacketAccumulator( String addr ) { this(addr, 20000); } + // Ping case -- used only once PacketAccumulator( String addr, int timeoutMS ) { Assert.assertTrue( !TextUtils.isEmpty(addr) ); @@ -575,6 +574,7 @@ public class BTUtils { mAdapter = getAdapterIf(); Assert.assertTrueNR( null != mAdapter ); mHelper = new BTHelper( mName, mAddr ); + mPostOnResponse = true; start(); } @@ -614,11 +614,6 @@ public class BTUtils { return this; } - int getResponseCount() - { - return mResponseCount; - } - void addInvite( NetLaunchInfo nli ) { try { @@ -825,6 +820,9 @@ public class BTUtils { mName, dos ); nDone += writeAndCheck( socket, dos ); updateStatusOut( true ); + if ( mPostOnResponse ) { + callListeners( socket.getRemoteDevice() ); + } } } catch ( IOException ioe ) { Log.e( TAG, "PacketAccumulator.run(): ioe: %s", diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 587cfe939..4e46af8ec 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -965,6 +965,7 @@ public class GamesListDelegate extends ListDelegateBase private Dialog mkNewWithKnowns() { String[] names = XwJNI.kplr_getPlayers(); + final String[] nameRef = {null}; final NewWithKnowns view = (NewWithKnowns) LocUtils.inflate( m_activity, R.layout.new_game_with_knowns ); view.setNames( names, GameUtils.makeDefaultName( m_activity ) ); @@ -972,11 +973,11 @@ public class GamesListDelegate extends ListDelegateBase .setView( view ) .setTitle( R.string.new_game_networked ) .setIcon( R.drawable.ic_multigame ) - .setPositiveButton( R.string.play, new OnClickListener() { + .setPositiveButton( "…" /* can't be empty*/, new OnClickListener() { @Override public void onClick( DialogInterface dlg, int item ) { - String player = view.getSelPlayer(); - CommsAddrRec addr = XwJNI.kplr_getAddr( player ); + Assert.assertTrueNR( null != nameRef[0] ); + CommsAddrRec addr = XwJNI.kplr_getAddr( nameRef[0] ); if ( null != addr ) { launchLikeRematch( addr, view.gameName() ); } @@ -991,7 +992,22 @@ public class GamesListDelegate extends ListDelegateBase } ) ; - return ab.create(); + final AlertDialog dialog = ab.create(); + view.setOnNameChangeListener( new NewWithKnowns.OnNameChangeListener() { + @Override + public void onNewName( String name ) { + nameRef[0] = name; + Button button = dialog.getButton( DialogInterface.BUTTON_POSITIVE ); + if ( null != button ) { + String msg = getString( R.string.invite_player_fmt, name ); + button.setText( msg ); + } else { + Log.e( TAG, "Button still null" ); + } + } + } ); + + return dialog; } private void enableMoveGroupButton( DialogInterface dlgi ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java index f23dcc69f..25e0aae65 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NewWithKnowns.java @@ -21,18 +21,34 @@ package org.eehouse.android.xw4; import android.content.Context; import android.util.AttributeSet; +import android.view.View; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; -public class NewWithKnowns extends LinearLayout { +public class NewWithKnowns extends LinearLayout implements OnItemSelectedListener +{ + public interface OnNameChangeListener { + void onNewName( String name ); + } + + private OnNameChangeListener mListener; public NewWithKnowns( Context cx, AttributeSet as ) { super( cx, as ); } + void setOnNameChangeListener( OnNameChangeListener listener ) + { + Assert.assertTrueNR( null == mListener ); + mListener = listener; + } + void setNames( String[] knowns, String gameName ) { ArrayAdapter adapter = new @@ -43,20 +59,29 @@ public class NewWithKnowns extends LinearLayout { .simple_spinner_dropdown_item ); Spinner spinner = (Spinner)findViewById( R.id.names ); spinner.setAdapter( adapter ); + spinner.setOnItemSelectedListener( this ); EditText et = (EditText)findViewById( R.id.name_edit ); et.setText( gameName ); } - String getSelPlayer() - { - Spinner spinner = (Spinner)findViewById( R.id.names ); - return spinner.getSelectedItem().toString(); - } - String gameName() { EditText et = (EditText)findViewById( R.id.name_edit ); return et.getText().toString(); } + + @Override + public void onItemSelected( AdapterView parent, View view, + int pos, long id ) + { + OnNameChangeListener listener = mListener; + if ( null != listener && view instanceof TextView ) { + TextView tv = (TextView)view; + listener.onNewName( tv.getText().toString() ); + } + } + + @Override + public void onNothingSelected( AdapterView parent ) {} } diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 313f7bd2e..ff8f37207 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -1007,6 +1007,8 @@ it immediately because an email or messaging app will be launched to send your invitation. --> Invite now + + Invite %1$s More info Drop Relay