Remove call log permissions, use SMS Retriever API during registration.

This is to adhere to the Play Store policy updates.

See: https://play.google.com/about/privacy-security-deception/permissions/
This commit is contained in:
Greyson Parrelli
2018-12-12 13:12:44 -08:00
parent 19d5ba5c0e
commit 3b67382f67
7 changed files with 168 additions and 122 deletions

View File

@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;

View File

@@ -39,9 +39,14 @@ import android.widget.TextView;
import android.widget.Toast;
import com.dd.CircularProgressButton;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.tasks.Task;
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
@@ -76,6 +81,7 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.VerificationCodeParser;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs;
@@ -96,6 +102,8 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.push.LockedException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -113,8 +121,6 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
private static final int SCENE_TRANSITION_DURATION = 250;
private static final int DEBUG_TAP_TARGET = 8;
private static final int DEBUG_TAP_ANNOUNCE = 4;
public static final String CHALLENGE_EVENT = "org.thoughtcrime.securesms.CHALLENGE_EVENT";
public static final String CHALLENGE_EXTRA = "CAAChallenge";
public static final String RE_REGISTRATION_EXTRA = "re_registration";
private static final String TAG = RegistrationActivity.class.getSimpleName();
@@ -125,7 +131,6 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
private TextView countryCode;
private TextView number;
private CircularProgressButton createButton;
private TextView termsLinkView;
private TextView informationView;
private TextView informationToggleText;
private TextView title;
@@ -150,7 +155,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
private VerificationPinKeyboard keyboard;
private VerificationCodeView verificationCodeView;
private RegistrationState registrationState;
private ChallengeReceiver challengeReceiver;
private SmsRetrieverReceiver smsRetrieverReceiver;
private SignalServiceAccountManager accountManager;
private int debugTapCounter;
@@ -313,8 +318,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
Permissions.with(RegistrationActivity.this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CALL_LOG,
Manifest.permission.PROCESS_OUTGOING_CALLS)
Manifest.permission.READ_PHONE_STATE)
.ifNecessary()
.withRationaleDialog(getString(R.string.RegistrationActivity_signal_needs_access_to_your_contacts_and_media_in_order_to_connect_with_friends),
R.drawable.ic_contacts_white_48dp, R.drawable.ic_folder_white_48dp)
@@ -448,31 +452,12 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
return;
}
Permissions.with(this)
.request(Manifest.permission.READ_SMS)
.ifNecessary()
.withRationaleDialog(getString(R.string.RegistrationActivity_to_easily_verify_your_phone_number_signal_can_automatically_detect_your_verification_code), R.drawable.ic_textsms_white_48dp)
.onAnyResult(this::handleRegisterWithPermissions)
.execute();
}
private void handleRegisterWithPermissions() {
if (TextUtils.isEmpty(countryCode.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_country_code), Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(number.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
return;
}
final String e164number = getConfiguredE164Number();
if (!PhoneNumberFormatter.isValidNumber(e164number)) {
Dialogs.showAlertDialog(this, getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid),
e164number));
Dialogs.showAlertDialog(this,
getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), e164number));
return;
}
@@ -490,11 +475,30 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
}
}
@SuppressLint("StaticFieldLeak")
private void handleRequestVerification(@NonNull String e164number, boolean gcmSupported) {
createButton.setIndeterminateProgressMode(true);
createButton.setProgress(50);
if (gcmSupported) {
SmsRetrieverClient client = SmsRetriever.getClient(this);
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(none -> {
Log.i(TAG, "Successfully registered SMS listener.");
requestVerificationCode(e164number, true, true);
});
task.addOnFailureListener(e -> {
Log.w(TAG, "Failed to register SMS listener.", e);
requestVerificationCode(e164number, true, false);
});
} else {
requestVerificationCode(e164number, false, false);
}
}
@SuppressLint("StaticFieldLeak")
private void requestVerificationCode(@NonNull String e164number, boolean gcmSupported, boolean smsRetrieverSupported) {
new AsyncTask<Void, Void, Pair<String, Optional<String>>> () {
@Override
protected @Nullable Pair<String, Optional<String>> doInBackground(Void... voids) {
@@ -512,7 +516,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
}
accountManager = AccountManagerFactory.createManager(RegistrationActivity.this, e164number, password);
accountManager.requestSmsVerificationCode();
accountManager.requestSmsVerificationCode(smsRetrieverSupported);
return new Pair<>(password, gcmToken);
} catch (IOException e) {
@@ -535,22 +539,34 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void handleChallengeReceived(@Nullable String challenge) {
if (challenge != null && challenge.length() == 6 && registrationState.state == RegistrationState.State.VERIFYING) {
verificationCodeView.clear();
private void handleVerificationCodeReceived(@Nullable String code) {
List<Integer> parsedCode = convertVerificationCodeToDigits(code);
try {
for (int i=0;i<challenge.length();i++) {
final int index = i;
verificationCodeView.postDelayed(() -> verificationCodeView.append(Integer.parseInt(Character.toString(challenge.charAt(index)))), i * 200);
}
} catch (NumberFormatException e) {
Log.w(TAG, e);
verificationCodeView.clear();
}
for (int i = 0; i < parsedCode.size(); i++) {
int index = i;
verificationCodeView.postDelayed(() -> verificationCodeView.append(parsedCode.get(index)), i * 200);
}
}
private List<Integer> convertVerificationCodeToDigits(@Nullable String code) {
if (code == null || code.length() != 6 || registrationState.state != RegistrationState.State.VERIFYING) {
return Collections.emptyList();
}
List<Integer> result = new LinkedList<>();
try {
for (int i = 0; i < code.length(); i++) {
result.add(Integer.parseInt(Character.toString(code.charAt(i))));
}
} catch (NumberFormatException e) {
Log.w(TAG, "Failed to convert code into digits.",e );
return Collections.emptyList();
}
return result;
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCodeComplete(@NonNull String code) {
@@ -1005,15 +1021,15 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
}
private void initializeChallengeListener() {
challengeReceiver = new ChallengeReceiver();
IntentFilter filter = new IntentFilter(CHALLENGE_EVENT);
registerReceiver(challengeReceiver, filter);
smsRetrieverReceiver = new SmsRetrieverReceiver();
IntentFilter filter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION);
registerReceiver(smsRetrieverReceiver, filter);
}
private void shutdownChallengeListener() {
if (challengeReceiver != null) {
unregisterReceiver(challengeReceiver);
challengeReceiver = null;
if (smsRetrieverReceiver != null) {
unregisterReceiver(smsRetrieverReceiver);
smsRetrieverReceiver = null;
}
}
@@ -1040,11 +1056,32 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
else restoreBackupProgress.setText(getString(R.string.RegistrationActivity_d_messages_so_far, event.getCount()));
}
private class ChallengeReceiver extends BroadcastReceiver {
private class SmsRetrieverReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Got a challenge broadcast...");
handleChallengeReceived(intent.getStringExtra(CHALLENGE_EXTRA));
Log.i(TAG, "SmsRetrieverReceiver received a broadcast...");
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
Bundle extras = intent.getExtras();
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
switch (status.getStatusCode()) {
case CommonStatusCodes.SUCCESS:
Optional<String> code = VerificationCodeParser.parse(context, (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE));
if (code.isPresent()) {
Log.i(TAG, "Received verification code.");
handleVerificationCodeReceived(code.get());
} else {
Log.w(TAG, "Could not parse verification code.");
}
break;
case CommonStatusCodes.TIMEOUT:
Log.w(TAG, "Hit a timeout waiting for the SMS to arrive.");
break;
}
} else {
Log.w(TAG, "SmsRetrieverReceiver received the wrong action?");
}
}
}

View File

@@ -22,28 +22,19 @@ import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Telephony;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.jobs.SmsReceiveJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SmsListener extends BroadcastReceiver {
private static final String SMS_RECEIVED_ACTION = Telephony.Sms.Intents.SMS_RECEIVED_ACTION;
private static final String SMS_DELIVERED_ACTION = Telephony.Sms.Intents.SMS_DELIVER_ACTION;
private static final Pattern CHALLENGE_PATTERN = Pattern.compile(".*Your (Signal|TextSecure) verification code:? ([0-9]{3,4})-([0-9]{3,4}).*", Pattern.DOTALL);
private boolean isExemption(SmsMessage message, String messageBody) {
// ignore CLASS0 ("flash") messages
@@ -98,9 +89,6 @@ public class SmsListener extends BroadcastReceiver {
if (!ApplicationMigrationService.isDatabaseImported(context))
return false;
if (isChallenge(context, messageBody))
return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
SMS_RECEIVED_ACTION.equals(intent.getAction()) &&
Util.isDefaultSmsProvider(context))
@@ -117,43 +105,12 @@ public class SmsListener extends BroadcastReceiver {
return false;
}
@VisibleForTesting boolean isChallenge(@NonNull Context context, @Nullable String messageBody) {
if (messageBody == null)
return false;
if (CHALLENGE_PATTERN.matcher(messageBody).matches() &&
TextSecurePreferences.isVerifying(context))
{
return true;
}
return false;
}
@VisibleForTesting String parseChallenge(String messageBody) {
Matcher challengeMatcher = CHALLENGE_PATTERN.matcher(messageBody);
if (!challengeMatcher.matches()) {
throw new AssertionError("Expression should match.");
}
return challengeMatcher.group(2) + challengeMatcher.group(3);
}
@Override
public void onReceive(Context context, Intent intent) {
Log.i("SMSListener", "Got SMS broadcast...");
String messageBody = getSmsMessageBodyFromIntent(intent);
if (SMS_RECEIVED_ACTION.equals(intent.getAction()) && isChallenge(context, messageBody)) {
Log.w("SmsListener", "Got challenge!");
Intent challengeIntent = new Intent(RegistrationActivity.CHALLENGE_EVENT);
challengeIntent.putExtra(RegistrationActivity.CHALLENGE_EXTRA, parseChallenge(messageBody));
context.sendBroadcast(challengeIntent);
abortBroadcast();
} else if ((intent.getAction().equals(SMS_DELIVERED_ACTION)) ||
(intent.getAction().equals(SMS_RECEIVED_ACTION)) && isRelevant(context, intent))
if ((intent.getAction().equals(SMS_DELIVERED_ACTION)) ||
(intent.getAction().equals(SMS_RECEIVED_ACTION)) && isRelevant(context, intent))
{
Log.i("SmsListener", "Constructing SmsReceiveJob...");
Object[] pdus = (Object[]) intent.getExtras().get("pdus");

View File

@@ -0,0 +1,44 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.service;
import android.content.Context;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class VerificationCodeParser {
private static final Pattern CHALLENGE_PATTERN = Pattern.compile(".*Your (Signal|TextSecure) verification code:? ([0-9]{3,4})-([0-9]{3,4}).*", Pattern.DOTALL);
public static Optional<String> parse(Context context, String messageBody) {
if (messageBody == null) {
return Optional.absent();
}
Matcher challengeMatcher = CHALLENGE_PATTERN.matcher(messageBody);
if (!challengeMatcher.matches() || !TextSecurePreferences.isVerifying(context)) {
return Optional.absent();
}
return Optional.of(challengeMatcher.group(2) + challengeMatcher.group(3));
}
}