mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-30 08:34:16 +01:00
permissions: ask for READ_CONTACTS
On launching SMS invite dialog, ask for READ_CONTACTS permission. Where it's needed, though, is for the user-invisible process of translating phone numbers to names (not launching the Contacts app via an Intent). This just seems like the best place to ask since the user's thinking about contacts and phone numbers.
This commit is contained in:
parent
09188bc704
commit
f199aef738
9 changed files with 474 additions and 378 deletions
File diff suppressed because it is too large
Load diff
|
@ -1971,8 +1971,6 @@
|
||||||
up please make sure that WiFi is turned on, that Crosswords is
|
up please make sure that WiFi is turned on, that Crosswords is
|
||||||
installed, and that play via WiFi Direct is enabled.</string>
|
installed, and that play via WiFi Direct is enabled.</string>
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
<string name="manual_owner_name">(Not in contacts)</string>
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<string name="warn_nomobile_fmt">The number %1$s for %2$s is not
|
<string name="warn_nomobile_fmt">The number %1$s for %2$s is not
|
||||||
a \"mobile\" number. Import anyway?</string>
|
a \"mobile\" number. Import anyway?</string>
|
||||||
|
@ -2732,6 +2730,13 @@
|
||||||
safe to permanently deny permission.
|
safe to permanently deny permission.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<string name="contacts_rationale">Crosswords want access to your
|
||||||
|
contacts in order to put a name to phone numbers that send you
|
||||||
|
invitations via SMS. You\'ll still be able to receive invitations if
|
||||||
|
you don\'t grant this permission, but only the phone number of the
|
||||||
|
sender will be displayed.</string>
|
||||||
|
|
||||||
<string name="remove_sms">Remove SMS</string>
|
<string name="remove_sms">Remove SMS</string>
|
||||||
|
<string name="contact_not_found">Not in Contacts</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1705,8 +1705,6 @@
|
||||||
pu esaelp ekam erus taht IfIw si denrut ,no taht Sdrowssorc si
|
pu esaelp ekam erus taht IfIw si denrut ,no taht Sdrowssorc si
|
||||||
,dellatsni dna taht yalp aiv IfIw Tcerid si delbane.</string>
|
,dellatsni dna taht yalp aiv IfIw Tcerid si delbane.</string>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<string name="manual_owner_name">tOn( ni )stcatnoc</string>
|
|
||||||
<!-- -->
|
|
||||||
<string name="warn_nomobile_fmt">Eht rebmun %1$s rof %2$s si ton
|
<string name="warn_nomobile_fmt">Eht rebmun %1$s rof %2$s si ton
|
||||||
a \"elibom\" rebmun. Tropmi ?yawyna</string>
|
a \"elibom\" rebmun. Tropmi ?yawyna</string>
|
||||||
<!-- Shows in SMS Invite dialog when no phone numbers have been saved previously -->
|
<!-- Shows in SMS Invite dialog when no phone numbers have been saved previously -->
|
||||||
|
@ -2332,5 +2330,11 @@
|
||||||
e.g. esuaceb uoy yap rof hcae egassem ro evah a Nozirev ,enohp ti\'s
|
e.g. esuaceb uoy yap rof hcae egassem ro evah a Nozirev ,enohp ti\'s
|
||||||
efas ot yltnenamrep yned noissimrep.
|
efas ot yltnenamrep yned noissimrep.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="contacts_rationale">Sdrowssorc tnaw ssecca ot ruoy
|
||||||
|
stcatnoc ni redro ot tup a eman ot enohp srebmun taht dnes uoy
|
||||||
|
snoitativni aiv SMS. Uoy\'ll llits eb elba ot eviecer snoitativni fi
|
||||||
|
uoy nod\'t tnarg siht ,noissimrep tub ylno eht enohp rebmun fo eht
|
||||||
|
rednes lliw eb deyalpsid.</string>
|
||||||
<string name="remove_sms">Evomer SMS</string>
|
<string name="remove_sms">Evomer SMS</string>
|
||||||
|
<string name="contact_not_found">Ton ni Stcatnoc</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1705,8 +1705,6 @@
|
||||||
UP PLEASE MAKE SURE THAT WIFI IS TURNED ON, THAT CROSSWORDS IS
|
UP PLEASE MAKE SURE THAT WIFI IS TURNED ON, THAT CROSSWORDS IS
|
||||||
INSTALLED, AND THAT PLAY VIA WIFI DIRECT IS ENABLED.</string>
|
INSTALLED, AND THAT PLAY VIA WIFI DIRECT IS ENABLED.</string>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<string name="manual_owner_name">(NOT IN CONTACTS)</string>
|
|
||||||
<!-- -->
|
|
||||||
<string name="warn_nomobile_fmt">THE NUMBER %1$s FOR %2$s IS NOT
|
<string name="warn_nomobile_fmt">THE NUMBER %1$s FOR %2$s IS NOT
|
||||||
A \"MOBILE\" NUMBER. IMPORT ANYWAY?</string>
|
A \"MOBILE\" NUMBER. IMPORT ANYWAY?</string>
|
||||||
<!-- Shows in SMS Invite dialog when no phone numbers have been saved previously -->
|
<!-- Shows in SMS Invite dialog when no phone numbers have been saved previously -->
|
||||||
|
@ -2332,5 +2330,11 @@
|
||||||
E.G. BECAUSE YOU PAY FOR EACH MESSAGE OR HAVE A VERIZON PHONE, IT\'S
|
E.G. BECAUSE YOU PAY FOR EACH MESSAGE OR HAVE A VERIZON PHONE, IT\'S
|
||||||
SAFE TO PERMANENTLY DENY PERMISSION.
|
SAFE TO PERMANENTLY DENY PERMISSION.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="contacts_rationale">CROSSWORDS WANT ACCESS TO YOUR
|
||||||
|
CONTACTS IN ORDER TO PUT A NAME TO PHONE NUMBERS THAT SEND YOU
|
||||||
|
INVITATIONS VIA SMS. YOU\'LL STILL BE ABLE TO RECEIVE INVITATIONS IF
|
||||||
|
YOU DON\'T GRANT THIS PERMISSION, BUT ONLY THE PHONE NUMBER OF THE
|
||||||
|
SENDER WILL BE DISPLAYED.</string>
|
||||||
<string name="remove_sms">REMOVE SMS</string>
|
<string name="remove_sms">REMOVE SMS</string>
|
||||||
|
<string name="contact_not_found">NOT IN CONTACTS</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -112,6 +112,7 @@ public class DlgDelegate {
|
||||||
CLEAR_ACTION,
|
CLEAR_ACTION,
|
||||||
USE_IMMOBILE_ACTION,
|
USE_IMMOBILE_ACTION,
|
||||||
POST_WARNING_ACTION,
|
POST_WARNING_ACTION,
|
||||||
|
RETRY_CONTACTS_ACTION,
|
||||||
|
|
||||||
// BT Invite
|
// BT Invite
|
||||||
OPEN_BT_PREFS_ACTION,
|
OPEN_BT_PREFS_ACTION,
|
||||||
|
|
|
@ -87,6 +87,11 @@ public class Perms23 {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void asyncQuery( Activity activity )
|
||||||
|
{
|
||||||
|
asyncQuery( activity, null );
|
||||||
|
}
|
||||||
|
|
||||||
public void asyncQuery( Activity activity, PermCbck cbck )
|
public void asyncQuery( Activity activity, PermCbck cbck )
|
||||||
{
|
{
|
||||||
DbgUtils.logd( TAG, "asyncQuery(%s)", m_perms.toString() );
|
DbgUtils.logd( TAG, "asyncQuery(%s)", m_perms.toString() );
|
||||||
|
@ -117,11 +122,13 @@ public class Perms23 {
|
||||||
if ( 0 < needShow.size() && null != m_onShow ) {
|
if ( 0 < needShow.size() && null != m_onShow ) {
|
||||||
m_onShow.onShouldShowRationale( needShow );
|
m_onShow.onShouldShowRationale( needShow );
|
||||||
} else if ( haveAll ) {
|
} else if ( haveAll ) {
|
||||||
|
if ( null != cbck ) {
|
||||||
Map<Perm, Boolean> map = new HashMap<Perm, Boolean>();
|
Map<Perm, Boolean> map = new HashMap<Perm, Boolean>();
|
||||||
for ( Perm perm : m_perms ) {
|
for ( Perm perm : m_perms ) {
|
||||||
map.put( perm, true );
|
map.put( perm, true );
|
||||||
}
|
}
|
||||||
cbck.onPermissionResult( map );
|
cbck.onPermissionResult( map );
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String[] permsArray = askStrings.toArray( new String[askStrings.size()] );
|
String[] permsArray = askStrings.toArray( new String[askStrings.size()] );
|
||||||
int code = register( cbck );
|
int code = register( cbck );
|
||||||
|
@ -135,6 +142,8 @@ public class Perms23 {
|
||||||
private static Map<Integer, PermCbck> s_map = new HashMap<Integer, PermCbck>();
|
private static Map<Integer, PermCbck> s_map = new HashMap<Integer, PermCbck>();
|
||||||
public static void gotPermissionResult( int code, String[] perms, int[] granteds )
|
public static void gotPermissionResult( int code, String[] perms, int[] granteds )
|
||||||
{
|
{
|
||||||
|
PermCbck cbck = s_map.get( code );
|
||||||
|
if ( null != cbck ) {
|
||||||
Map<Perm, Boolean> result = new HashMap<Perm, Boolean>();
|
Map<Perm, Boolean> result = new HashMap<Perm, Boolean>();
|
||||||
for ( int ii = 0; ii < perms.length; ++ii ) {
|
for ( int ii = 0; ii < perms.length; ++ii ) {
|
||||||
Perm perm = Perm.getFor( perms[ii] );
|
Perm perm = Perm.getFor( perms[ii] );
|
||||||
|
@ -144,7 +153,8 @@ public class Perms23 {
|
||||||
// record.cbck.getClass().getSimpleName(), perm.toString(),
|
// record.cbck.getClass().getSimpleName(), perm.toString(),
|
||||||
// granted );
|
// granted );
|
||||||
}
|
}
|
||||||
s_map.get( code ).onPermissionResult( result );
|
cbck.onPermissionResult( result );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean havePermission( Perm perm )
|
public static boolean havePermission( Perm perm )
|
||||||
|
|
|
@ -48,8 +48,12 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
public class SMSInviteDelegate extends InviteDelegate
|
public class SMSInviteDelegate extends InviteDelegate
|
||||||
implements View.OnClickListener {
|
implements View.OnClickListener {
|
||||||
private static final String TAG = SMSInviteDelegate.class.getSimpleName();
|
private static final String TAG = SMSInviteDelegate.class.getSimpleName();
|
||||||
|
@ -100,6 +104,8 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
|
|
||||||
getSavedState();
|
getSavedState();
|
||||||
rebuildList( true );
|
rebuildList( true );
|
||||||
|
|
||||||
|
askContactsPermission( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -210,33 +216,49 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
|
|
||||||
// DlgDelegate.DlgClickNotify interface
|
// DlgDelegate.DlgClickNotify interface
|
||||||
@Override
|
@Override
|
||||||
public void dlgButtonClicked( Action action, int which, Object[] params )
|
public void dlgButtonClicked( Action action, int which,
|
||||||
|
final Object[] params )
|
||||||
{
|
{
|
||||||
switch( which ) {
|
boolean isPositive = AlertDialog.BUTTON_POSITIVE == which;
|
||||||
case AlertDialog.BUTTON_POSITIVE:
|
DbgUtils.logd( TAG, "dlgButtonClicked(%s,pos:%b)",
|
||||||
switch( action ) {
|
action.toString(), isPositive );
|
||||||
case CLEAR_ACTION:
|
|
||||||
clearSelectedImpl();
|
switch ( action ) {
|
||||||
|
case RETRY_CONTACTS_ACTION:
|
||||||
|
askContactsPermission( false );
|
||||||
break;
|
break;
|
||||||
case USE_IMMOBILE_ACTION:
|
case CLEAR_ACTION:
|
||||||
m_immobileConfirmed = true;
|
if ( isPositive) {
|
||||||
|
clearSelectedImpl();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case POST_WARNING_ACTION:
|
case POST_WARNING_ACTION:
|
||||||
|
DbgUtils.printStack( TAG );
|
||||||
|
if ( isPositive ) { // ???
|
||||||
Assert.assertTrue( ((String)params[0]).equals(m_pendingNumber) );
|
Assert.assertTrue( ((String)params[0]).equals(m_pendingNumber) );
|
||||||
Assert.assertTrue( params[1] == null
|
Assert.assertTrue( params[1] == null
|
||||||
|| ((String)params[1]).equals(m_pendingName) );
|
|| ((String)params[1]).equals(m_pendingName) );
|
||||||
m_phoneRecs.add( new PhoneRec( m_pendingName, m_pendingNumber ) );
|
m_phoneRecs.add( new PhoneRec( m_pendingName, m_pendingNumber ) );
|
||||||
saveAndRebuild();
|
saveAndRebuild();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DlgDelegate.DISMISS_BUTTON:
|
case USE_IMMOBILE_ACTION:
|
||||||
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
|
if ( isPositive ) {
|
||||||
|
m_immobileConfirmed = true;
|
||||||
|
} else if ( m_immobileConfirmed ) {
|
||||||
|
// Putting up a new alert from inside another's handler
|
||||||
|
// confuses things. So post instead.
|
||||||
|
post( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
makeConfirmThenBuilder( R.string.warn_unlimited,
|
makeConfirmThenBuilder( R.string.warn_unlimited,
|
||||||
Action.POST_WARNING_ACTION )
|
Action.POST_WARNING_ACTION )
|
||||||
.setPosButton( R.string.button_yes )
|
.setPosButton( R.string.button_yes )
|
||||||
|
.setParams( params )
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,6 +301,7 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
number, name );
|
number, name );
|
||||||
makeConfirmThenBuilder( msg, Action.USE_IMMOBILE_ACTION )
|
makeConfirmThenBuilder( msg, Action.USE_IMMOBILE_ACTION )
|
||||||
.setPosButton( R.string.button_yes )
|
.setPosButton( R.string.button_yes )
|
||||||
|
.setParams( number, name )
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,22 +329,28 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
|
|
||||||
private void getSavedState()
|
private void getSavedState()
|
||||||
{
|
{
|
||||||
String[] phones = XWPrefs.getSMSPhones( m_activity );
|
JSONObject phones = XWPrefs.getSMSPhones( m_activity );
|
||||||
|
|
||||||
m_phoneRecs = new ArrayList<PhoneRec>(phones.length);
|
m_phoneRecs = new ArrayList<PhoneRec>();
|
||||||
for ( String phone : phones ) {
|
for ( Iterator<String> iter = phones.keys(); iter.hasNext(); ) {
|
||||||
PhoneRec rec = new PhoneRec( null, phone );
|
String phone = iter.next();
|
||||||
|
String name = phones.optString( phone, null );
|
||||||
|
PhoneRec rec = new PhoneRec( name, phone );
|
||||||
m_phoneRecs.add( rec );
|
m_phoneRecs.add( rec );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndRebuild()
|
private void saveAndRebuild()
|
||||||
{
|
{
|
||||||
String[] phones = new String[m_phoneRecs.size()];
|
JSONObject phones = new JSONObject();
|
||||||
Iterator<PhoneRec> iter = m_phoneRecs.iterator();
|
Iterator<PhoneRec> iter = m_phoneRecs.iterator();
|
||||||
for ( int ii = 0; iter.hasNext(); ++ii ) {
|
while ( iter.hasNext() ) {
|
||||||
PhoneRec rec = iter.next();
|
PhoneRec rec = iter.next();
|
||||||
phones[ii] = rec.m_phone;
|
try {
|
||||||
|
phones.put( rec.m_phone, rec.m_name );
|
||||||
|
} catch ( JSONException ex ) {
|
||||||
|
DbgUtils.logex( TAG, ex );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
XWPrefs.setSMSPhones( m_activity, phones );
|
XWPrefs.setSMSPhones( m_activity, phones );
|
||||||
|
|
||||||
|
@ -340,6 +369,23 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
saveAndRebuild();
|
saveAndRebuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void askContactsPermission( boolean showRationale )
|
||||||
|
{
|
||||||
|
Perms23.Builder builder = new Perms23.Builder( Perms23.Perm.READ_CONTACTS );
|
||||||
|
if ( showRationale ) {
|
||||||
|
builder.setOnShowRationale( new Perms23.OnShowRationale() {
|
||||||
|
@Override
|
||||||
|
public void onShouldShowRationale( Set<Perms23.Perm> perms )
|
||||||
|
{
|
||||||
|
makeOkOnlyBuilder( R.string.contacts_rationale )
|
||||||
|
.setAction( Action.RETRY_CONTACTS_ACTION )
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
builder.asyncQuery( m_activity );
|
||||||
|
}
|
||||||
|
|
||||||
private class PhoneRec implements InviterItem {
|
private class PhoneRec implements InviterItem {
|
||||||
public String m_phone;
|
public String m_phone;
|
||||||
public String m_name;
|
public String m_name;
|
||||||
|
@ -354,10 +400,7 @@ public class SMSInviteDelegate extends InviteDelegate
|
||||||
m_phone = phone;
|
m_phone = phone;
|
||||||
|
|
||||||
if ( null == name ) {
|
if ( null == name ) {
|
||||||
name = Utils.phoneToContact( m_activity, phone, false );
|
name = getString( R.string.contact_not_found );
|
||||||
if ( null == name ) {
|
|
||||||
name = getString( R.string.manual_owner_name );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m_name = name;
|
m_name = name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,7 +295,8 @@ public class Utils {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
s_phonesHash.put( phone, name );
|
s_phonesHash.put( phone, name );
|
||||||
} catch ( Exception ex ) {
|
} catch ( Exception ex ) {
|
||||||
name = "not found";
|
// could just be lack of permsisions
|
||||||
|
name = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,16 @@ import android.content.res.Configuration;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
||||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
|
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
|
||||||
|
|
||||||
public class XWPrefs {
|
public class XWPrefs {
|
||||||
|
private static final String TAG = XWPrefs.class.getSimpleName();
|
||||||
|
|
||||||
// No reason to put this in xml if they're private to this file!
|
// No reason to put this in xml if they're private to this file!
|
||||||
private static final String key_checked_upgrades = "key_checked_upgrades";
|
private static final String key_checked_upgrades = "key_checked_upgrades";
|
||||||
|
@ -236,14 +240,39 @@ public class XWPrefs {
|
||||||
return getPrefsStringArray( context, R.string.key_closed_langs );
|
return getPrefsStringArray( context, R.string.key_closed_langs );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setSMSPhones( Context context, String[] names )
|
public static void setSMSPhones( Context context, JSONObject phones )
|
||||||
{
|
{
|
||||||
setPrefsStringArray( context, R.string.key_sms_phones, names );
|
setPrefsString( context, R.string.key_sms_phones, phones.toString() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getSMSPhones( Context context )
|
public static JSONObject getSMSPhones( Context context )
|
||||||
{
|
{
|
||||||
return getPrefsStringArray( context, R.string.key_sms_phones );
|
String asStr = getPrefsString( context, R.string.key_sms_phones );
|
||||||
|
JSONObject obj = null;
|
||||||
|
|
||||||
|
if ( null != asStr ) {
|
||||||
|
try {
|
||||||
|
obj = new JSONObject( asStr );
|
||||||
|
} catch ( JSONException ex ) {
|
||||||
|
obj = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( null == obj ) {
|
||||||
|
obj = new JSONObject();
|
||||||
|
if ( null != asStr ) {
|
||||||
|
String[] numbers = TextUtils.split( asStr, "\n" );
|
||||||
|
for ( String number : numbers ) {
|
||||||
|
try {
|
||||||
|
obj.put( number, (String)null );
|
||||||
|
} catch ( JSONException ex ) {
|
||||||
|
DbgUtils.logex( TAG, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by RelayInviteDelegate.java
|
// Used by RelayInviteDelegate.java
|
||||||
|
|
Loading…
Add table
Reference in a new issue