add pref to disable use of bluetooth

It's buggy enough on some devices that a user might need to disable it.
This commit is contained in:
Eric House 2020-11-01 19:19:30 -08:00
parent 04000ddf7e
commit 157332d2cc
11 changed files with 223 additions and 45 deletions

View file

@ -0,0 +1,69 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2009 - 2012 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.eehouse.android.xw4;
import android.content.Context;
import android.util.AttributeSet;
import java.lang.ref.WeakReference;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.loc.LocUtils;
public class BTCheckBoxPreference extends ConfirmingCheckBoxPreference {
private static final String TAG = BTCheckBoxPreference.class.getSimpleName();
private static WeakReference<BTCheckBoxPreference> s_this = null;
public BTCheckBoxPreference( Context context, AttributeSet attrs )
{
super( context, attrs );
s_this = new WeakReference<>( this );
}
@Override
protected void checkIfConfirmed()
{
PrefsActivity activity = (PrefsActivity)getContext();
String msg = LocUtils.getString( activity,
R.string.warn_bt_havegames );
int count = DBUtils
.getGameCountUsing( activity, CommsConnType.COMMS_CONN_BT );
if ( 0 < count ) {
msg += LocUtils.getQuantityString( activity, R.plurals.warn_bt_games_fmt,
count, count );
}
activity.makeConfirmThenBuilder( msg, Action.DISABLE_BT_DO )
.setPosButton( R.string.button_disable_bt )
.show();
}
protected static void setChecked()
{
if ( null != s_this ) {
BTCheckBoxPreference self = s_this.get();
if ( null != self ) {
self.super_setChecked( true );
}
}
}
}

View file

@ -47,6 +47,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eehouse.android.xw4.DbgUtils.DeadlockWatch;
import org.eehouse.android.xw4.MultiService.DictFetchOwner;
@ -99,7 +100,7 @@ public class BTUtils {
public static boolean BTAvailable()
{
BluetoothAdapter adapter = getAdapterIf();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
return null != adapter;
}
@ -109,22 +110,38 @@ public class BTUtils {
return null != adapter && adapter.isEnabled();
}
public static void enable()
public static void enable( Context context )
{
BluetoothAdapter adapter = getAdapterIf();
if ( null != adapter ) {
// Only do this after explicit action from user -- Android guidelines
adapter.enable();
}
XWPrefs.setBTDisabled( context, false );
}
public static void setEnabled( Context context, boolean enabled )
{
if ( enabled ) {
onResume( context );
} else {
stopThreads();
}
}
public static void disabledChanged( Context context )
{
boolean disabled = XWPrefs.getBTDisabled( context );
setEnabled( context, !disabled );
}
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() ) {
// BT crashes a lot inside the OS when running on behalf of a
// background user account. We catch exceptions that indicate that's
// going on and set this flag.
if ( ! XWPrefs.getBTDisabled( getContext() ) && !sBackUser.get() ) {
result = BluetoothAdapter.getDefaultAdapter();
}
return result;
@ -195,6 +212,12 @@ public class BTUtils {
sBackUser.set( false );
}
private static void stopThreads()
{
ListenThread.stopSelf();
ReadThread.stopSelf();
}
private static String nameForAddr( BluetoothAdapter adapter, String btAddr )
{
String result = null;
@ -316,14 +339,14 @@ public class BTUtils {
private static void updateStatusIn( boolean success )
{
Context context = XWApp.getContext();
Context context = getContext();
ConnStatusHandler
.updateStatusIn( context, CommsConnType.COMMS_CONN_BT, success );
}
private static void updateStatusOut( boolean success )
{
Context context = XWApp.getContext();
Context context = getContext();
ConnStatusHandler
.updateStatusOut( context, CommsConnType.COMMS_CONN_BT, success );
}
@ -398,32 +421,36 @@ public class BTUtils {
return btAddr;
}
private static void clearInstance( Thread[] holder, Thread instance )
private static void clearInstance( AtomicReference<Thread> holder,
Thread instance )
{
synchronized ( holder ) {
if ( holder[0] == null ) {
Thread curThread = holder.get();
if ( null == curThread ) {
// nothing to do
} else if ( holder[0] == instance ) {
holder[0] = null;
} else if ( instance == curThread ) {
holder.set( null );
} else {
Log.e( TAG, "clearInstance(): cur instance %s not == %s",
holder[0], instance );
curThread, instance );
}
}
}
// Save a few keystrokes...
private static Context getContext() { return XWApp.getContext(); }
private static class ScanThread extends Thread {
private static Thread[] sInstance = {null};
private static AtomicReference<Thread> sInstance = new AtomicReference<>();
private int mTimeoutMS;
private Set<BluetoothDevice> mDevs;
static void startOnce( int timeoutMS, Set<BluetoothDevice> devs )
{
synchronized ( sInstance ) {
if ( sInstance[0] == null ) {
if ( null == sInstance.get() ) {
ScanThread thread = new ScanThread( timeoutMS, devs );
sInstance[0] = thread;
Assert.assertTrueNR( thread == sInstance.get() );
thread.start();
}
}
@ -433,12 +460,13 @@ public class BTUtils {
{
mTimeoutMS = timeoutMS;
mDevs = devs;
sInstance.set( this );
}
@Override
public void run()
{
Assert.assertTrueNR( this == sInstance[0] );
Assert.assertTrueNR( this == sInstance.get() );
Map<BluetoothDevice, PacketAccumulator> pas = new HashMap<>();
for ( BluetoothDevice dev : mDevs ) {
@ -1049,12 +1077,14 @@ public class BTUtils {
} // class PacketAccumulator
private static class ListenThread extends Thread {
private static Thread[] sInstance = {null};
private static AtomicReference<Thread> sInstance = new AtomicReference<>();
private BluetoothAdapter mAdapter;
private BluetoothServerSocket mServerSocket;
private ListenThread( BluetoothAdapter adapter )
{
mAdapter = adapter;
sInstance.set( this );
}
@Override
@ -1062,32 +1092,31 @@ public class BTUtils {
{
Log.d( TAG, "ListenThread: %s.run() starting", this );
BluetoothServerSocket serverSocket;
try {
Assert.assertTrueNR( null != sAppName && null != sUUID );
serverSocket = mAdapter
mServerSocket = mAdapter
.listenUsingRfcommWithServiceRecord( sAppName, sUUID );
} catch ( IOException ioe ) {
Log.ex( TAG, ioe );
serverSocket = null;
mServerSocket = null;
} catch ( SecurityException ex ) {
// Got this with a message saying not allowed to call
// listenUsingRfcommWithServiceRecord() in background (on
// Android 9)
sBackUser.set( true ); // two-user system: disable BT
Log.d( TAG, "set sBackUser; outta here (first case)" );
serverSocket = null;
mServerSocket = null;
}
while ( null != serverSocket ) {
while ( null != mServerSocket && this == sInstance.get() ) {
Log.d( TAG, "%s.run(): calling accept()", this );
try {
BluetoothSocket socket = serverSocket.accept(); // blocks
BluetoothSocket socket = mServerSocket.accept(); // blocks
Log.d( TAG, "%s.run(): accept() returned", this );
ReadThread.handle( socket );
} catch ( IOException ioe ) {
Log.ex( TAG, ioe );
serverSocket = null;
mServerSocket = null;
}
}
@ -1100,20 +1129,41 @@ public class BTUtils {
ListenThread result = null;
BluetoothAdapter adapter = getAdapterIf();
if ( null != adapter ) {
synchronized (sInstance) {
result = (ListenThread)sInstance[0];
synchronized ( sInstance ) {
result = (ListenThread)sInstance.get();
if ( null == result ) {
sInstance[0] = result = new ListenThread( adapter );
result = new ListenThread( adapter );
Assert.assertTrueNR( result == sInstance.get() );
result.start();
}
}
}
return result;
}
private static void stopSelf()
{
synchronized ( sInstance ) {
ListenThread self = (ListenThread)sInstance.get();
Log.d( TAG, "ListenThread.stopSelf(): self: %s", self );
if ( null != self ) {
sInstance.set( null );
BluetoothServerSocket serverSocket = self.mServerSocket;
if ( null != serverSocket ) {
try {
serverSocket.close();
} catch ( IOException ioe ) {
Log.ex( TAG, ioe );
}
}
}
}
}
}
private static class ReadThread extends Thread {
private static Thread[] sInstance = {null};
private static AtomicReference<Thread> sInstance = new AtomicReference<>();
private LinkedBlockingQueue<BluetoothSocket> mQueue;
private BTMsgSink mBTMsgSink;
@ -1128,13 +1178,14 @@ public class BTUtils {
{
mQueue = new LinkedBlockingQueue<>();
mBTMsgSink = new BTMsgSink();
sInstance.set( this );
}
@Override
public void run()
{
Log.d( TAG, "ReadThread: %s.run() starting", this );
for ( ; ; ) {
while ( this == sInstance.get() ) {
try {
BluetoothSocket socket = mQueue.take();
DataInputStream inStream =
@ -1144,7 +1195,7 @@ public class BTUtils {
BTInviteDelegate.onHeardFromDev( socket.getRemoteDevice() );
parsePacket( proto, inStream, socket );
updateStatusIn( true );
TimerReceiver.restartBackoff( XWApp.getContext() );
TimerReceiver.restartBackoff( getContext() );
// nBadCount = 0;
} else {
writeBack( socket, BTCmd.BAD_PROTO );
@ -1210,7 +1261,7 @@ public class BTUtils {
case INVITE:
NetLaunchInfo nli;
if ( isOldProto ) {
nli = NetLaunchInfo.makeFrom( XWApp.getContext(),
nli = NetLaunchInfo.makeFrom( getContext(),
dis.readUTF() );
} else {
data = new byte[dis.readShort()];
@ -1258,7 +1309,7 @@ public class BTUtils {
{
Log.d( TAG, "receivePing()" );
boolean deleted = 0 != gameID
&& !DBUtils.haveGame( XWApp.getContext(), gameID );
&& !DBUtils.haveGame( getContext(), gameID );
DataOutputStream os = new DataOutputStream( socket.getOutputStream() );
os.writeByte( BTCmd.PONG.ordinal() );
@ -1314,18 +1365,30 @@ public class BTUtils {
{
ReadThread result;
synchronized ( sInstance ) {
result = (ReadThread)sInstance[0];
result = (ReadThread)sInstance.get();
if ( null == result ) {
sInstance[0] = result = new ReadThread();
result = new ReadThread();
Assert.assertTrueNR( result == sInstance.get() );
result.start();
}
}
return result;
}
private static void stopSelf()
{
synchronized ( sInstance ) {
ReadThread self = (ReadThread)sInstance.get();
if ( null != self ) {
sInstance.set( null );
self.interrupt();
}
}
}
}
private static class BTMsgSink extends MultiMsgSink {
public BTMsgSink() { super( XWApp.getContext() ); }
public BTMsgSink() { super( getContext() ); }
@Override
public int sendViaBluetooth( byte[] buf, String msgID, int gameID,
@ -1348,7 +1411,7 @@ public class BTUtils {
private CommsAddrRec mReturnAddr;
private Context mContext;
private BTHelper() { super( XWApp.getContext() ); }
private BTHelper() { super( BTUtils.getContext() ); }
BTHelper( CommsAddrRec from )
{

View file

@ -819,7 +819,8 @@ public class DBUtils {
return result;
}
public static int getRelayGameCount( Context context ) {
public static int getGameCountUsing( Context context, CommsConnType typ )
{
int result = 0;
String[] columns = { DBHelper.CONTYPE };
String selection = String.format( "%s = 0", DBHelper.GAME_OVER );
@ -829,7 +830,7 @@ public class DBUtils {
int indx = cursor.getColumnIndex( DBHelper.CONTYPE );
while ( cursor.moveToNext() ) {
CommsConnTypeSet typs = new CommsConnTypeSet( cursor.getInt(indx) );
if ( typs.contains(CommsConnType.COMMS_CONN_RELAY) ) {
if ( typs.contains( typ ) ) {
++result;
}
}

View file

@ -776,7 +776,7 @@ public abstract class DelegateBase implements DlgClickNotify,
XWPrefs.setNBSEnabled( m_activity, true );
break;
case ENABLE_BT_DO:
BTUtils.enable();
BTUtils.enable( m_activity );
break;
case ENABLE_RELAY_DO:
RelayService.setEnabled( m_activity, true );

View file

@ -123,6 +123,7 @@ public class DlgDelegate {
ENABLE_RELAY_DO,
ENABLE_RELAY_DO_OR,
DISABLE_RELAY_DO,
DISABLE_BT_DO,
ASKED_PHONE_STATE,
PERMS_QUERY,
PERMS_BANNED_INFO,

View file

@ -61,6 +61,7 @@ public class PrefsDelegate extends DelegateBase
R.string.key_disable_nag,
R.string.key_disable_nag_solo,
R.string.key_disable_relay,
R.string.key_disable_bt,
R.string.key_force_tablet,
R.string.key_mqtt_host,
R.string.key_mqtt_port,
@ -237,6 +238,9 @@ public class PrefsDelegate extends DelegateBase
case R.string.key_disable_relay:
RelayService.enabledChanged( m_activity );
break;
case R.string.key_disable_bt:
BTUtils.disabledChanged( m_activity );
break;
case R.string.key_force_tablet:
makeOkOnlyBuilder( R.string.after_restart ).show();
break;
@ -265,6 +269,10 @@ public class PrefsDelegate extends DelegateBase
RelayService.setEnabled( m_activity, false );
RelayCheckBoxPreference.setChecked();
break;
case DISABLE_BT_DO:
BTUtils.setEnabled( m_activity, false );
BTCheckBoxPreference.setChecked();
break;
default:
handled = super.onPosButton( action, params );
}

View file

@ -26,6 +26,7 @@ import android.util.AttributeSet;
import java.lang.ref.WeakReference;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.loc.LocUtils;
public class RelayCheckBoxPreference extends ConfirmingCheckBoxPreference {
@ -45,7 +46,8 @@ public class RelayCheckBoxPreference extends ConfirmingCheckBoxPreference {
String msg = LocUtils.getString( activity,
R.string.warn_relay_havegames );
int count = DBUtils.getRelayGameCount( activity );
int count = DBUtils
.getGameCountUsing( activity, CommsConnType.COMMS_CONN_RELAY );
if ( 0 < count ) {
msg += LocUtils.getQuantityString( activity, R.plurals.warn_relay_games_fmt,
count, count );

View file

@ -161,6 +161,18 @@ public class XWPrefs {
return enabled;
}
public static boolean getBTDisabled( Context context )
{
boolean disabled = getPrefsBoolean( context, R.string.key_disable_bt,
false );
return disabled;
}
public static void setBTDisabled( Context context, boolean disabled )
{
setPrefsBoolean( context, R.string.key_disable_bt, disabled );
}
public static boolean getSkipToWebAPI( Context context )
{
return getPrefsBoolean( context, R.string.key_relay_via_http_first, false );

View file

@ -68,6 +68,7 @@
<string name="key_default_timerenabled">key_default_timerenabled</string>
<string name="key_notify_sound">key_notify_sound</string>
<string name="key_disable_relay">key_disable_relay</string>
<string name="key_disable_bt">key_disable_bt</string>
<string name="key_notify_vibrate">key_notify_vibrate</string>
<string name="key_enable_nbs">key_enable_nbs</string>
<string name="key_enable_p2p">key_enable_p2p</string>

View file

@ -967,6 +967,8 @@
networked games</string>
<string name="disable_relay">Disable play via the relay </string>
<string name="disable_relay_summary">Disable all internet communication</string>
<string name="disable_bt">Disable play via Bluetooth</string>
<string name="disable_bt_summary">Disable all Bluetooth communication</string>
<!--
############################################################
# :Screens:
@ -1874,9 +1876,9 @@
disabled. No moves will be sent via Data SMS.\n\nYou can enable
play via Data SMS now, or later.
</string>
<string name="warn_bt_disabled">Bluetooth is currently off on this
device. No moves will be sent via Bluetooth.\n\nYou can enable
Bluetooth now, or later.
<string name="warn_bt_disabled">Bluetooth play is currently
disabled, and no moves will be exchanged via Bluetooth until it is
enabled.\n\nYou can enable Bluetooth now, or later.
</string>
<string name="warn_relay_disabled">Relay play is currently disable
on this device. No moves will be sent or received via the
@ -1891,14 +1893,26 @@
Most networked games exchange moves via the relay, so only do this
if you plan to play ALL games against a robot on this same
device.</string>
<string name="warn_bt_havegames">Are you sure you want to
disable play using Bluetooth?
\n\nBluetooth is useful for exchanging moves, and especially
invitations, with devices physically close to you. Unless youre
certain you wont play against anybody nearby theres little harm
in leaving it on.
</string>
<plurals name="warn_relay_games_fmt">
<item quantity="one">\n\n(You have one active game using the relay now.)</item>
<item quantity="other">\n\n(You have %1$d active games using the relay now.)</item>
</plurals>
<plurals name="warn_bt_games_fmt">
<item quantity="one">\n\n(You have one active game using Bluetooth.)</item>
<item quantity="other">\n\n(You have %1$d active games using Bluetooth.)</item>
</plurals>
<string name="button_enable_sms">Enable Data SMS</string>
<string name="button_enable_bt">Enable Bluetooth</string>
<string name="button_enable_relay">Enable Relay play</string>
<string name="button_disable_relay">Disable Relay play</string>
<string name="button_disable_bt">Disable Bluetooth play</string>
<string name="button_later">Later</string>
<!-- -->
<string name="gamel_menu_checkupdates">Check for updates</string>

View file

@ -369,6 +369,13 @@
android:defaultValue="false"
/>
<org.eehouse.android.xw4.BTCheckBoxPreference
android:key="@string/key_disable_bt"
android:title="@string/disable_bt"
android:summary="@string/disable_bt_summary"
android:defaultValue="false"
/>
</PreferenceScreen>
<!-- For Debugging -->