交互式ussd会话(多步骤)在android 8(Oreo)上不起作用
我目前正在使用android api level 26(Nexus 6P)中提供的电话管理器(USSD response)。对于单步ussd会话,它是有效的 参考: 例如: USSD请求:“A”(USSD会话启动) USSD响应:“X”(USSD会话终止) 但对于交互式ussd请求响应(多步骤),它不起作用。 多步骤场景如下所示: 第1步 USSD请求:“A”(USSD会话启动) USSD回应:“X” 第二步 USSD请求:“B”(USSD会议继续) USSD回应:“Y” 第三步 USSD请求:“C”交互式ussd会话(多步骤)在android 8(Oreo)上不起作用,android,ussd,android-8.0-oreo,Android,Ussd,Android 8.0 Oreo,我目前正在使用android api level 26(Nexus 6P)中提供的电话管理器(USSD response)。对于单步ussd会话,它是有效的 参考: 例如: USSD请求:“A”(USSD会话启动) USSD响应:“X”(USSD会话终止) 但对于交互式ussd请求响应(多步骤),它不起作用。 多步骤场景如下所示: 第1步 USSD请求:“A”(USSD会话启动) USSD回应:“X” 第二步 USSD请求:“B”(USSD会议继续) USSD回应:“Y” 第三步 USSD请求
USSD响应:“Z”(USSD会话终止)通过使用反射,我设法绕过了一些问题(如多会话USSD响应不工作)。我做了一个GitHub要点 显然,我们的假设是,给出了正确的权限(此时只有
CALL\u PHONE
)——就上下文而言,我只在活动中运行过此功能,但我认为它在大多数情况下(如果不是全部/任何情况下)都可以正常工作
下一件事是找出如何维持会话,如果可能的话。我可以得到一个菜单,通过发送一个数字来响应它,但如果超过了这个数字,我想是由电信公司决定的-他们发送的菜单会阻塞整个屏幕,如果你取消它,整个会话就会消失。在电信行业工作过,我认为这可能与电信公司不同,因为其中一些公司有网关,可以终止用户发起的会话,并用电信端发起的会话替换它。从技术上讲,这两个会话是分离的。但这是我的代码:
package org.rootio.test.telephony.telephonyautorespond;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import androidx.annotation.RequiresApi;
import static android.content.ContentValues.TAG;
interface UssdResultNotifiable {
void notifyUssdResult(String request, String returnMessage, int resultCode);
}
public class HomeActivity extends Activity implements UssdResultNotifiable {
USSDSessionHandler hdl;
private TelephonyManager telephonyManager;
private PhoneCallListener listener;
private TelecomManager telecomManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
public void onUssdSend(View view) {
//USSDHandler callback = new USSDHandler(view);
/* if (checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
*//*if(ActivityCompat.shouldShowRequestPermissionRationale(HomeActivity.this, Manifest.permission.CALL_PHONE))
{
}
else
{*//*
//ActivityCompat.requestPermissions(HomeActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 0);
// }
Snackbar.make(view, "permissions were missing", Snackbar.LENGTH_LONG)
.setAction("Response", null).show();
return;
}*/
//HomeActivity.this.telephonyManager.sendUssdRequest("*#123*99#", callback, new Handler());
hdl = new USSDSessionHandler(HomeActivity.this, HomeActivity.this);
hdl.doSession(((EditText)this.findViewById(R.id.ussdText)).getText().toString());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_home, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void toggleListener(View v) {
if (((Switch) v).isChecked()) {
this.listenForTelephony();
Toast.makeText(this, "Listening for calls", Toast.LENGTH_LONG).show();
} else {
this.stopListeningForTelephony();
}
}
private void listenForTelephony() {
this.telephonyManager = (TelephonyManager) this.getSystemService(this.TELEPHONY_SERVICE);
this.telecomManager = (TelecomManager) this.getSystemService(this.TELECOM_SERVICE);
this.listener = new PhoneCallListener();
telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
}
private void stopListeningForTelephony() {
this.telephonyManager = null;
this.telecomManager = null;
}
@Override
public void notifyUssdResult(final String request, final String returnMessage, final int resultCode) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(HomeActivity.this, "Request was " + request + "\n response is " + returnMessage + "\n result code is " + resultCode, Toast.LENGTH_LONG).show();
}
});
}
class PhoneCallListener extends PhoneStateListener {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
HomeActivity.this.telecomManager.acceptRingingCall();
break;
case TelephonyManager.CALL_STATE_IDLE:
Toast.makeText(HomeActivity.this, "Call is no longer active...", Toast.LENGTH_LONG);
break;
}
}
}
@TargetApi(Build.VERSION_CODES.O)
class USSDHandler extends TelephonyManager.UssdResponseCallback {
View parent;
USSDHandler(View v) {
this.parent = v;
}
@Override
public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
super.onReceiveUssdResponse(telephonyManager, request, response);
Snackbar.make(this.parent, response, Snackbar.LENGTH_LONG)
.setAction("Response", null).show();
}
@Override
public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
Snackbar.make(this.parent, "error is " + failureCode + " for req " + request, Snackbar.LENGTH_LONG)
.setAction("Response", null).show();
}
}
}
class USSDSessionHandler {
TelephonyManager tm;
private UssdResultNotifiable client;
private Method handleUssdRequest;
private Object iTelephony;
USSDSessionHandler(Context parent, UssdResultNotifiable client) {
this.client = client;
this.tm = (TelephonyManager) parent.getSystemService(Context.TELEPHONY_SERVICE);
try {
this.getUssdRequestMethod();
} catch (Exception ex) {
//log
}
}
private void getUssdRequestMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (tm != null) {
Class telephonyManagerClass = Class.forName(tm.getClass().getName());
if (telephonyManagerClass != null) {
Method getITelephony = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephony.setAccessible(true);
this.iTelephony = getITelephony.invoke(tm); // Get the internal ITelephony object
Method[] methodList = iTelephony.getClass().getMethods();
this.handleUssdRequest = null;
/*
* Somehow, the method wouldn't come up if I simply used:
* iTelephony.getClass().getMethod('handleUssdRequest')
*/
for (Method _m : methodList)
if (_m.getName().equals("handleUssdRequest")) {
handleUssdRequest = _m;
break;
}
}
}
}
public void doSession(String ussdRequest) {
try {
if (handleUssdRequest != null) {
handleUssdRequest.setAccessible(true);
handleUssdRequest.invoke(iTelephony, SubscriptionManager.getDefaultSubscriptionId(), ussdRequest, new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
/*
* Usually you should the getParcelable() response to some Parcel
* child class but that's not possible here, since the "UssdResponse"
* class isn't in the SDK so we need to
* reflect again to get the result of getReturnMessage() and
* finally return that!
*/
Object p = ussdResponse.getParcelable("USSD_RESPONSE");
if (p != null) {
Method[] methodList = p.getClass().getMethods();
for(Method m : methodList)
{
Log.i(TAG, "onReceiveResult: " + m.getName());
}
try {
CharSequence returnMessage = (CharSequence) p.getClass().getMethod("getReturnMessage").invoke(p);
CharSequence request = (CharSequence) p.getClass().getMethod("getUssdRequest").invoke(p);
USSDSessionHandler.this.client.notifyUssdResult("" + request, "" + returnMessage, resultCode); //they could be null
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
});
}
} catch (IllegalAccessException | InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
请忽略电话应答的东西-我以前使用这个应用程序在Oreo上测试自动电话应答。下面是显示器的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".HomeActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="USSD Input"
android:textSize="18sp" />
<EditText
android:id="@+id/ussdText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPersonName" />
</LinearLayout>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onUssdSend"
android:text="send" />
</LinearLayout>
这些API不能正确处理基于菜单的USSD。他们预期的用例将用于查询简单的事情,如用户计划中剩余的分钟数或数据。对于基于菜单的系统,需要一些持续会话的概念,而API不支持这种概念
public class MainActivity extends AppCompatActivity {
private Button dail;
private String number;
private TelephonyManager telephonyManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
dail = (Button) findViewById(R.id.dail);
dail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
runtimepermissions();
return;
}else{
telephonyManager.sendUssdRequest("*121#", new TelephonyManager.UssdResponseCallback() {
@Override
public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
super.onReceiveUssdResponse(telephonyManager, request, response);
Log.d("Received response","okay");
((TextView)findViewById(R.id.response)).setText(response);
}
@Override
public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
Log.e("ERROR ","can't receive response"+failureCode);
}
},new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
Log.e("ERROR","error");
}
});
}
}
});
}
public boolean runtimepermissions() {
if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE}, 100);
return true;
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.d("PERMISSIONS","granted");
// doJob();
} else {
runtimepermissions();
}
}
}
}
如果您使用双sim卡,请确保更改您要测试的网络(在我使用的Airtel(*121#)不幸的是,谷歌在Oreo中添加的API仅适用于USSD服务,在该服务中,您可以在开始时拨打整个USSD代码,并在会话中无需输入任何内容即可获得响应。他们显然没有意识到的是,大多数电信公司出于安全原因,特别是在有PIN输入的情况下,会阻止这种做法f该API实际上似乎是为了处理进一步响应的情况,但正如各种海报所指出的,即使在Android 10上,它实际上也不起作用 我的公司开发了一个Android SDK,它使用可访问性服务来运行多步骤USSD会话,并让它出现在你的应用程序中。你为USSD服务创建配置,触发会话从你的应用程序运行,并传入你需要的任何运行时变量。用户从未看到USSD会话,并且当响应为r时eturned您的应用程序会收到通知,您可以根据需要对其进行解析。它适用于Android 4.3及更高版本
SDK是免费集成和使用的,直到你达到大规模。请参阅我们的开始
(披露:我是Hover的首席技术官)你好。我现在也遇到了同样的问题。你是如何解决你的问题的?除此之外,我只得到了错误代码-1…直到现在我还没有找到任何解决方案。很高兴你回答了我,我忘记了我对这篇文章的评论…实际上我甚至没有正确阅读你的文章…因为我尝试了多步USSD,它失败了。我求助于单步USSD。N对于multistep,可能您需要覆盖
sendusdrequest
中的某些内容。我发现有一种类似于onreceivesult
的方法,它可以捕获多步骤的消息,但它不知道如何继续处理请求……现在就忘记多步骤,或者将其集成到您的UI中……我知道了我发现了另一种解决方案,使用android易访问性服务接收USSD响应并给出input@zoraf你能分享这个替代解决方案的链接吗?有没有比USSDresponseCallback的26个更具向后兼容性的解决方案呢?这很遗憾。有时候运营商不提供直接的方式来访问你的互联网计划或其他,只有一个连续的菜单。API实际上是为了处理这个问题而设计的-这就是为什么它将处理程序作为参数。为什么它实际上不处理它是另一个问题…包含处理程序并不意味着API是为了处理基于菜单的USSD而设计的;它只是为调用者提供了一种方法来确保callback操作被发布到他们选择的处理程序。干得好,我们需要更多像你这样的人!在你的示例中,l
在newresultreceiver(l)中指的是什么{
?@Razgriz它指的是我在上下文中拥有的一些处理程序
类型的对象,通常是主UI线程处理程序。因此,要实例化一个,可以这样做:Handler l=new Handler(Looper.getMainLooper())
如果您已经在主UI线程中执行,那么您可以省略对主循环器的引用,只使用新处理程序();
。关于这一点有什么建议吗?我无法获得handleUssdRequest方法!@Abdu您是否能够提取HandleusDreQuest
?@real\u shardul否“SDK可以免费集成和使用,直到您实现大规模应用。”。我可以在您的定价页面上看到()免费计划中没有提到最大请求数。你能澄清一下吗?嘿@davkutalek有没有办法关闭屏幕上显示动作名称和插入方法extra的数据的自定义内容?@vkamme
public class MainActivity extends AppCompatActivity {
private Button dail;
private String number;
private TelephonyManager telephonyManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
dail = (Button) findViewById(R.id.dail);
dail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
runtimepermissions();
return;
}else{
telephonyManager.sendUssdRequest("*121#", new TelephonyManager.UssdResponseCallback() {
@Override
public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
super.onReceiveUssdResponse(telephonyManager, request, response);
Log.d("Received response","okay");
((TextView)findViewById(R.id.response)).setText(response);
}
@Override
public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
Log.e("ERROR ","can't receive response"+failureCode);
}
},new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
Log.e("ERROR","error");
}
});
}
}
});
}
public boolean runtimepermissions() {
if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE}, 100);
return true;
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.d("PERMISSIONS","granted");
// doJob();
} else {
runtimepermissions();
}
}
}