I'm developing a Unity app which will be able to interface with the Android app Termux via the Intent described here:
https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent#advance-examples
In my Unity-Android plugin I have two classes:
PluginInstance.java:
package com.saricden.exd2termux;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE;
import com.unity3d.player.UnityPlayer;
import java.util.Arrays;
public class PluginInstance {
private static Activity unityActivity;
public static void receiveUnityActivity(Activity tActivity) {
unityActivity = tActivity;
}
private String unityGameObjectName = null;
public void debugLog(String msg) {
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "BIGTEST (" + msg + ")");
}
public void loginToTermux() {
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/login");
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
unityActivity.startForegroundService(intent);
}
public void sendToUnity(String msg) {
UnityPlayer.UnitySendMessage(unityGameObjectName, "ReceiveTermuxOutput", msg);
}
public void execTermuxCMD(String cmd) {
debugLog("Started command...");
String[] cmdParts = cmd.split(" ");
String cmdRoot = cmdParts[0];
String[] cmdArgs = Arrays.copyOfRange(cmdParts, 1, cmdParts.length);
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/" + cmdRoot);
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", cmdArgs);
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
Intent pluginResultsServiceIntent = new Intent(unityActivity, PluginResultsService.class);
int executionId = PluginResultsService.getNextExecutionId();
pluginResultsServiceIntent.putExtra(PluginResultsService.EXTRA_EXECUTION_ID, executionId);
PendingIntent pendingIntent = PendingIntent.getService(unityActivity, executionId, pluginResultsServiceIntent, PendingIntent.FLAG_ONE_SHOT);
intent.putExtra(RUN_COMMAND_SERVICE.EXTRA_PENDING_INTENT, pendingIntent);
unityActivity.startForegroundService(intent);
debugLog("Started foreground service, with run_background 'true'");
}
public void setUnityGameObjectName(String name) {
unityGameObjectName = name;
}
}
PluginResultsService.java:
package com.saricden.exd2termux;
import android.app.IntentService;
import android.content.Intent;
import androidx.annotation.Nullable;
import com.unity3d.player.UnityPlayer;
public class PluginResultsService extends IntentService {
public static final String EXTRA_EXECUTION_ID = "execution_id";
private static int EXECUTION_ID = 1000;
public static final String PLUGIN_SERVICE_LABEL = "PluginResultsService";
private static final String LOG_TAG = "PluginResultsService";
public PluginResultsService(){
super(PLUGIN_SERVICE_LABEL);
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "onHandleIntent!!!");
}
public static synchronized int getNextExecutionId() {
EXECUTION_ID++;
UnityPlayer.UnitySendMessage("DebugParent/DebugListener", "LogMessage", "Get next execution_id (" + EXECUTION_ID + ")");
return EXECUTION_ID;
}
}
Then finally, my Unity MonoBehaviour class that kicks it all off:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Vuplex.WebView;
public class TerminalManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject _pluginInstance;
private WebViewPrefab webViewPrefab;
void InitializePlugin(string pluginName) {
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
_pluginInstance = new AndroidJavaObject(pluginName);
if (_pluginInstance == null) {
Debug.Log("!!!!! >> PLUGIN INSTANCE ERROR << !!!!!");
}
_pluginInstance.CallStatic("receiveUnityActivity", unityActivity);
_pluginInstance.Call("loginToTermux");
}
async void Start() {
webViewPrefab = GetComponent<WebViewPrefab>();
InitializePlugin("com.saricden.exd2termux.PluginInstance");
_pluginInstance.Call("setUnityGameObjectName", transform.root.name + "/" + transform.name);
await webViewPrefab.WaitUntilInitialized();
webViewPrefab.WebView.MessageEmitted += (sender, eArgs) => {
Debug.Log("Dispatching Termux command...");
Debug.Log(eArgs.Value);
_pluginInstance.Call("execTermuxCMD", eArgs.Value);
};
}
public void ReceiveTermuxOutput(string res) {
Debug.Log("Receiving Termux response...");
webViewPrefab.WebView.ExecuteJavaScript("appendResponse(`" + res + "`)");
}
}
I can confirm the Termux commands are running. I have tested by running mkdir testFolder, then opening Termux and checking the home directory. The "testFolder" was successfully created.
The problem: PluginResultsService -> onHandleIntent never gets called!
How can I go about further debugging this? And/or does anyone have any suggestions as to what the problem might be?
I figured out the solution.
I was defining:
<service android:name=".PluginResultsService" android:exported="false" />
Inside my Unity app's top-level AndroidManifest.xml, but really it needed to be defined in the plugin-level AndroidManifest.xml!
Let's goo!
Related
In my app, I am relying on onUserLeaveHint() method when the user presses the home button, but this method is also being called when you are starting multi window mode in android 7.0 by long pressing "recents button" (which I don't want to perform same thing that I do when home button been pressed). So I want to know if there is a way to detect which is which. Cheers!
Note: onMultiWindowModeChanged() being called after onUserLeaveHint()
I think this is what you're looking for.
HomeWatcher mHomeWatcher = new HomeWatcher(this);
mHomeWatcher.setOnHomePressedListener(new OnHomePressedListener() {
#Override
public void onHomePressed() {
// do something here...
}
#Override
public void onHomeLongPressed() {
}
});
mHomeWatcher.startWatch();
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
public class HomeWatcher {
static final String TAG = "hg";
private Context mContext;
private IntentFilter mFilter;
private OnHomePressedListener mListener;
private InnerRecevier mRecevier;
public HomeWatcher(Context context) {
mContext = context;
mFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
}
public void setOnHomePressedListener(OnHomePressedListener listener) {
mListener = listener;
mRecevier = new InnerRecevier();
}
public void startWatch() {
if (mRecevier != null) {
mContext.registerReceiver(mRecevier, mFilter);
}
}
public void stopWatch() {
if (mRecevier != null) {
mContext.unregisterReceiver(mRecevier);
}
}
class InnerRecevier extends BroadcastReceiver {
final String SYSTEM_DIALOG_REASON_KEY = "reason";
final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
final String SYSTEM_DIALOG_REASON_LONG_PRESS = "assist";
final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (reason != null) {
Log.e(TAG, "action:" + action + ",reason:" + reason);
if (mListener != null) {
if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
mListener.onHomePressed();
} else if (reason.equals(SYSTEM_DIALOG_REASON_LONG_PRESS)) {
mListener.onHomeLongPressed();
}
}
}
}
}
}
}
public interface OnHomePressedListener {
public void onHomePressed();
public void onHomeLongPressed();
}
Yesterday i ask a simplified question of my problem, but think its too simplified.
What my programm should do, is to hear a keyword and when he hear it, he should listen to what i said. (like if you told to siri or google now, by saying siri or ok google).
I'm using pocketsphinx for the keyword and the google speechrecognizer for the longer parts. It works, but only for one time. The pocketsphinx is in the MainActivity and the google recognizer is in an extra class (Jarvis).
The programm starts with the pocketsphinx listener, when he hear the KEYPHRASE, he starts the google listener by calling jarvis.startListener() (by the next()-method) and there is the problem, when the googlelistener is done, i dont come back from the Jarvis-class to the MainActivity to call the next() method again.
(when the google recognizer is done, the last things he do is in the onResult() in Jarvis-class, but from there i cant call the next()-method from MainActivity-class)
MainActivity
package com.example.superuser.jarvis;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import edu.cmu.pocketsphinx.Assets;
import edu.cmu.pocketsphinx.Hypothesis;
import edu.cmu.pocketsphinx.SpeechRecognizer;
import edu.cmu.pocketsphinx.SpeechRecognizerSetup;
import static android.widget.Toast.makeText;
import static edu.cmu.pocketsphinx.SpeechRecognizerSetup.defaultSetup;
public class MainActivity extends Activity implements edu.cmu.pocketsphinx.RecognitionListener {
private String LOG_TAG = "Jarvis_hears_anything";
private TextView tv;
private Jarvis jarvis;
private boolean wannahearjarvis = false;
/* Named searches allow to quickly reconfigure the decoder */
private static final String KWS_SEARCH = "wakeup";
/* Keyword we are looking for to activate menu */
private static final String KEYPHRASE = "jarvis";
private edu.cmu.pocketsphinx.SpeechRecognizer recognizer;
//private HashMap<String, Integer> captions;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = (Button) findViewById(R.id.b1);
tv = (TextView) findViewById(R.id.tv1);
//captions = new HashMap<String, Integer>();
//captions.put(KWS_SEARCH, R.string.kws_caption);
jarvis = new Jarvis(getApplicationContext());
new AsyncTask<Void, Void, Exception>() {
#Override
protected Exception doInBackground(Void... params) {
try {
Assets assets = new Assets(MainActivity.this);
File assetDir = assets.syncAssets();
setupRecognizer(assetDir);
} catch (IOException e) {
return e;
}
return null;
}
#Override
protected void onPostExecute(Exception result) {
if (result != null) {
((TextView) findViewById(R.id.tv1))
.setText("Failed to init recognizer " + result);
} else {
//switchSearch(KWS_SEARCH);
recognizer.startListening(KWS_SEARCH);
}
}
}.execute();
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "geht", Toast.LENGTH_SHORT).show();
}
});
}
public void next(){
if (wannahearjarvis){
recognizer.startListening(KWS_SEARCH);
wannahearjarvis = false;
}
else{
jarvis.startListening();
wannahearjarvis = true;
}
}
#Override
public void onDestroy() {
super.onDestroy();
recognizer.cancel();
recognizer.shutdown();
}
/**
* In partial result we get quick updates about current hypothesis. In
* keyword spotting mode we can react here, in other modes we need to wait
* for final result in onResult.
*/
#Override
public void onPartialResult(Hypothesis hypothesis) {
if (hypothesis == null)
return;
String text = hypothesis.getHypstr();
if (text.equals(KEYPHRASE)){
tv.append("found");
recognizer.stop();
//switchSearch(KWS_SEARCH);
}
else {
//((TextView) findViewById(R.id.tv1)).append(text+"PR");
//Log.i(LOG_TAG, text+"PR");
}
}
/**
* This callback is called when we stop the recognizer.
*/
#Override
public void onResult(Hypothesis hypothesis) {
//((TextView) findViewById(R.id.tv1)).setText("");
((TextView) findViewById(R.id.tv1)).append("oR");
if (hypothesis != null) {
String text = hypothesis.getHypstr();
makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
}
next();
}
#Override
public void onBeginningOfSpeech() {
}
/**
* We stop recognizer here to get a final result
*/
#Override
public void onEndOfSpeech() {
if (!recognizer.getSearchName().equals(KWS_SEARCH)){
tv.append("fuck");
}
//switchSearch(KWS_SEARCH);
}
/*private void switchSearch(String searchName) {
recognizer.stop();
// If we are not spotting, start listening with timeout (10000 ms or 10 seconds).
if (searchName.equals(KWS_SEARCH))
recognizer.startListening(searchName);
else
recognizer.startListening(searchName, 10000);
//String caption = getResources().getString(captions.get(searchName));
//((TextView) findViewById(R.id.tv1)).setText(caption);
//((TextView) findViewById(R.id.tv1)).append(caption);
}*/
private void setupRecognizer(File assetsDir) throws IOException {
// The recognizer can be configured to perform multiple searches
// of different kind and switch between them
recognizer = defaultSetup()
.setAcousticModel(new File(assetsDir, "en-us-ptm"))
.setDictionary(new File(assetsDir, "cmudict-en-us.dict"))
// To disable logging of raw audio comment out this call (takes a lot of space on the device)
.setRawLogDir(assetsDir)
// Threshold to tune for keyphrase to balance between false alarms and misses
.setKeywordThreshold(1e-20f)
// Use context-independent phonetic search, context-dependent is too slow for mobile
.setBoolean("-allphone_ci", true)
.getRecognizer();
recognizer.addListener(this);
/** In your application you might not need to add all those searches.
* They are added here for demonstration. You can leave just one.
*/
// Create keyword-activation search.
recognizer.addKeyphraseSearch(KWS_SEARCH, KEYPHRASE);
}
#Override
public void onError(Exception error) {
((TextView) findViewById(R.id.tv1)).setText(error.getMessage());
}
#Override
public void onTimeout() {
//switchSearch(KWS_SEARCH);
}
}
Jarvis
package com.example.superuser.jarvis;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.widget.Toast;
import java.util.ArrayList;
public class Jarvis implements RecognitionListener{
private AudioManager audiom;
private SpeechRecognizer speech;
private Intent recogIntent;
private Toast m;
private Context c;
private String text;
public Jarvis(Context context){
speech = SpeechRecognizer.createSpeechRecognizer(context);
speech.setRecognitionListener(this);
recogIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
recogIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "de");
//recogIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, context.getPackageName());
m = new Toast(context);
c=context;
}
public void startListening(){
speech.startListening(recogIntent);
}
public void destroy(){
speech.stopListening();
speech.cancel();
speech.destroy();
}
#Override
public void onReadyForSpeech(Bundle params) {
}
#Override
public void onBeginningOfSpeech() {
}
#Override
public void onRmsChanged(float rmsdB) {
}
#Override
public void onBufferReceived(byte[] buffer) {
}
#Override
public void onEndOfSpeech() {
}
#Override
public void onError(int error) {
}
#Override
public void onResults(Bundle results) {
ArrayList<String> matches = results
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
Toast.makeText(c, matches.get(0), Toast.LENGTH_LONG).show();
speech.cancel();
//tried
//MainActivity m = new MainActivity();
//m.next();
//but got a Nullpointer Exception
}
#Override
public void onPartialResults(Bundle partialResults) {
}
#Override
public void onEvent(int eventType, Bundle params) {
}
}
You can store reference to the main activity in Jarvis object in a field:
class Jarvis {
....
private MainActivity m;
....
public Jarvis(MainActivity m) {
this.m = m;
}
....
public void onResults(Bundle results) {
....
m.next();
}
You can also send intents to the main activity as described here. This might be overkill in your case though.
Edit: User QuickFix's answer worked for me. Code right at the bottom of this question.
I am trying to write a Cordova 3 Android plugin that makes normal and custom Toast. However, I am just a front-end developer and very new to Cordova and Android. I am still learning and would appreciate any help you can give.
So far I have managed to do these 2 tasks individually and successfully:
write a function in the main activity that makes normal and custom Toast (the custom Toast is simply a RelativeLayout in /res/layout that shows an icon and some text).
write a Cordova plugin by following Devgirl's tutorial: How to Write a PhoneGap 3.0 Plugin for Android.
My problem now is - how do I get the plugin to call the showCustomToast() function in the main activity? As you can see in Code block #2 below, I ran into the problem of how to even get the main activity so I could call showCustomToast(). Here is the extract of how I am currently doing this:
// Problem?
HelloCordova main = (HelloCordova) cordova.getActivity();
main.showCustomToast(toastTitle, toastText, duration);
I have to cast cordova.getActivity() to HelloCordova, otherwise it won't recognise that it has the showCustomToast() function. But surely this is not the correct approach, although it does "work", ie, I am able to get custom Toast to show in the app. I just can't help but feel that I've gone about this completely the wrong way. It's not exactly a reusable plugin at the moment!
I would be very grateful if someone could set me on the right path of how to achieve this. For example, should I give up on the plugin completely and just do this instead?
This is my first Stackoverflow question so please let me know if I should change or clarify anything. Thank you for reading!!
Here's my existing code:
Code block #1
This HelloCordova class was automatically generated when starting a new Cordova project. I added the showCustomToast() function.
package io.cordova.hellocordova;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.cordova.*;
public class HelloCordova extends CordovaActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.init();
// Set by <content src="index.html" /> in config.xml
super.loadUrl(Config.getStartUrl());
//super.loadUrl("file:///android_asset/www/index.html")
}
public void showCustomToast(String toastTitleText, String toastDescText, int toastDuration) {
Toast toast = new Toast(this);
toast.setDuration(toastDuration);
LayoutInflater inflater = getLayoutInflater();
View appearance = inflater.inflate(R.layout.toast_layout, (ViewGroup) findViewById(R.id.toastRoot));
toast.setView(appearance);
TextView toastTitle = (TextView) appearance.findViewById(R.id.toastTitle);
toastTitle.setText(toastTitleText);
TextView toastDesc = (TextView) appearance.findViewById(R.id.toastDescription);
toastDesc.setText(toastDescText);
toast.show();
}
}
Code block #2
The Java part of the Cordova plugin.
package com.example.plugins.toast;
//Problem?
import io.cordova.hellocordova.HelloCordova;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
public class ToastPlugin extends CordovaPlugin {
final String LOG_TAG = "ToastLog";
public static final String ACTION_NORMAL_TOAST = "normalToast";
public static final String ACTION_CUSTOM_TOAST = "customToast";
#Override
public boolean execute(String action, JSONArray args,
CallbackContext callbackContext) throws JSONException {
final JSONObject arg_object = args.getJSONObject(0);
final String toastTitle = arg_object.getString("toastTitle");
final String toastText = arg_object.getString("toastText");
final String toastDuration = arg_object.getString("toastDuration");
final CallbackContext ctx = callbackContext;
try {
if (ACTION_NORMAL_TOAST.equals(action)) {
Log.d(LOG_TAG, "Normal toast: " + toastText);
Runnable runnable = new Runnable() {
public void run() {
Context context = cordova.getActivity()
.getApplicationContext();
int duration = Toast.LENGTH_SHORT;
if (toastDuration.equals("LONG")) {
duration = Toast.LENGTH_LONG;
}
Toast.makeText(context, toastText, duration).show();
}
};
this.cordova.getActivity().runOnUiThread(runnable);
callbackContext.success();
return true;
} else if (ACTION_CUSTOM_TOAST.equals(action)) {
Log.d(LOG_TAG, "Custom toast: " + toastTitle + ": " + toastText);
Runnable runnable = new Runnable() {
public void run() {
int duration = Toast.LENGTH_SHORT;
if (toastDuration.equals("LONG")) {
duration = Toast.LENGTH_LONG;
}
//Problem?
HelloCordova main = (HelloCordova) cordova
.getActivity();
main.showCustomToast(toastTitle, toastText, duration);
ctx.success();
}
};
this.cordova.getActivity().runOnUiThread(runnable);
callbackContext.success();
return true;
}
callbackContext.error("Invalid action");
return false;
} catch (Exception e) {
System.err.println("Exception: " + e.getMessage());
callbackContext.error(e.getMessage());
return false;
}
}
}
Edit: Here is the solution that worked for me. As QuickFix mentioned in their answer below, the custom toast code is now in the plugin.
package com.example.plugins.toast;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
public class ToastPlugin extends CordovaPlugin {
final String LOG_TAG = "ToastLog";
public static final String ACTION_NORMAL_TOAST = "normalToast";
public static final String ACTION_CUSTOM_TOAST = "customToast";
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
final JSONObject arg_object = args.getJSONObject(0);
final String toastTitle = arg_object.getString("toastTitle");
final String toastText = arg_object.getString("toastText");
final String toastDuration = arg_object.getString("toastDuration");
try {
if (ACTION_NORMAL_TOAST.equals(action)) {
Log.i(LOG_TAG, "[Normal toast] toastText: " + toastText);
Runnable runnable = new Runnable() {
public void run() {
Context context = cordova.getActivity().getApplicationContext();
int duration = Toast.LENGTH_SHORT;
if (toastDuration.equals("LONG")) {
duration = Toast.LENGTH_LONG;
}
Toast.makeText(context, toastText, duration).show();
}
};
this.cordova.getActivity().runOnUiThread(runnable);
callbackContext.success();
return true;
} else if (ACTION_CUSTOM_TOAST.equals(action)) {
Log.i(LOG_TAG, "[Custom toast] toastTitle: " + toastTitle + "\n toastText: " + toastText);
Runnable runnable = new Runnable() {
public void run() {
int duration = Toast.LENGTH_SHORT;
if (toastDuration.equals("LONG")) {
duration = Toast.LENGTH_LONG;
}
Context context = cordova.getActivity().getApplicationContext();
Toast toast = new Toast(context);
toast.setDuration(duration);
LayoutInflater inflater = LayoutInflater.from(context);
Resources resources = context.getResources();
String packageName = context.getPackageName();
View appearance = inflater.inflate(resources.getIdentifier("toast_layout","layout",packageName),null);
toast.setView(appearance);
TextView toastTitleView = (TextView) appearance.findViewById(resources.getIdentifier("toastTitle","id",packageName));
toastTitleView.setText(toastTitle);
TextView toastDesc = (TextView) appearance.findViewById(resources.getIdentifier("toastDescription","id",packageName));
toastDesc.setText(toastText);
toast.show();
}
};
this.cordova.getActivity().runOnUiThread(runnable);
callbackContext.success();
return true;
}
callbackContext.error("Invalid action");
return false;
} catch (Exception e) {
System.err.println("Exception: " + e.getMessage());
callbackContext.error(e.getMessage());
return false;
}
}
}
Maybe you could put showCustomToast inside the plugin instead of inside the app?
In that case you will have to replace in the function the R.layout.layoutname and R.id.viewname with
getApplication().getResources().getIdentifier("layoutname","layout",getApplication().getPackageName());
and
getApplication().getResources().getIdentifier("viewname","id",getApplication().getPackageName());
I'm trying to find the way to make USSD requests in Android. I found this - http://commandus.com/blog/?p=58 .
I added all needed files to my project.
USSDDumbExtendedNetworkService.java:
package com.android.ussdcodes;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.telephony.IExtendedNetworkService;
import com.android.ussdcodes.R;
/**
* Service implements IExtendedNetworkService interface.
* USSDDumbExtendedNetworkService
* Service must have name "com.android.ussd.IExtendedNetworkService" of the intent declared
* in the Android manifest file so com.android.phone.PhoneUtils class bind
* to this service after system rebooted.
* Please note service is loaded after system reboot!
* Your application must check is system rebooted.
* #see Util#syslogHasLine(String, String, String, boolean)
*/
public class USSDDumbExtendedNetworkService extends Service {
public static final String TAG = "CommandusUSSDExtNetSvc";
public static final String LOG_STAMP = "*USSDTestExtendedNetworkService bind successfully*";
public static final String URI_SCHEME = "ussdcodes";
public static final String URI_AUTHORITY = "android.com";
public static final String URI_PATH = "/";
public static final String URI_PAR = "return";
public static final String URI_PARON = "on";
public static final String URI_PAROFF = "off";
public static final String MAGIC_ON = ":ON;)";
public static final String MAGIC_OFF = ":OFF;(";
public static final String MAGIC_RETVAL = ":RETVAL;(";
private static boolean mActive = false;
private static CharSequence mRetVal = null;
private Context mContext = null;
private String msgUssdRunning = "USSD running...";
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_INSERT.equals(intent.getAction())) {
mContext = context;
if (mContext != null) {
msgUssdRunning = mContext.getString(R.string.USSD_run);
mActive = true;
Log.d(TAG, "activate");
}
} else if (Intent.ACTION_DELETE.equals(intent.getAction())) {
mContext = null;
mActive = false;
Log.d(TAG, "deactivate");
}
}
};
private final IExtendedNetworkService.Stub mBinder = new IExtendedNetworkService.Stub() {
#Override
public void setMmiString(String number) throws RemoteException {
Log.d(TAG, "setMmiString: " + number);
}
#Override
public CharSequence getMmiRunningText() throws RemoteException {
Log.d(TAG, "getMmiRunningText: " + msgUssdRunning);
return msgUssdRunning;
}
#Override
public CharSequence getUserMessage(CharSequence text)
throws RemoteException {
if (MAGIC_ON.contentEquals(text)) {
mActive = true;
Log.d(TAG, "control: ON");
return text;
} else {
if (MAGIC_OFF.contentEquals(text)) {
mActive = false;
Log.d(TAG, "control: OFF");
return text;
} else {
if (MAGIC_RETVAL.contentEquals(text)) {
mActive = false;
Log.d(TAG, "control: return");
return mRetVal;
}
}
}
if (!mActive) {
Log.d(TAG, "getUserMessage deactivated: " + text);
return text;
}
String s = text.toString();
// store s to the !
Uri uri = new Uri.Builder()
.scheme(URI_SCHEME)
.authority(URI_AUTHORITY)
.path(URI_PATH)
.appendQueryParameter(URI_PAR, text.toString())
.build();
sendBroadcast(new Intent(Intent.ACTION_GET_CONTENT, uri));
mActive = false;
mRetVal = text;
Log.d(TAG, "getUserMessage: " + text + "=" + s);
return null;
}
#Override
public void clearMmiString() throws RemoteException {
Log.d(TAG, "clearMmiString");
}
};
/**
* Put stamp to the system log when PhoneUtils bind to the service
* after Android has rebooted. Application must call {#link Util#syslogHasLine(String, String, String, boolean)} to
* check is phone rebooted or no. Without reboot phone application does not bind tom this service!
*/
#Override
public IBinder onBind(Intent intent) {
// Do not localize!
Log.i(TAG, LOG_STAMP);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_INSERT);
filter.addAction(Intent.ACTION_DELETE);
filter.addDataScheme(URI_SCHEME);
filter.addDataAuthority(URI_AUTHORITY, null);
filter.addDataPath(URI_PATH, PatternMatcher.PATTERN_LITERAL);
registerReceiver(mReceiver, filter);
return mBinder;
}
public IBinder asBinder() {
Log.d(TAG, "asBinder");
return mBinder;
}
}
Manifest:
<receiver android:name="com.android.ussdcodes.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".USSDDumbExtendedNetworkService" >
<intent-filter android:icon="#drawable/ic_launcher">
<action android:name="com.android.ussd.IExtendedNetworkService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
BootReceiver.java:
package com.android.ussdcodes;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.d("USSDService", context.getString(R.string.service_started));
context.startService(new Intent(context,USSDDumbExtendedNetworkService.class));
}
}
So now the service starts after boot complete.
And my question is how I have to send USSD request and get responce with service?
Thanks!)
Well, I have found the answer.
I just put the link on my gist.
Deactivate messages
USSDDumbExtendedNetworkService.mActive = false;
Send USSD:
Intent launchCall = new Intent(Intent.ACTION_CALL,
Uri.parse("tel:" + Uri.encode(ussdcode)));
launchCall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
launchCall.addFlags(Intent.FLAG_FROM_BACKGROUND);
startActivity(launchCall);
Activate messages again
USSDDumbExtendedNetworkService.mActive = true;
USSDDumbExtendedNetworkService.mRetVal = null;
I am trying to access CallManager class object from com.android.internal.telephony package.
Here is my code:
ClassLoader classLoader = TestActivity.class.getClassLoader();
final ClassLoader classLoader = this.getClass().getClassLoader();
try {
final Class<?> classCallManager =
classLoader.loadClass("com.android.internal.telephony.CallManager");
Log.i("TestActivity", classCallManager);
} catch (final ClassNotFoundException e) {
Log.e("TestActivity", e);
}
Unfortunately, this is throwing a ClassNotFoundException. The same approach allows me to access PhoneFactory, but apparently I'm not allowed to access CallManager.
If I could reach the class, then I'd want to proceed using the class as follows:
Method method_getInstance;
method_getInstance = classCallManager.getDeclaredMethod("getInstance");
method_getInstance.setAccessible(true);
Object callManagerInstance = method_getInstance.invoke(null);
Can anyone help me on this?
Thanks in advance,
Harsha C
I could successfully load CallManager and its methods. However, when I invoke getState(), getActiveFgCallState(), it always return IDLE even when the app receives different call states from TelephonyManager, i.e. TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_OFFHOOK, TelephonyManager.CALL_STATE_RINGING.
I used the following code to load the class and its methods:
final Class<?> classCallManager = classLoader.loadClass("com.android.internal.telephony.CallManager");
Log.i(TAG, "Class loaded " + classCallManager.toString());
Method methodGetInstance = classCallManager.getDeclaredMethod("getInstance");
Log.i(TAG, "Method loaded " + methodGetInstance.getName());
Object objectCallManager = methodGetInstance.invoke(null);
Log.i(TAG, "Object loaded " + objectCallManager.getClass().getName());
Method methodGetState = classCallManager.getDeclaredMethod("getState");
Log.i(TAG, "Method loaded " + methodGetState.getName());
Log.i(TAG, "Phone state = " + methodGetState.invoke(objectCallManager));
Btw, what I am trying to do is detecting when the phone starts ringing. I saw in the source code that ALERTING is the internal event that I should listen to. Therefore, I tried to use CallManager to get Call.State, rather than Phone.State. I also tried to use registerForPreciseCallStateChanges() of CallManager class but none of approached worked so far.
I had to solve the exact same problem - getting the disconnection cause during call hangup. My solution was to grep the radio logs for the relevant lines. Seems to work - hope it helps.
private void startRadioLogListener() {
new Thread(new Runnable() {
public void run() {
Process process = null;
try {
// Clear the log first
String myCommandClear = "logcat -b radio -c";
Runtime.getRuntime().exec(myCommandClear);
String myCommand = "logcat -b radio";
process = Runtime.getRuntime().exec(myCommand);
Log.e(LogHelper.CAL_MON, "RadioLogProc: " + process);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while (true) {
final String line = bufferedReader.readLine();
if (line != null) {
if (line.contains("disconnectCauseFromCode") || line.contains("LAST_CALL_FAIL_CAUSE")) {
Log.d(LogHelper.CAL_MON, "RadioLog: " + line);
radioLogHandler.post(new Runnable() {
public void run() {
consolePrint("Radio: " + line + "\n");
}
});
}
}
}
} catch (IOException e) {
Log.e(LogHelper.CAL_MON, "Can't get radio log", e);
} finally {
if (process != null) {
process.destroy();
}
}
}
}).start();
}
Have you tried this method found here on this blog?
By loading the Phone.apk might be the clue to getting around the ClassNotFound exception error...
I have written a program that recognize the phone calls and list all recent calls in a list. Maybe you can have a look on this code and it can help I think.
All you need is this:
String state = bundle.getString(TelephonyManager.EXTRA_STATE);
if(state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_RINGING))
but check this example also:
package org.java.sm222bt;
import org.java.sm222bt.R;
import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
public class MainActivity extends ListActivity {
private CountryDbAdapter db;
private Cursor entrycursor;
private ListAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
registerForContextMenu(getListView());
db = new CountryDbAdapter(this);
updateEntry();
}
public void updateEntry(){
db.open();
entrycursor = db.fetchAllEntries();
adapter = new SimpleCursorAdapter(this, R.layout.row, entrycursor, new String[] {"phonenumber"}, new int[] {R.id.number});
setListAdapter(adapter);
db.close();
}
#Override
protected void onResume() {
super.onResume();
updateEntry();
}
public static final int Call = 0;
public static final int SendSMS = 1;
public static final int Share = 2;
public static final int Delete = 3;
public static final int ClearList = 4;
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
menu.setHeaderTitle("Select:");
menu.add(0, Call, 0, "Call");
menu.add(0, SendSMS, 0, "Send SMS");
menu.add(0, Share, 0, "Share");
menu.add(0, Delete, 0, "Delete");
menu.add(0, ClearList, 0, "Clear all contacts");
}
#Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Call:
//System.out.println(entrycursor.getString(1));
//EditText number=(EditText)findViewById(R.id.number);
String toDial="tel:"+entrycursor.getString(1);
//start activity for ACTION_DIAL or ACTION_CALL intent
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(toDial)));
//update(entryCursor.getLong(0));
return true;
case SendSMS:
//EditText number=(EditText)findViewById(R.id.number);
//EditText msg=(EditText)findViewById(R.id.msg);
String sendUri="smsto:"+entrycursor.getString(1);
Intent sms=new Intent(Intent.ACTION_SENDTO,
Uri.parse(sendUri));
//sms.putExtra("sms_body", msg.getText().toString());
startActivity(sms);
return true;
case Share:
Intent i=new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_SUBJECT, "Hello");
i.putExtra(Intent.EXTRA_TEXT, "Sharing this number"+entrycursor.getString(1));
startActivity(Intent.createChooser(i,
entrycursor.getString(1)));
return true;
case Delete:
db.open();
db.deleteEntry(entrycursor.getLong(0));
db.close();
updateEntry();
return true;
case ClearList:
db.open();
db.deleteEntry();
db.close();
updateEntry();
return true;
default:
return super.onContextItemSelected(item);
}
}
}
and here is the incomming call receiver:
package org.java.sm222bt;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
public class IncomingCallReceiver extends BroadcastReceiver {
CountryDbAdapter db;
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Bundle bundle = intent.getExtras();
if(null == bundle)
return;
String state = bundle.getString(TelephonyManager.EXTRA_STATE);
if(state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_RINGING))
{
String phonenumber = bundle.getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
System.out.println(phonenumber);
db = new CountryDbAdapter(context);
db.open();
db.insertEntry(phonenumber);
db.fetchAllEntries();
db.close();
}
}}
I understand that you want to detect when the call is disconnected. You can instead use the PhoneStateListener.LISTEN_CALL_STATE. For example:
final TelephonyManager telephony = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
telephony.listen(new PhoneStateListener() {
public void onCallStateChanged(final int state,
final String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.d("TestActivity", "Call disconnected");
break;
case TelephonyManager.CALL_STATE_RINGING:
break;
default:
break;
}
}
}, PhoneStateListener.LISTEN_CALL_STATE);
Use this :
telephone = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
private PhoneStateListener psl = new PhoneStateListener() {
#Override
public void onCallStateChanged (int state, String incomingNumber)
{
state = telephone.getCallState();
switch(state) {
case android.telephony.TelephonyManager.CALL_STATE_IDLE:
if (callsucces ) act();
break;
case android.telephony.TelephonyManager.CALL_STATE_RINGING:
callsucces = true;
break;
case android.telephony.TelephonyManager.CALL_STATE_OFFHOOK:
callsucces = true;
break;
}
}
};
private void call(String pnum) {
try {
callIntent = new Intent(Intent.ACTION_CALL);
callsucces = false;
if (telephone.getNetworkType() != 0) {
if (telephone.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
callIntent.setData(Uri.parse("tel:"+pnum));
startActivity(callIntent);
telephone.listen(psl,PhoneStateListener.LISTEN_CALL_STATE);
}
} else act();
//callIntent.ACTION_NEW_OUTGOING_CALL
} catch (ActivityNotFoundException activityException) {
Toast.makeText(getBaseContext(), "Call failed",Toast.LENGTH_SHORT).show();
act();
}
}
Did you add READ_PHONE_STATE in AndroidManifest.xml ?
I think you miss that one.