use NBSProxy if available

Lots of work dealing with banned permissions (SEND_SMS and
RECEIVE_SMS). First, if they're banned and NBSProxy is installed, just
use it as if the permissions had been granted. When it's not there,
explain at various points where users will otherwise be confused: when
they try to invite using data sms, or when they open a game that already
uses it.)
This commit is contained in:
Eric House 2019-03-06 12:39:08 -08:00
parent c59e86b724
commit 5bdc473e02
25 changed files with 445 additions and 170 deletions

View file

@ -47,6 +47,8 @@ android {
// variant.buildConfigField "String", "FIELD_NAME", "\"my String\""
variant.buildConfigField "String", "FABRIC_API_KEY", "\"$FABRIC_API_KEY\""
// We need both of these because xwprefs.xml can't reference
// the BuildConfig constant
resValue "string", "git_rev", "$GITREV"
variant.buildConfigField "String", "GIT_REV", "\"$GITREV\""
@ -68,9 +70,9 @@ android {
buildConfigField "String", "BUILD_INFO_NAME", "\"${BUILD_INFO_NAME}\""
buildConfigField "boolean", "IS_TAGGED_BUILD", "${CURTAG}" == '' ? "false" : "true"
resValue "string", "invite_prefix", "/and/"
buildConfigField "int[]", "SMS_BANNED_EXPL", "null"
buildConfigField "boolean", "UDP_ENABLED", "true"
buildConfigField "boolean", "REPORT_LOCKS", "false"
buildConfigField "boolean", "SMS_BANNED", "false"
buildConfigField "boolean", "UDP_ENABLED", "true"
buildConfigField "boolean", "REPORT_LOCKS", "false"
}
xw4 {
@ -82,8 +84,7 @@ android {
buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\""
buildConfigField "int[]", "SMS_BANNED_EXPL", "null"
// "{R.string.key_notagain_sms_banned, R.string.sms_banned_expl}"
buildConfigField "boolean", "SMS_BANNED", "false"
}
xw4fdroid {
@ -117,8 +118,7 @@ android {
buildConfigField "boolean", "WIDIR_ENABLED", "true"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug sans SMS\""
buildConfigField "int[]", "SMS_BANNED_EXPL", "null"
// "{R.string.key_notagain_sms_banned, R.string.sms_banned_expl}"
buildConfigField "boolean", "SMS_BANNED", "true"
}
// WARNING: "all" breaks things. Seems to be a keyword. Need
@ -246,7 +246,7 @@ dependencies {
implementation 'com.google.firebase:firebase-messaging:17.3.4' // rm-for-fdroid
implementation 'com.google.firebase:firebase-core:16.0.6' // rm-for-fdroid
implementation 'com.github.eehouse:nbsproxy:v0.0.2'
implementation 'com.github.eehouse:nbsproxy:v0.0.5'
}
task mkImages(type: Exec) {

View file

@ -46,10 +46,8 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.ActionPair;
@ -147,6 +145,16 @@ public class BoardDelegate extends DelegateBase
}
}
// Quick hack to manage a series of alerts meant to be presented
// one-at-a-time in order. Each tests whether it's its turn, and if so
// checks its conditions for being shown (e.g. NO_MEANS for no way to
// communicate). If the conditions aren't met (no need to show alert), it
// just sets the StartAlertOrder ivar to the next value and
// exits. Otherwise it needs to ensure that however the alert it's posting
// exits that ivar is incremented as well.
private static enum StartAlertOrder { NBS_PERMS, NO_MEANS, INVITE, DONE, };
private static class MySIS implements Serializable {
MySIS() { nMissing = -1; }
String toastStr;
@ -155,9 +163,22 @@ public class BoardDelegate extends DelegateBase
int nMissing;
int nAlerts;
boolean inTrade;
StartAlertOrder mAlertOrder = StartAlertOrder.values()[0];
}
private MySIS m_mySIS;
private boolean alertOrderAt( StartAlertOrder ord )
{
return m_mySIS.mAlertOrder == ord;
}
private void alertOrderIncrIfAt( StartAlertOrder ord )
{
if ( alertOrderAt( ord ) ) {
m_mySIS.mAlertOrder = ord.values()[ord.ordinal() + 1];
}
}
@Override
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
@ -694,6 +715,7 @@ public class BoardDelegate extends DelegateBase
setBackgroundColor();
setKeepScreenOn();
} else {
warnIfNoTransport();
showInviteAlertIf();
}
}
@ -1063,6 +1085,7 @@ public class BoardDelegate extends DelegateBase
deleteAndClose();
break;
case DROP_SMS_ACTION: // do nothing; work done in onNegButton case
alertOrderIncrIfAt( StartAlertOrder.NBS_PERMS );
break;
case INVITE_SMS_DATA:
@ -1157,6 +1180,20 @@ public class BoardDelegate extends DelegateBase
case ASKED_PHONE_STATE:
showInviteChoicesThen( params );
break;
case INVITE_SMS_DATA:
Perms23.Perm[] perms = (Perms23.Perm[])params[2];
if ( Perms23.bannedWithWorkaround( m_activity, perms ) ) {
int nMissing = (Integer)params[0];
SentInvitesInfo info = (SentInvitesInfo)params[1];
launchPhoneNumberInvite( nMissing, info, InviteMeans.SMS_DATA,
RequestCode.SMS_DATA_INVITE_RESULT );
} else if ( Perms23.anyBanned( perms ) ) {
makeOkOnlyBuilder( R.string.sms_banned_ok_only )
.setActionPair(new ActionPair( Action.PERMS_BANNED_INFO,
R.string.button_more_info ) )
.show();
}
break;
default:
handled = super.onNegButton( action, params );
}
@ -1222,10 +1259,10 @@ public class BoardDelegate extends DelegateBase
RequestCode.BT_INVITE_RESULT );
break;
case SMS_DATA:
Perms23.tryGetPerms( this, new Perm[] { Perm.SEND_SMS,
Perm.RECEIVE_SMS },
R.string.sms_invite_rationale,
Action.INVITE_SMS_DATA, m_mySIS.nMissing, info );
Perm[] perms = { Perm.SEND_SMS, Perm.RECEIVE_SMS };
Perms23.tryGetPerms( this, perms, R.string.sms_invite_rationale,
Action.INVITE_SMS_DATA, m_mySIS.nMissing,
info, perms );
break;
case SMS_USER: // like an email invite, but we want the phone #
launchPhoneNumberInvite( m_mySIS.nMissing, info, means,
@ -2211,7 +2248,7 @@ public class BoardDelegate extends DelegateBase
Utils.cancelNotification( m_activity, (int)m_rowid );
askPermissions();
askNBSPermissions();
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) {
warnIfNoTransport();
@ -2221,23 +2258,51 @@ public class BoardDelegate extends DelegateBase
}
} // resumeGame
private void askPermissions()
private void askNBSPermissions()
{
if ( m_summary.conTypes.contains( CommsConnType.COMMS_CONN_SMS )
&& null == m_permCbck ) { // already asked?
m_permCbck = new Perms23.PermCbck() {
@Override
public void onPermissionResult( Map<Perm, Boolean> perms ) {
if ( ! perms.get(Perm.SEND_SMS) ) {
makeConfirmThenBuilder( R.string.missing_perms,
Action.DROP_SMS_ACTION )
.setNegButton(R.string.remove_sms)
.show();
final StartAlertOrder thisOrder = StartAlertOrder.NBS_PERMS;
if ( alertOrderAt( thisOrder ) // already asked?
&& m_summary.conTypes.contains( CommsConnType.COMMS_CONN_SMS ) ) {
Perm[] nbsPerms = { Perm.SEND_SMS, Perm.RECEIVE_SMS };
if ( Perms23.havePermissions( m_activity, nbsPerms ) ) {
// We have them or a workaround; cool! proceed
alertOrderIncrIfAt( thisOrder );
} else {
// Make sure these can be treated the same!!!
Assert.assertTrue( nbsPerms.length == 2 &&
nbsPerms[0].isBanned() == nbsPerms[1].isBanned() );
m_permCbck = new Perms23.PermCbck() {
@Override
public void onPermissionResult( boolean allGood,
Map<Perm, Boolean> perms )
{
if ( allGood ) {
// Yay! nothing to do
alertOrderIncrIfAt( thisOrder );
} else {
Log.d( TAG, "askNBSPermissions(): posting alert" );
boolean banned = Perm.SEND_SMS.isBanned();
int explID = banned
? R.string.banned_nbs_perms : R.string.missing_sms_perms;
DlgDelegate.ConfirmThenBuilder builder =
makeConfirmThenBuilder( explID, Action.DROP_SMS_ACTION );
if ( banned ) {
ActionPair pr = new ActionPair( Action.PERMS_BANNED_INFO,
R.string.button_more_info );
builder.setActionPair( pr );
}
builder.setNegButton( R.string.remove_sms )
.show();
Log.d( TAG, "askNBSPermissions(): DONE posting alert" );
}
}
}
};
new Perms23.Builder(Perm.SEND_SMS)
.asyncQuery( m_activity, m_permCbck );
};
new Perms23.Builder( nbsPerms )
.asyncQuery( m_activity, m_permCbck );
}
} else {
alertOrderIncrIfAt( thisOrder );
}
}
@ -2385,17 +2450,21 @@ public class BoardDelegate extends DelegateBase
private void showInviteAlertIf()
{
DbgUtils.assertOnUIThread();
if ( ! m_haveStartedShowing && null == m_inviteAlert
&& m_mySIS.nMissing > 0 && !isFinishing() ) {
InviteAlertState ias = new InviteAlertState();
ias.summary = m_summary;
ias.gi = m_gi;
ias.relayMissing = m_relayMissing;
ias.connTypes = m_connTypes;
ias.rowid = m_rowid;
ias.nMissing = m_mySIS.nMissing;
showDialogFragment( DlgID.DLG_INVITE, ias );
m_haveStartedShowing = true;
if ( alertOrderAt( StartAlertOrder.INVITE ) ) {
if ( ! m_haveStartedShowing && null == m_inviteAlert
&& m_mySIS.nMissing > 0 && !isFinishing() ) {
InviteAlertState ias = new InviteAlertState();
ias.summary = m_summary;
ias.gi = m_gi;
ias.relayMissing = m_relayMissing;
ias.connTypes = m_connTypes;
ias.rowid = m_rowid;
ias.nMissing = m_mySIS.nMissing;
showDialogFragment( DlgID.DLG_INVITE, ias );
m_haveStartedShowing = true;
} else {
alertOrderIncrIfAt( StartAlertOrder.INVITE );
}
}
}
@ -2455,24 +2524,32 @@ public class BoardDelegate extends DelegateBase
private void warnIfNoTransport()
{
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_SMS ) ) {
if ( !XWPrefs.getNBSEnabled( m_activity ) ) {
makeConfirmThenBuilder( R.string.warn_sms_disabled,
Action.ENABLE_NBS_ASK )
.setPosButton( R.string.button_enable_sms )
.setNegButton( R.string.button_later )
.show();
if ( alertOrderAt( StartAlertOrder.NO_MEANS ) ) {
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_SMS ) ) {
if ( !XWPrefs.getNBSEnabled( m_activity ) ) {
makeConfirmThenBuilder( R.string.warn_sms_disabled,
Action.ENABLE_NBS_ASK )
.setPosButton( R.string.button_enable_sms )
.setNegButton( R.string.button_later )
.show();
}
}
}
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
if ( !RelayService.relayEnabled( m_activity ) ) {
m_dropRelayOnDismiss = false;
String msg = getString( R.string.warn_relay_disabled )
+ "\n\n" + getString( R.string.warn_relay_remove );
makeConfirmThenBuilder( msg, Action.ENABLE_RELAY_DO_OR )
.setPosButton( R.string.button_enable_relay )
.setNegButton( R.string.newgame_drop_relay )
.show();
if ( m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ) ) {
if ( !RelayService.relayEnabled( m_activity ) ) {
m_dropRelayOnDismiss = false;
String msg = getString( R.string.warn_relay_disabled )
+ "\n\n" + getString( R.string.warn_relay_remove );
makeConfirmThenBuilder( msg, Action.ENABLE_RELAY_DO_OR )
.setPosButton( R.string.button_enable_relay )
.setNegButton( R.string.newgame_drop_relay )
.show();
}
}
if ( m_connTypes.isEmpty() ) {
askNoAddrsDelete();
} else {
alertOrderIncrIfAt( StartAlertOrder.NO_MEANS );
}
}
}

View file

@ -433,7 +433,8 @@ public class DBUtils {
return result;
}
public static int countOpenGamesUsingRelay( Context context )
private static int countOpenGamesUsing( Context context,
CommsConnType connTyp )
{
int result = 0;
String[] columns = { DBHelper.CONTYPE };
@ -446,16 +447,28 @@ 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( connTyp ) ) {
++result;
}
}
cursor.close();
}
// Log.d( TAG, "countOpenGamesUsingRelay() => %d", result );
// Log.d( TAG, "countOpenGamesUsing(%s) => %d", connTyp, result );
return result;
}
public static int countOpenGamesUsingRelay( Context context )
{
int result = countOpenGamesUsing( context, CommsConnType.COMMS_CONN_RELAY );
return result;
}
public static int countOpenGamesUsingNBS( Context context )
{
int result = countOpenGamesUsing( context, CommsConnType.COMMS_CONN_SMS );
// Log.d( TAG, "countOpenGamesUsingNBS() => %d", result );
return result;
}

View file

@ -755,6 +755,9 @@ public class DelegateBase implements DlgClickNotify,
case PERMS_QUERY:
Perms23.onGotPermsAction( this, true, params );
break;
case PERMS_BANNED_INFO:
NetUtils.launchWebBrowserWith( m_activity, R.string.nbs_ban_url );
break;
default:
Log.d( TAG, "unhandled action %s", action.toString() );
// Assert.assertTrue( !BuildConfig.DEBUG );

View file

@ -181,7 +181,8 @@ public class DictUtils {
// Note: if STORAGE permission is changed the set being returned here
// will change. Might want to check for that and invalidate this list
// if it's changed.
boolean haveStorage = Perms23.havePermission( Perms23.Perm.STORAGE );
boolean haveStorage = Perms23.havePermissions( context,
Perms23.Perm.STORAGE );
boolean permsChanged = null == s_hadStorage
|| haveStorage != s_hadStorage;

View file

@ -124,11 +124,12 @@ public class DlgDelegate {
DISABLE_RELAY_DO,
ASKED_PHONE_STATE,
PERMS_QUERY,
PERMS_BANNED_INFO,
// Sent when not-again checkbox checked
SET_NA_DEFAULTNAME,
SET_GOT_LANGDICT,
}
} // Action enum
public static class ActionPair implements Serializable {
public ActionPair( Action act, int str ) {

View file

@ -44,7 +44,6 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.eehouse.android.xw4.DBUtils.GameChangeType;
import org.eehouse.android.xw4.DBUtils.GameGroupInfo;
import org.eehouse.android.xw4.DBUtils.SentInvitesInfo;
@ -944,6 +943,7 @@ public class GamesListDelegate extends ListDelegateBase
@Override
protected void init( Bundle savedInstanceState )
{
boolean isFirstLaunch = null == savedInstanceState;
m_origTitle = getTitle();
m_handler = new Handler();
@ -995,8 +995,12 @@ public class GamesListDelegate extends ListDelegateBase
// asking (OS will grant without user interaction) since they're in
// the same group. So just do it now. This code can be removed
// later...
if ( Perms23.havePermission( Perm.SEND_SMS ) ) {
Perms23.tryGetPerms( this, Perm.RECEIVE_SMS, 0, Action.SKIP_CALLBACK );
if ( !Perm.RECEIVE_SMS.isBanned() ) {
if ( Perms23.havePermissions( m_activity, Perm.SEND_SMS ) ) {
Perms23.tryGetPerms( this, Perm.RECEIVE_SMS, 0, Action.SKIP_CALLBACK );
}
} else if ( isFirstLaunch ) {
warnSMSBannedIf();
}
} // init
@ -1060,6 +1064,23 @@ public class GamesListDelegate extends ListDelegateBase
}
}
private void warnSMSBannedIf()
{
if ( !Perms23.havePermissions( m_activity, Perm.SEND_SMS, Perm.RECEIVE_SMS )
&& Perm.SEND_SMS.isBanned() ) {
int smsGameCount = DBUtils.countOpenGamesUsingNBS( m_activity );
if ( 0 < smsGameCount ) {
String msg = LocUtils.getString( m_activity,
R.string.not_again_nbsGamesOnUpgrade,
smsGameCount );
makeNotAgainBuilder( msg, R.string.key_notagain_nbsGamesOnUpgrade )
.setActionPair( new ActionPair( Action.PERMS_BANNED_INFO,
R.string.button_more_info ) )
.show();
}
}
}
private void moveGroup( long groupID, boolean moveUp )
{
m_adapter.moveGroup( groupID, moveUp );
@ -1448,8 +1469,7 @@ public class GamesListDelegate extends ListDelegateBase
showItemsIf( ONEGAME_ITEMS, menu, 1 == nGamesSelected );
showItemsIf( ONEGROUP_ITEMS, menu, 1 == nGroupsSelected );
boolean enable = showDbg && nothingSelected
&& UpdateCheckReceiver.haveToCheck( m_activity );
boolean enable = showDbg && nothingSelected;
Utils.setItemVisible( menu, R.id.games_menu_checkupdates, enable );
int selGroupPos = -1;

View file

@ -21,9 +21,10 @@
package org.eehouse.android.xw4;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -203,6 +204,18 @@ public class NetUtils {
return result;
}
public static void launchWebBrowserWith( Context context, int uriResID )
{
String uri = context.getString( uriResID );
launchWebBrowserWith( context, uri );
}
public static void launchWebBrowserWith( Context context, String uri )
{
Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse(uri) );
context.startActivity( intent );
}
protected static HttpsURLConnection makeHttpsRelayConn( Context context,
String proc )
{

View file

@ -35,6 +35,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eehouse.android.nbsplib.NBSProxy;
import org.eehouse.android.xw4.DlgDelegate.Action;
import org.eehouse.android.xw4.DlgDelegate.DlgClickNotify;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -47,21 +49,20 @@ public class Perms23 {
public static enum Perm {
READ_PHONE_STATE(Manifest.permission.READ_PHONE_STATE),
STORAGE(Manifest.permission.WRITE_EXTERNAL_STORAGE),
SEND_SMS(Manifest.permission.SEND_SMS, BuildConfig.SMS_BANNED_EXPL),
RECEIVE_SMS(Manifest.permission.RECEIVE_SMS, BuildConfig.SMS_BANNED_EXPL),
SEND_SMS(Manifest.permission.SEND_SMS, BuildConfig.SMS_BANNED),
RECEIVE_SMS(Manifest.permission.RECEIVE_SMS, BuildConfig.SMS_BANNED),
READ_CONTACTS(Manifest.permission.READ_CONTACTS);
private String m_str;
private int[] m_expl;
private Perm(String str) { this(str, null); }
private Perm(String str, int[] bannedExpl) {
private boolean m_banned;
private Perm(String str) { this(str, false); }
private Perm(String str, boolean banned) {
m_str = str;
m_expl = bannedExpl;
m_banned = banned;
}
public String getString() { return m_str; }
public boolean isBanned() { return m_expl != null; }
public int[] getExpl() { Assert.assertTrue(isBanned()); return m_expl; }
public boolean isBanned() { return m_banned; }
public static Perm getFor( String str ) {
Perm result = null;
for ( Perm one : Perm.values() ) {
@ -75,7 +76,7 @@ public class Perms23 {
}
public interface PermCbck {
void onPermissionResult( Map<Perm, Boolean> perms );
void onPermissionResult( boolean allGood, Map<Perm, Boolean> perms );
}
public interface OnShowRationale {
void onShouldShowRationale( Set<Perm> perms );
@ -89,21 +90,12 @@ public class Perms23 {
m_perms.addAll( perms );
}
public Builder( Perm[] perms ) {
public Builder( Perm... perms ) {
for ( Perm perm : perms ) {
m_perms.add( perm );
}
}
public Builder( Perm perm ) {
m_perms.add( perm );
}
public Builder add( Perm perm ) {
m_perms.add( perm );
return this;
}
public Builder setOnShowRationale( OnShowRationale onShow )
{
m_onShow = onShow;
@ -115,6 +107,11 @@ public class Perms23 {
asyncQuery( activity, null );
}
// We have set of permissions. For any of them that needs asking (not
// granted AND not banned) start an ask.
//
// PENDING: I suspect this'll crash if I ask for a banned and
// non-banned at the same time (and don't have either)
public void asyncQuery( Activity activity, PermCbck cbck )
{
Log.d( TAG, "asyncQuery(%s)", m_perms.toString() );
@ -125,10 +122,13 @@ public class Perms23 {
ArrayList<String> askStrings = new ArrayList<String>();
for ( Perm perm : m_perms ) {
String permStr = perm.getString();
boolean haveIt = PackageManager.PERMISSION_GRANTED
boolean haveIt = perm.isBanned() || PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission( activity, permStr );
if ( !haveIt ) {
// do not pass banned perms to the OS! They're not in
// AndroidManifest.xml so may crash on some devices
Assert.assertFalse( perm.isBanned() );
askStrings.add( permStr );
if ( null != m_onShow && ActivityCompat
@ -144,10 +144,13 @@ public class Perms23 {
if ( haveAll ) {
if ( null != cbck ) {
Map<Perm, Boolean> map = new HashMap<>();
boolean allGood = true;
for ( Perm perm : m_perms ) {
map.put( perm, true );
boolean banned = perm.isBanned();
map.put( perm, !banned );
allGood = allGood & !banned;
}
cbck.onPermissionResult( map );
cbck.onPermissionResult( allGood, map );
}
} else if ( 0 < needShow.size() && null != m_onShow ) {
// Log.d( TAG, "calling onShouldShowRationale()" );
@ -228,18 +231,10 @@ public class Perms23 {
}
builder.asyncQuery( m_delegate.getActivity(), new PermCbck() {
@Override
public void onPermissionResult( Map<Perm, Boolean> permsMap ) {
public void onPermissionResult( boolean allGood,
Map<Perm, Boolean> permsMap ) {
if ( Action.SKIP_CALLBACK != m_action ) {
// We need all the sought perms to have been granted
boolean allGranted = true;
Iterator<Perm> iter = permsMap.keySet().iterator();
while ( allGranted && iter.hasNext() ) {
Perm perm = iter.next();
allGranted = allGranted && permsMap.get( perm );
}
if ( allGranted ) {
if ( allGood ) {
m_delegate.onPosButton( m_action, m_params );
} else {
m_delegate.onNegButton( m_action, m_params );
@ -252,16 +247,11 @@ public class Perms23 {
// Cons up a call with a "no" answer, and post it.
private void doItFail( Set<Perm> bannedPerms )
{
int resID = 0;
final Perm[] perms = bannedPerms.toArray( new Perm[bannedPerms.size()] );
int[] expls = perms[0].getExpl();
m_delegate.makeNotAgainBuilder(expls[1], expls[0]).show();
m_delegate.post( new Runnable() {
@Override
public void run() {
m_delegate.onNegButton( m_action, perms );
Log.d( TAG, "doItFail(); passing perms to onNegButton(%s)", m_action );
m_delegate.onNegButton( m_action, m_params );
}
} );
}
@ -338,11 +328,14 @@ public class Perms23 {
String[] perms, int[] granteds )
{
// Log.d( TAG, "gotPermissionResult(%s)", perms.toString() );
Map<Perm, Boolean> result = new HashMap<Perm, Boolean>();
Map<Perm, Boolean> result = new HashMap<>();
boolean shouldResend = false;
boolean allGood = true;
for ( int ii = 0; ii < perms.length; ++ii ) {
Perm perm = Perm.getFor( perms[ii] );
Assert.assertTrue( !perm.isBanned() || ! BuildConfig.DEBUG );
boolean granted = PackageManager.PERMISSION_GRANTED == granteds[ii];
allGood = allGood && granted;
result.put( perm, granted );
// Hack. If SMS has been granted, resend all moves. This should be
@ -364,16 +357,58 @@ public class Perms23 {
PermCbck cbck = s_map.remove( code );
if ( null != cbck ) {
cbck.onPermissionResult( result );
cbck.onPermissionResult( allGood, result );
}
}
public static boolean havePermission( Perm perm )
public static boolean havePermissions( Context context, Perm... perms )
{
String permString = perm.getString();
boolean result = PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission( XWApp.getContext(), permString );
// Log.d( TAG, "havePermission(%s) => %b", permString, result );
boolean result = true;
for ( int ii = 0; result && ii < perms.length; ++ii ) {
Perm perm = perms[ii];
boolean thisResult;
if ( perm.isBanned() ) {
thisResult = bannedWithWorkaround( context, perm );
} else {
thisResult = PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission( XWApp.getContext(),
perm.getString() );
}
result = result && thisResult;
}
return result;
}
static boolean anyBanned( Perms23.Perm... perms )
{
boolean anyBanned = false;
for ( int ii = 0; !anyBanned && ii < perms.length; ++ii ) {
anyBanned = perms[ii].isBanned();
}
return anyBanned;
}
static boolean bannedWithWorkaround( Context context, Perms23.Perm... perms )
{
boolean allBanned = true;
boolean workaroundKnown = true;
for ( Perms23.Perm perm : perms ) {
allBanned = allBanned && perm.isBanned();
switch ( perm ) {
case SEND_SMS:
case RECEIVE_SMS:
workaroundKnown = workaroundKnown && NBSProxy.isInstalled( context );
break;
default:
Log.e( TAG, "bannedWithWorkaround(): unexpected perm %s", perm );
Assert.assertFalse( BuildConfig.DEBUG );
break;
}
}
boolean result = allBanned && workaroundKnown;
Log.d( TAG, "bannedWithWorkaround() => %b", result );
return result;
}

View file

@ -557,8 +557,8 @@ public class RelayService extends XWJIService
for ( outData = s_queue.poll( ts, TimeUnit.MILLISECONDS );
null != outData;
outData = s_queue.poll() ) { // doesn't block
Log.d( TAG, "removed packet from queue (%d left): %s",
s_queue.size(), outData );
// Log.d( TAG, "removed packet from queue (%d left): %s",
// s_queue.size(), outData );
if ( outData == sEOQPacket ) {
shouldGoOn = false;
break;
@ -691,7 +691,7 @@ public class RelayService extends XWJIService
int sentLen = 0;
if ( packets.size() > 0 ) {
Log.d( TAG, "sendViaUDP(): sending %d at once", packets.size() );
// Log.d( TAG, "sendViaUDP(): sending %d at once", packets.size() );
final RelayService service = this;
service.noteSent( packets, true );
for ( PacketData packet : packets ) {
@ -1155,9 +1155,9 @@ public class RelayService extends XWJIService
byte proto = dis.readByte();
if ( XWPDevProto.XWPDEV_PROTO_VERSION_1.ordinal() == proto ) {
int packetID = vli2un( dis );
if ( 0 != packetID ) {
Log.d( TAG, "readHeader(): got packetID %d", packetID );
}
// if ( 0 != packetID ) {
// Log.d( TAG, "readHeader(): got packetID %d", packetID );
// }
byte ordinal = dis.readByte();
XWRelayReg cmd = XWRelayReg.values()[ordinal];
result = new PacketHeader( cmd, packetID );

View file

@ -376,8 +376,9 @@ public class SMSService extends XWJIService {
private void sendDiedPacket( String phone, int gameID )
{
if ( !s_sentDied.contains(gameID) ) {
if ( !s_sentDied.contains( gameID ) ) {
resendFor( phone, SMS_CMD.DEATH, gameID, null );
s_sentDied.add( gameID );
}
}
@ -515,19 +516,25 @@ public class SMSService extends XWJIService {
short nbsPort = getNBSPort();
try {
SmsManager mgr = SmsManager.getDefault();
PendingIntent sent = makeStatusIntent( MSG_SENT );
PendingIntent delivery = makeStatusIntent( MSG_DELIVERED );
boolean useProxy = Perms23.Perm.SEND_SMS.isBanned()
&& NBSProxy.isInstalled( this );
PendingIntent sent = useProxy ? null : makeStatusIntent( MSG_SENT );
PendingIntent delivery = useProxy ? null : makeStatusIntent( MSG_DELIVERED );
for ( byte[] fragment : fragments ) {
mgr.sendDataMessage( phone, null, nbsPort, fragment, sent,
delivery );
Log.i( TAG, "sendBuffers(): sent %d byte fragment to %s",
fragment.length, phone );
if ( useProxy ) {
NBSProxy.send( this, phone, nbsPort, fragment );
} else {
mgr.sendDataMessage( phone, null, nbsPort, fragment,
sent, delivery );
}
// Log.i( TAG, "sendBuffers(): sent %d byte fragment to %s",
// fragment.length, phone );
}
success = true;
} catch ( IllegalArgumentException iae ) {
Log.w( TAG, "sendBuffers(%s): %s", phone, iae.toString() );
} catch ( NullPointerException npe ) {
Assert.fail(); // shouldn't be trying to do this!!!
Assert.assertFalse( BuildConfig.DEBUG ); // shouldn't be trying to do this!!!
} catch ( java.lang.SecurityException se ) {
mHelper.postEvent( MultiEvent.SMS_SEND_FAILED_NOPERMISSION );
} catch ( Exception ee ) {

View file

@ -52,6 +52,7 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
// constants that are also used in info.py
private static final String k_NAME = "name";
private static final String k_AVERS = "avers";
private static final String k_VARIANT = "variant";
private static final String k_GVERS = "gvers";
private static final String k_GHASH = "ghash";
private static final String k_INSTALLER = "installer";
@ -66,6 +67,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
private static final String k_DEBUG = "dbg";
private static final String k_XLATEINFO = "xlatinfo";
private static final String k_STRINGSHASH = "strings";
private static final String k_UPGRADE_TITLE = "title";
private static final String k_UPGRADE_BODY = "body";
@Override
public void onReceive( Context context, Intent intent )
@ -99,16 +102,6 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
interval_millis, pi );
}
// Is app upgradeable OR have we installed any dicts?
public static boolean haveToCheck( Context context )
{
boolean result = !Utils.isGooglePlayApp( context );
if ( !result ) { // give another chance
result = null != getDownloadedDicts( context );
}
return result;
}
public static void checkVersions( Context context, boolean fromUI )
{
JSONObject params = new JSONObject();
@ -123,17 +116,22 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
}
// App update
if ( BuildConfig.FOR_FDROID || Utils.isGooglePlayApp( context ) ) {
// Do nothing; can't or mustn't upgrade app
if ( BuildConfig.FOR_FDROID ) {
// Do nothing; can't upgrade app
} else {
String installer = pm.getInstallerPackageName( packageName );
if ( null == installer ) {
installer = "none";
}
try {
JSONObject appParams = new JSONObject();
appParams.put( k_VARIANT, BuildConfig.VARIANT_NAME );
appParams.put( k_AVERS, versionCode );
// Look at whether server needs these duplicates. PENDING....
appParams.put( k_GVERS, BuildConfig.GIT_REV );
appParams.put( k_GHASH, context.getString( R.string.git_rev ) );
appParams.put( k_GHASH, BuildConfig.GIT_REV );
appParams.put( k_INSTALLER, installer );
if ( devOK( context ) ) {
appParams.put( k_DEVOK, true );
@ -315,12 +313,17 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
.makeAppDownloadIntent( m_context, url );
}
String title =
LocUtils.getString( m_context, R.string.new_app_avail_fmt,
label );
String body =
LocUtils.getString( m_context,
R.string.new_app_avail );
// title and/or body might be in the reply
String title = app.optString( k_UPGRADE_TITLE, null );
if ( null == title ) {
title = LocUtils
.getString( m_context, R.string.new_app_avail_fmt, label );
}
String body = app.optString( k_UPGRADE_BODY, null );
if ( null == body ) {
body = LocUtils
.getString( m_context, R.string.new_app_avail );
}
Utils.postNotification( m_context, intent, title,
body, url.hashCode() );
gotOne = true;

View file

@ -125,7 +125,7 @@ public class Utils {
public static boolean isGSMPhone( Context context )
{
boolean result = false;
if ( Perms23.havePermission( Perm.READ_PHONE_STATE ) ) {
if ( Perms23.havePermissions( context, Perm.READ_PHONE_STATE ) ) {
SMSService.SMSPhoneInfo info = SMSService.getPhoneInfo( context );
result = null != info && info.isPhone && info.isGSM;
}
@ -141,7 +141,7 @@ public class Utils {
public static boolean deviceSupportsNBS( Context context )
{
boolean result = false;
if ( Perms23.havePermission( Perm.READ_PHONE_STATE ) ) {
if ( Perms23.havePermissions( context, Perm.READ_PHONE_STATE ) ) {
TelephonyManager tm = (TelephonyManager)
context.getSystemService( Context.TELEPHONY_SERVICE );
if ( null != tm ) {
@ -314,7 +314,7 @@ public class Utils {
synchronized ( s_phonesHash ) {
if ( s_phonesHash.containsKey( phone ) ) {
name = s_phonesHash.get( phone );
} else if ( Perms23.havePermission( Perm.READ_CONTACTS ) ) {
} else if ( Perms23.havePermissions( context, Perm.READ_CONTACTS ) ) {
try {
ContentResolver contentResolver = context
.getContentResolver();

View file

@ -40,7 +40,8 @@ import java.util.UUID;
import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
public class XWApp extends Application implements LifecycleObserver {
public class XWApp extends Application
implements LifecycleObserver, NBSProxy.Callbacks {
private static final String TAG = XWApp.class.getSimpleName();
public static final boolean BTSUPPORTED = true;
@ -62,6 +63,8 @@ public class XWApp extends Application implements LifecycleObserver {
private static Boolean s_onEmulator = null;
private static Context s_context = null;
private short mPort;
@Override
public void onCreate()
{
@ -72,7 +75,7 @@ public class XWApp extends Application implements LifecycleObserver {
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
android.util.Log.i( TAG, "onCreate(); git_rev="
+ getString( R.string.git_rev ) );
+ BuildConfig.GIT_REV );
Log.enable( this );
OnBootReceiver.startTimers( this );
@ -92,6 +95,11 @@ public class XWApp extends Application implements LifecycleObserver {
RelayService.startService( this );
FBMService.init( this );
WiDirWrapper.init( this );
mPort = Short.valueOf( getString( R.string.nbs_port ) );
if ( NBSProxy.isInstalled( this ) ) {
NBSProxy.register( mPort, BuildConfig.APPLICATION_ID, this );
}
}
@OnLifecycleEvent(ON_ANY)
@ -119,6 +127,24 @@ public class XWApp extends Application implements LifecycleObserver {
super.onTerminate();
}
// NBSProxy.Callbacks
@Override
public void onDataReceived( short port, String fromPhone, byte[] data )
{
Assert.assertTrue( port == mPort || !BuildConfig.DEBUG );
SMSService.handleFrom( this, data, fromPhone );
}
// NBSProxy.Callbacks
@Override
public void onRegResponse( boolean appReached )
{
if ( !appReached ) {
String channelID = Channels.getChannelID( this, Channels.ID.FOREGROUND );
NBSProxy.postLaunchNotification( this, channelID, R.drawable.notify );
}
}
public static UUID getAppUUID()
{
if ( null == s_UUID ) {

View file

@ -216,10 +216,8 @@ public class DUtilCtxt {
public void store( String key, byte[] data )
{
Log.d( TAG, "store(key=%s)", key );
if ( null == data ) {
} else {
// Log.d( TAG, "store(key=%s)", key );
if ( null != data ) {
DBUtils.setBytesFor( m_context, key, data );
}
}

View file

@ -136,7 +136,7 @@
<string name="key_invite_multi">key_invite_multi</string>
<string name="key_na_rematch_two_only">key_notagain_rematch_two_only</string>
<string name="key_notagain_dfltname">key_notagain_dfltname</string>
<string name="key_notagain_sms_banned">key_notagain_sms_banned</string>
<string name="key_notagain_nbsGamesOnUpgrade">key_notagain_nbsGamesOnUpgrade</string>
<string name="key_na_comms_bt">key_na_comms_bt</string>
<string name="key_na_comms_p2p">key_na_comms_p2p</string>
<string name="key_na_comms_sms">key_na_comms_sms</string>
@ -155,6 +155,7 @@
<!--string name="invite_mime">text/plain</string-->
<string name="dict_url">https://eehouse.org/and_wordlists</string>
<string name="nbs_ban_url">https://eehouse.org/sms.html</string>
<string name="default_update_url">https://eehouse.org/xw4/info.py</string>
<string name="default_relay_url">https://eehouse.org/xw4/relay.py</string>
<!-- <string name="default_relay_url">http://10.0.3.2/xw4/relay.py</string> -->

View file

@ -2700,12 +2700,23 @@
<string name="after_restart">This change will take effect after you
restart CrossWords.</string>
<string name="missing_perms">This game is configured to communicate
<string name="missing_sms_perms">This game is configured to communicate
via Data SMS but CrossWords does not have permission to do so. You
can still open the game, but it may not be able to send or receive
moves.\n\nYou can re-open it to be asked for permission again. Or
you can remove the Data SMS communication setting.</string>
<string name="not_again_nbsGamesOnUpgrade">The Google Play Store
version of CrossWords is no longer allowed to support the
Play-by-Data-SMS feature.
\n\n
You have %1$d open games that use this feature. They will not be
able to send or receive moves via Data SMS as long as you are using
the Google Play version of CrossWords, or until I can figure out a
workaround that meets Store policies.
\n\n
You can read more using the button below.</string>
<string name="download_rationale">CrossWords needs access to
temporary storage to keep what you\'re about to download.
</string>

View file

@ -2318,7 +2318,7 @@
<string name="dualpane_restart">Gnitixe ppa…</string>
<string name="after_restart">Siht egnahc lliw ton ekat tceffe litnu
uoy tratser Sdrowssorc.</string>
<string name="missing_perms">Siht emag si derugifnoc ot
<string name="missing_sms_perms">Siht emag si derugifnoc ot
etacinummoc aiv SMS tub Sdrowssorc seod ton evah noissimrep ot od
os. Uoy nac llits nepo eht ,emag tub ti yam ton eb elba ot dnes ro
eviecer sevom.\n\nUoy nac nepo-er ti ot eb deksa rof noissimrep

View file

@ -2318,7 +2318,7 @@
<string name="dualpane_restart">EXITING APP…</string>
<string name="after_restart">THIS CHANGE WILL NOT TAKE EFFECT UNTIL
YOU RESTART CROSSWORDS.</string>
<string name="missing_perms">THIS GAME IS CONFIGURED TO
<string name="missing_sms_perms">THIS GAME IS CONFIGURED TO
COMMUNICATE VIA SMS BUT CROSSWORDS DOES NOT HAVE PERMISSION TO DO
SO. YOU CAN STILL OPEN THE GAME, BUT IT MAY NOT BE ABLE TO SEND OR
RECEIVE MOVES.\n\nYOU CAN RE-OPEN IT TO BE ASKED FOR PERMISSION

View file

@ -1128,7 +1128,7 @@
<string name="disable_dualpane">Tablet-Layout deaktivieren</string>
<string name="after_restart">Diese Änderung wird aktiv, wenn Sie CrossWords neu starten.</string>
<string name="missing_perms">Diese Partie ist konfiguriert über SMS zu kommunizieren, aber CrossWords hat keine Berechtigung dazu. Sie können die Partie immer noch öffnen, aber möglicherweise keine Züge senden oder empfangen.
<string name="missing_sms_perms">Diese Partie ist konfiguriert über SMS zu kommunizieren, aber CrossWords hat keine Berechtigung dazu. Sie können die Partie immer noch öffnen, aber möglicherweise keine Züge senden oder empfangen.
\n
\nSie können sie neu öffnen, um wieder nach der Berechtigung gefragt zu werden. Oder Sie können die SMS-Verbindung aus den Einstellungen entfernen.</string>
@ -1341,4 +1341,4 @@
\n • Starten Sie CrossWords auf dem anderen Gerät
\n • Wenn alles nichts hilft, booten Sie dieses Gerät
\n</string>
</resources>
</resources>

View file

@ -3533,7 +3533,7 @@ Merci de me faire savoir si vous aimez cette fonctionnalité, de signaler les pl
<string name="not_again_comms_p2p">Utiliser le WiFi Direct pour jouer contre un appareil faisant du WiFi Direct, sur lequel CrossWords est installé.</string>
<string name="missing_perms">Cette partie est configurée pour communiquer par SMS mais Crosswords n\'a pas la permission de le faire. Vous pouvez toujours ouvrir cette partie, mais elle pourrait ne pas être en capacité d\'envoyer ou recevoir des coups.
<string name="missing_sms_perms">Cette partie est configurée pour communiquer par SMS mais Crosswords n\'a pas la permission de le faire. Vous pouvez toujours ouvrir cette partie, mais elle pourrait ne pas être en capacité d\'envoyer ou recevoir des coups.
Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouvez retirer le réglage de communication par SMS.</string>
@ -3624,4 +3624,4 @@ Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouve
<string name="force_tablet_default">Utiliser par défaut pour mon appareil</string>
<string name="button_close">Fermer</string>
<string name="btservice_expl">Cette notification est présente dès que CrossWords tourne en tâche de fond pour recevoir des messages Bluetooth. Elle reste pendant environ 15 minutes après que CrossWords ait démarré ou qu\'un message Bluetooth ait été reçu.</string>
</resources>
</resources>

View file

@ -848,7 +848,9 @@
WiFi ダイレクト経由で接続可能なデバイスはありません。</string>
<string name="not_again_comms_p2p">WiFi ダイレクトを使用して、クロスワードがインストールされた
近くの WiFi ダイレクト対応デバイスに対してプレイします。</string>
<string name="missing_perms">このゲームは SMS 経由で通信するように構成されていますが、
<string name="missing_sms_perms">このゲームは SMS 経由で通信するように構成されていますが、
クロスワードにそれを行うアクセス許可がありません。
まだ、ゲームを開くことができますが、移動を送信または受信できない場合があります。

View file

@ -1279,7 +1279,7 @@
<string name="not_again_dfltname_fmt">Du bruker forvalgt spillernavn \"%1$s\". Ønsker du å personalisere med ditt eget navn før du oppretter dette spillet?</string>
<string name="missing_perms">Dette spillet er satt opp for kommunikasjon via SMS, men CrossWords mangler tilgangen. Du kan åpne spillet, men det kan hende du ikke vil kunne sende eller motta trekk.
<string name="missing_sms_perms">Dette spillet er satt opp for kommunikasjon via SMS, men CrossWords mangler tilgangen. Du kan åpne spillet, men det kan hende du ikke vil kunne sende eller motta trekk.
\n
\nDu kan gjenåpne det for å bli spurt om tilgang igjen. Eller du kan fjerne SMS-kommunikasjonsinnstillingen.</string>
@ -1336,4 +1336,4 @@
\n • Kjør CrossWords på den andre enheten
\n • Hvis ingenting annet virker, start denne enheten på ny
\n</string>
</resources>
</resources>

View file

@ -23,6 +23,7 @@ except ImportError:
VERBOSE = False
k_NAME = 'name'
k_AVERS = 'avers'
k_VARIANT = 'variant'
k_GVERS = 'gvers'
k_INSTALLER = 'installer'
k_DEVOK = 'devOK'
@ -35,6 +36,9 @@ k_LOCALE = 'locale'
k_XLATPROTO = 'proto'
k_XLATEVERS = 'xlatevers'
k_STRINGSHASH = 'strings'
k_UPGRADE_TITLE = 'title'
k_UPGRADE_BODY = 'body'
k_DICT_HEADER_MASK = 0x08

View file

@ -0,0 +1,60 @@
<html>
<body style="max-width: 500px; width:100%; margin: 0px auto;"
>
<h2>CrossWords and Play-via-Data-SMS</h2>
<p>Play-via-Data-SMS is one of my favorite features of
CrossWords. It's super-reliable, and works between GSM phones
round-the-world without requiring any kind of server. Most apps
have servers in order to harvest saleable infomation about you
or to show ads, but CrossWords doesn't care about that and so
has always supported this feature.</p>
<p>Unfortunately, Google's decided there are security problems
with SMS (or at least that's their claimed motivation) and is
prohibiting most apps from continuing to send or receive SMS
(including Data SMS) if they want to be listed on the Play
Store. And so I've removed the feature from the CrossWords
"variant" that's available there. The feature remains, however,
in other variants, including those at:
<ul>
<li><a href="https://github.com/eehouse/xwords/releases/">Github
OpenSource repository</a></li>
<li><a href="https://sourceforge.net/projects/xwords/">Sourceforge
OpenSource repository</a></li>
<li><a href="https://f-droid.org/en/packages/org.eehouse.android.xw4/">F-droid
OpenSource "app store"</a></li>
</ul>
</p>
<p>(The GitHub and Sourceforge variants will install over the top
of the Play Store variant without problems. With the F-droid
variant, however, you'll have to uninstall your current
variant first, losing your games and settings.)</p>
<p>I've also starting working on a data-SMS-forwarding app called
NBSProxy. With NBSProxy installed, CrossWords can communicate
using Data SMS without the SMS permissions (since NBSProxy has
them, and is the one using Android's Data SMS functionality.)
Google says that apps whose core functionality requires SMS
permissions will be allowed on the Play Store, so NBSProxy
should be available there soon. In the meantime, you
can <a href="https://github.com/eehouse/nbsproxy/releases">get
it from GitHub</a>.
</p>
<p>Side note: It'd be helpful to know how many people use the
Play-via-Data-SMS feature in deciding whether to keep these
separate variants going. Would you
mind <a href="mailto:xwords@eehouse.org">emailing me</a> and
letting me know whether you've found it useful?
</p>
<p>Thanks!
<br>
<br>
--Eric
</p>
</body>
</html>