我有一个SMS服务,我的应用程序在其中响应特定的SMS消息。在可以对托管该应用的电话号码进行SMS查询之前,该应用会尝试验证用户。该过程应通过在第一次SMS时显示通知提示操作来完成从新号码收到。该通知提供两个选项,“批准”或“拒绝”。批准或拒绝在默认共享首选项中另存为布尔值以发件人的电话号码为密钥。
至少那是应该做的。
当我用来实现上述三个类的交互时,我陷入了一些奇怪的行为。它们是SMSReceiver,NotificationUtils和SMSAuthReceiver。
SMSReceiver
解析并响应传入的SMS消息。如果它检测到新用户的授权请求,它创建NotificationUtils
的实例,并使用showNotification
方法显示通知。showNotification
接受一个Context
对象和一个名为sender的String
,以保存传入请求的电话号码。该通知提供了拒绝意图和批准意图,由SMSAuthReceiver
处理。无论请求是批准还是拒绝,共享首选项都将相应更新,请参见下面的代码。
出现问题的行为如下:安装该应用程序后,新用户首次通过SMS进行联系时,身份验证过程将顺利进行。但是,所有连续的身份验证请求都在SMSAuthReceiver
阶段失败。它总是依赖于包含在其中的数据从安装应用程序时触发的第一个通知意图。
我已经尝试将通道ID和通知ID随机化,希望将它们分开对待,但是显然,我在某些方面不知所措。
我如何通过对下面的代码进行最小的更改来实现所需的行为?
SMSReceiver.java中的相关行:
if (
(StringUtils.equalsIgnoreCase(message,"myapp sign up")) ||
(StringUtils.equalsIgnoreCase(message,"myapp signup")) ||
(StringUtils.equalsIgnoreCase(message,"myapp start"))
){
NotificationUtils notificationUtils = new NotificationUtils();
notificationUtils.showNotification(context,sender); //Problems start here...
SendSMS(context.getString(R.string.sms_auth_pending));
}
NotificationUtils.java:
package com.myapp.name;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import org.apache.commons.lang3.RandomStringUtils;
public class NotificationUtils {
private static final String TAG = NotificationUtils.class.getSimpleName();
private int notificationID;
private String channelID;
public void hideNotification(Context context, int notificationId){
try {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
if (notificationId == 0) {
notificationManager.cancelAll();
} else {
notificationManager.cancel(notificationId);
}
}catch (Exception ignore){}
}
public void showNotification(Context context,String sender){
createNotificationChannel(context);
notificationID = getRandomID();
channelID = String.valueOf(getRandomID());
Log.d(TAG, "showNotification: Notification ID: "+notificationID);
Log.d(TAG, "showNotification: Channel ID: "+channelID);
Log.d(TAG, "showNotification: Sender: "+sender);
Intent approveAuth = new Intent(context, SMSAuthReceiver.class);
approveAuth.setAction("org.myapp.name.APPROVE_AUTH");
approveAuth.putExtra("sender",sender);
approveAuth.putExtra("notification_id",notificationID);
PendingIntent approveAuthP =
PendingIntent.getBroadcast(context, 0, approveAuth, 0);
Intent denyAuth = new Intent(context, SMSAuthReceiver.class);
denyAuth.setAction("org.myapp.name.DENY_AUTH");
denyAuth.putExtra("sender",sender);
denyAuth.putExtra("notification_id",notificationID);
PendingIntent denyAuthP =
PendingIntent.getBroadcast(context, 0, denyAuth, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelID)
.setSmallIcon(R.drawable.ic_lock_open)
.setContentTitle(context.getResources().getString(R.string.app_name))
.setContentText(sender+" "+context.getString(R.string.sms_noti_request))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(approveAuthP)
.addAction(R.drawable.ic_lock_open, context.getString(R.string.approve), approveAuthP)
.addAction(R.drawable.ic_lock_close, context.getString(R.string.deny), denyAuthP);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(notificationID, builder.build());
}
private int getRandomID(){
return Integer.parseInt(
RandomStringUtils.random(
8,
'1', '2', '3', '4', '5', '6', '7', '8', '9')
);
}
private void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "myapp Authorization Channel";
String description = "myapp SMS Service Authorizations";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(channelID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
try {
assert notificationManager != null;
notificationManager.createNotificationChannel(channel);
} catch (NullPointerException ex) {
Log.e(TAG,ex.getMessage(),ex);
}
}
}
}
SMSAuthReceiver.java
package com.myapp.name;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.telephony.SmsManager;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
import java.util.Objects;
public class SMSAuthReceiver extends BroadcastReceiver {
private static final String TAG = SMSAuthReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
try {
String sender = intent.getStringExtra("sender");
int id = intent.getIntExtra("notification_id",0);
/*Todo: bug! for some reason, data is always read from first intent, even if if
* more request come in. this causes the approval feature to add the same guy a bunch
* of times, and to mishandle dismissing the notification. (the purpose of this question...)*/
Log.d(TAG, "onReceive: Sender: "+sender);
NotificationUtils notificationUtils = new NotificationUtils();
notificationUtils.hideNotification(context,id);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
switch (Objects.requireNonNull(intent.getAction())) {
case "org.myapp.name.APPROVE_AUTH":
sharedPrefs.edit().putBoolean(sender,true).apply();
Toast.makeText(context, sender+" Approved!", Toast.LENGTH_SHORT).show();
SendSMS(context.getString(R.string.sms_invitation),sender);
break;
case "org.myapp.name.DENY_AUTH":
sharedPrefs.edit().putBoolean(sender,false).apply();
Toast.makeText(context, sender+" Denied!", Toast.LENGTH_SHORT).show();
SendSMS(context.getString(R.string.denied),sender);
break;
}
}catch (Exception e){
Log.e("SMSAuthReceiver", "onReceive: Error committing sender to preferences! ", e);
}
}
void SendSMS(String smsBody, String phone_number){
SmsManager manager = SmsManager.getDefault();
manager.sendTextMessage(phone_number,null,smsBody,null,null);
}
}
NotificationUtils.java
生成的日志始终输出“发送方”的当前电话号码,而SMSAuthReceiver.java
生成的日志始终反映测试该应用程序的第一部电话。为什么...?
感谢@MikeM。是谁吸引了我。
这里的问题是用于将操作传递给通知的一对PendingIntent
对象。构造函数的第二个参数接受唯一的ID,该ID可用于标识PendingIntent
的特定实例。在我的情况下,该ID始终为0
,因此导致在每个通知中重复使用同一实例。
我使用的解决方案是将为通知ID生成的随机数用作PendingIntent
的第二个参数,如下所示:
PendingIntent approveAuthP =
PendingIntent.getBroadcast(context, notificationID, approveAuth, 0);
而不是我使用的是:
PendingIntent approveAuthP =
PendingIntent.getBroadcast(context, 0, approveAuth, 0);
我希望这可以对使用PendingIntent
遇到类似问题的所有人有所帮助。