perms: ask for the set needed to open a game

rewrite perms23 class to use Builder pattern, making it easier to pass
more than one required permission. Use that (with hard-coded set for
now) to check and ask for permissions a game needs to communicate. Offer
to remove the comms methods the user doesn't want to permit (but that's
not implemented yet.)
This commit is contained in:
Eric House 2016-12-11 20:54:24 -08:00
parent dfb22c13fa
commit c398af2ac9
9 changed files with 941 additions and 836 deletions

File diff suppressed because it is too large Load diff

View file

@ -125,6 +125,7 @@
<string name="key_nag_intervals">key_nag_intervals</string>
<string name="key_download_path">key_download_path</string>
<string name="key_got_langdict">key_got_langdict</string>
<string name="key_notagain_missing_perms">key_notagain_missing_perms</string>
<string name="key_xlations_locale">key_xlations_locale</string>
<string name="key_xlations_enabled">key_xlations_enabled</string>
<string name="key_invite_multi">key_invite_multi</string>

View file

@ -2718,4 +2718,13 @@
<string name="dualpane_restart">Exiting app…</string>
<string name="after_restart">This change will not take effect until
you restart Crosswords.</string>
<string name="not_again_missing_perms">This game is configured to
communicate in at least one way that requires a permission that has
not been granted. You can still open it, but it may not be able to
send or receive moves.\n\nYou can re-open it to be asked again. Or
you can remove the settings that require missing permissions.</string>
<string name="remove_connvia">Remove</string>
</resources>

View file

@ -2321,4 +2321,10 @@
<string name="dualpane_restart">Gnitixe ppa…</string>
<string name="after_restart">Siht egnahc lliw ton ekat tceffe litnu
uoy tratser Sdrowssorc.</string>
<string name="not_again_missing_perms">Siht emag si derugifnoc ot
etacinummoc ni ta tsael eno yaw taht seriuqer a noissimrep taht sah
ton neeb detnarg. Uoy nac llits nepo ,ti tub ti yam ton eb elba ot
dnes ro eviecer sevom.\n\nUoy nac nepo-er ti ot eb deksa niaga. Ro
uoy nac evomer eht sgnittes taht eriuqer gnissim snoissimrep.</string>
<string name="remove_connvia">Evomer</string>
</resources>

View file

@ -2321,4 +2321,10 @@
<string name="dualpane_restart">EXITING APP…</string>
<string name="after_restart">THIS CHANGE WILL NOT TAKE EFFECT UNTIL
YOU RESTART CROSSWORDS.</string>
<string name="not_again_missing_perms">THIS GAME IS CONFIGURED TO
COMMUNICATE IN AT LEAST ONE WAY THAT REQUIRES A PERMISSION THAT HAS
NOT BEEN GRANTED. YOU CAN STILL OPEN IT, BUT IT MAY NOT BE ABLE TO
SEND OR RECEIVE MOVES.\n\nYOU CAN RE-OPEN IT TO BE ASKED AGAIN. OR
YOU CAN REMOVE THE SETTINGS THAT REQUIRE MISSING PERMISSIONS.</string>
<string name="remove_connvia">REMOVE</string>
</resources>

View file

@ -28,6 +28,8 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import java.util.Map;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -82,12 +84,13 @@ public class ConnViaViewLayout extends LinearLayout {
// ask for it here. When we get what we're getting, proceed to
// actually check for what's supported. Those methods will return
// false if they don't have the permission they need.
Perms23.doWithPermission( m_activity, Perms23.Perm.READ_PHONE_STATE,
new Perms23.PermCbck() {
public void onPermissionResult( Perms23.Perm perm, boolean granted ) {
addConnectionsPostPermCheck();
}
} );
new Perms23.Builder( Perms23.Perm.READ_PHONE_STATE )
.asyncQuery( m_activity, new Perms23.PermCbck() {
@Override
public void onPermissionResult( Map<Perms23.Perm, Boolean> granted ) {
addConnectionsPostPermCheck();
}
} );
}
private void addConnectionsPostPermCheck()

View file

@ -71,6 +71,8 @@ public class DlgDelegate {
NEW_GAME_DFLT_NAME,
ENABLE_DUALPANE,
ENABLE_DUALPANE_EXIT,
LAUNCH_PERMS_REMOVE,
LAUNCH_PERMS,
// BoardDelegate
UNDO_LAST_ACTION,

View file

@ -1315,6 +1315,15 @@ public class GamesListDelegate extends ListDelegateBase
m_newGameParams = params;
askDefaultName();
break;
case LAUNCH_PERMS_REMOVE:
Assert.fail(); // not implemented
break;
case LAUNCH_PERMS:
long rowid = (Long)params[0];
launchGameAfterPerms( rowid );
break;
default:
Assert.fail();
}
@ -1559,33 +1568,33 @@ public class GamesListDelegate extends ListDelegateBase
break;
case R.id.games_menu_loaddb:
Perms23.doWithPermission( m_activity, Perms23.Perm.STORAGE,
new Perms23.PermCbck() {
public void onPermissionResult( Perms23.Perm perm,
boolean granted )
{
Assert.assertTrue( Perms23.Perm.STORAGE == perm );
if ( granted ) {
DBUtils.loadDB( m_activity );
XWPrefs.clearGroupPositions( m_activity );
mkListAdapter();
}
}
} );
new Perms23.Builder( Perms23.Perm.STORAGE )
.asyncQuery( m_activity, new Perms23.PermCbck() {
@Override
public void onPermissionResult( Map<Perms23.Perm, Boolean> granted )
{
Assert.assertTrue( granted.containsKey(Perms23.Perm.STORAGE) );
if ( granted.get(Perms23.Perm.STORAGE) ) {
DBUtils.loadDB( m_activity );
XWPrefs.clearGroupPositions( m_activity );
mkListAdapter();
}
}
} );
break;
case R.id.games_menu_storedb:
Perms23.doWithPermission( m_activity, Perms23.Perm.STORAGE,
new Perms23.PermCbck() {
public void onPermissionResult( Perms23.Perm perm,
boolean granted )
{
Assert.assertTrue( Perms23.Perm.STORAGE == perm );
if ( granted ) {
DBUtils.saveDB( m_activity );
showToast( R.string.db_store_done );
}
}
} );
new Perms23.Builder( Perms23.Perm.STORAGE )
.asyncQuery( m_activity, new Perms23.PermCbck() {
@Override
public void onPermissionResult( Map<Perms23.Perm, Boolean> granted )
{
Assert.assertTrue( granted.containsKey( Perms23.Perm.STORAGE ) );
if ( granted.get( Perms23.Perm.STORAGE ) ) {
DBUtils.saveDB( m_activity );
showToast( R.string.db_store_done );
}
}
} );
break;
default:
@ -2423,23 +2432,74 @@ public class GamesListDelegate extends ListDelegateBase
tryNFCIntent( intent );
}
private Set<Perms23.Perm> getPermsNeededFor( GameSummary summary )
{
// PENDING: do this for real
Set<Perms23.Perm> perms = new HashSet<Perms23.Perm>();
perms.add( Perms23.Perm.READ_PHONE_STATE );
perms.add( Perms23.Perm.STORAGE );
perms.add( Perms23.Perm.SEND_SMS );
perms.add( Perms23.Perm.READ_CONTACTS );
return perms;
}
private void doOpenGame( Object[] params )
{
GameSummary summary = (GameSummary)params[1];
long rowid = (Long)params[0];
final long rowid = (Long)params[0];
if ( summary.conTypes.contains( CommsAddrRec.CommsConnType.COMMS_CONN_RELAY )
&& summary.roomName.length() == 0 ) {
Assert.fail();
} else {
try {
if ( checkWarnNoDict( rowid ) ) {
launchGame( rowid );
}
} catch ( GameLock.GameLockedException gle ) {
DbgUtils.logex( TAG, gle );
finish();
final Set<Perms23.Perm> perms = getPermsNeededFor( summary );
new Perms23.Builder( perms )
.asyncQuery( m_activity, new Perms23.PermCbck() {
@Override
public void onPermissionResult( Map<Perms23.Perm,
Boolean> perms ) {
// If we get here, we were missing some
// permissions. The user may or may not have
// granted them. We'll go ahead and launch the
// game, but some stuff may fail (because we'll
// check for permissions later, e.g. when sending
// an SMS message, without allowing for asking
// again.
Set<Perms23.Perm> missing = new HashSet<Perms23.Perm>();
for ( Iterator<Perms23.Perm> iter = perms.keySet().iterator();
iter.hasNext(); ) {
Perms23.Perm perm = iter.next();
boolean granted = perms.get( perm );
if ( !granted ) {
missing.add( perm );
}
}
if ( 0 == missing.size() ) {
launchGameAfterPerms( rowid );
} else {
ActionPair pair = new ActionPair( Action.LAUNCH_PERMS_REMOVE,
R.string.remove_connvia );
makeNotAgainBuilder( R.string.not_again_missing_perms,
R.string.key_notagain_missing_perms,
Action.LAUNCH_PERMS )
.setActionPair( pair )
.setParams( rowid, missing )
.show();
}
}
} );
}
}
private void launchGameAfterPerms( long rowid )
{
try {
if ( checkWarnNoDict( rowid ) ) {
launchGame( rowid );
}
} catch ( GameLock.GameLockedException gle ) {
DbgUtils.logex( TAG, gle );
finish();
}
}

View file

@ -24,15 +24,20 @@ import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Perms23 {
private static final String TAG = Perms23.class.getSimpleName();
public static enum Perm {
READ_PHONE_STATE("android.permission.READ_PHONE_STATE"),
STORAGE("android.permission.WRITE_EXTERNAL_STORAGE")
STORAGE("android.permission.WRITE_EXTERNAL_STORAGE"),
SEND_SMS("android.permission.SEND_SMS"),
READ_CONTACTS("android.permission.READ_CONTACTS")
;
private String m_str;
@ -51,56 +56,77 @@ public class Perms23 {
}
public interface PermCbck {
void onPermissionResult( Perm perm, boolean granted );
void onPermissionResult( Map<Perm, Boolean> perms );
}
public static class Builder {
private Set<Perm> m_perms = new HashSet<Perm>();
public Builder(Set<Perm> perms) {
m_perms.addAll( perms );
}
public Builder( Perm perm ) {
m_perms.add( perm );
}
public void asyncQuery( Activity activity, PermCbck cbck )
{
DbgUtils.logd( TAG, "asyncQuery()" );
boolean haveAll = true;
boolean shouldShow = false;
ArrayList<String> askStrings = new ArrayList<String>();
for ( Perm perm : m_perms ) {
String permStr = perm.getString();
boolean haveIt = PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission( activity, permStr );
// For research: ask the OS if we should be printing a rationale
if ( BuildConfig.DEBUG && !haveIt ) {
shouldShow = shouldShow || ActivityCompat
.shouldShowRequestPermissionRationale( activity, permStr );
}
haveAll = haveAll && haveIt;
if ( !haveIt ) {
askStrings.add( permStr );
}
}
if ( shouldShow ) {
DbgUtils.showf( "Should show rationale!!!" );
}
if ( haveAll ) {
Map<Perm, Boolean> map = new HashMap<Perm, Boolean>();
for ( Perm perm : m_perms ) {
map.put( perm, true );
}
cbck.onPermissionResult( map );
} else {
String[] permsArray = askStrings.toArray( new String[askStrings.size()] );
int code = register( cbck );
ActivityCompat.requestPermissions( activity, permsArray, code );
}
DbgUtils.logd( TAG, "asyncQuery(%s) => %b", m_perms.toString(), haveAll );
}
}
private static Map<Integer, PermCbck> s_map = new HashMap<Integer, PermCbck>();
public static void gotPermissionResult( int code, String[] perms, int[] granteds )
{
CbckRecord record = s_map.get( code );
Map<Perm, Boolean> result = new HashMap<Perm, Boolean>();
for ( int ii = 0; ii < perms.length; ++ii ) {
Perm perm = Perm.getFor( perms[ii] );
boolean granted = PackageManager.PERMISSION_GRANTED == granteds[ii];
DbgUtils.logd( TAG, "calling %s.onPermissionResult(%s, %b)",
record.cbck.getClass().getSimpleName(), perm.toString(),
granted );
record.cbck.onPermissionResult( perm, granted );
result.put( perm, granted );
// DbgUtils.logd( TAG, "calling %s.onPermissionResult(%s, %b)",
// record.cbck.getClass().getSimpleName(), perm.toString(),
// granted );
}
}
public static void doWithPermission( Activity activity, Perm perm, PermCbck cbck )
{
DbgUtils.logd( TAG, "doWithPermission()" );
String permStr = perm.getString();
if ( PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission( activity, permStr ) ) {
DbgUtils.logd( TAG, "doWithPermission(): already have it" );
cbck.onPermissionResult( perm, true );
} else {
// Should we show an explanation?
boolean shouldShow = ActivityCompat
.shouldShowRequestPermissionRationale( activity, permStr );
if ( shouldShow && BuildConfig.DEBUG ) {
DbgUtils.logd( TAG, "should show rationalle!!!" );
}
// Show an explanation to the user *asynchronously* -- don't
// block // this thread waiting for the user's
// response! After the user // sees the
// explanation, try again to request the
// permission.
String[] perms = new String[]{ permStr };
int code = register( perms, cbck );
ActivityCompat.requestPermissions( activity, perms, code );
}
// int check = ContextCompat.checkSelfPermission( activity, permStr );
// if ( PackageManager.PERMISSION_GRANTED == check ) {
// if ( null != onSuccess ) {
// onSuccess.run();
// }
// } else {
// Assert.assertTrue( PackageManager.PERMISSION_DENIED == check );
// }
s_map.get( code ).onPermissionResult( result );
}
public static boolean havePermission( Perm perm )
@ -112,23 +138,12 @@ public class Perms23 {
return result;
}
private static class CbckRecord {
public PermCbck cbck;
public String[] perms;
public CbckRecord( String[] perms, PermCbck cbck ) {
this.perms = perms;
this.cbck = cbck;
}
}
private static int s_nextRecord;
private static Map<Integer, CbckRecord> s_map = new HashMap<Integer, CbckRecord>();
private static int register( String[] perms, PermCbck cbck )
private static int register( PermCbck cbck )
{
DbgUtils.assertOnUIThread();
int code = ++s_nextRecord;
CbckRecord record = new CbckRecord( perms, cbck );
s_map.put( code, record );
s_map.put( code, cbck );
return code;
}
}