Detecting if headphones are connected without using dangerous permissions - java

I've been trying to look for a good solution to detect when headphones are connected in Android regardless of whether they are wired or wireless.
The case for wired headphones is fairly straight-forward. However, when detecting if a wireless headset is connected, the APIs I have been using require Manifest.permission.BLUETOOTH or Manifest.permission.BLUETOOTH_CONNECT.
For some time I've accepted that only if those permissions are granted will I know for sure if headphones are connected, but this is now increasingly becoming problematic.
Does anyone know of an alternative that would determine if a headset is connected without user-facing 'dangerous' permission prompts like the BluetoothHeadset solution does? Is there anything in the AudioManager that can be used instead?
Here is what I have today:
package ai.numbereight.sdk.platform.sensors
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothHeadset
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
private val listener = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val connection = when (intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_DISCONNECTED)) {
BluetoothHeadset.STATE_CONNECTED -> "Connected"
BluetoothHeadset.STATE_DISCONNECTED -> "Disconnected"
else -> "Unknown"
}
Log.d("Connection", connection)
}
}
fun register(context: Context) {
// Register for changes - events will only be received
// if Manifest.permission.BLUETOOTH (< Android 10)
// or Manifest.permission.BLUETOOTH_CONNECT (>= Android 10)
// are granted.
val filter = IntentFilter()
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
context.registerReceiver(listener, filter)
// Fire initial event - this API call requires the above Bluetooth permissions
// For brevity I assume that the default adapter is not null
val currentState = BluetoothAdapter.getDefaultAdapter()!!.getProfileConnectionState(BluetoothProfile.HEADSET)
val initialIntent = Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
initialIntent.putExtra(BluetoothHeadset.EXTRA_STATE, currentState)
listener.onReceive(context, initialIntent)
}

Related

USB Serial Driver Java Plugin not working in Unity Android build

I'm trying to make an Android app with the Unity editor in order to read from an USB device. I'm using Unity 2019.4.12f1 and the android device I'm building to is a Xiaomi Mi Box S, running Android 9.
What I've done so far is to use Android Studio to compile some java code in order to gain access to the USB port on Xiaomi.
I'm using this repo: https://github.com/mik3y/usb-serial-for-android as the base functionality of the library I'm trying to build.
This is the code I wrote in my Java library:
public class Plugin extends Activity {
private static UsbSerialPort port;
private static final int READ_WAIT_MILLIS = 2000;
public static String Initialize(Context unityContext) throws IOException {
UsbManager manager = (UsbManager) unityContext.getSystemService(Context.USB_SERVICE);
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x067b, 0x2303, CdcAcmSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> availableDrivers = prober.findAllDrivers(manager);
if (availableDrivers.isEmpty()) {
return "Drivers list is empty";
}
// Open a connection to the first available driver.
UsbSerialDriver driver = availableDrivers.get(0);
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) {
return "Connection is null";
}
port = driver.getPorts().get(0); // Most devices have just one port (port 0)
port.open(connection);
port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
return "Connection Successful";
}
public static String ReadPort() throws IOException {
if(port == null || !port.isOpen())
return "Null";
byte[] buffer = new byte[8192];
int len = port.read(buffer, READ_WAIT_MILLIS);
return buffer.toString();
}
public static void CloseSerialPort() throws IOException {
if(port != null && port.isOpen())
port.close();
}
And this is the C# code i'm using on the Unity side:
void Start()
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject context = activity.Call<AndroidJavaObject>("getApplicationContext");
AndroidJavaClass pluginClass = new AndroidJavaClass("com.hoho.android.usbserial.Plugin");
string response = pluginClass.CallStatic<string>("Initialize", context);
Debug.Log(response);
if (response.Contains("Successful"))
{
string read = pluginClass.CallStatic<string>("ReadPort");
Debug.Log(read);
}
else
{
Debug.Log("Init failed");
}
}
You can see that I've created a custom "Prober" for my device with the it's Vendor and Product ID's, so in theory the Java side should know what to look for. Unfortunately the collection "availableDrivers" is empty after the code executes.
On the C# side, I'm sending my unity activity for the Java to get it's context from. I see that the USB manager needs that context in order to be instanced. I did check and the variable "unityContext" is not null after is passed into the Java library from C#. Right now I'm investigating if maybe the context I'm sending to Java is not the right one, causing the USB manager not being able to access the USB devices.
The return String that I get from the Java code to Unity is: "Drivers list is empty", hence the conclusion that the USB drivers list is empty.
Any help or any kind of leading in the right direction is going to be helpful.
PS. Things I've checked:
I know that the USB device is able to communicate with the Android device because I've used an app to check and everything seemed fine. (https://play.google.com/store/apps/details?id=de.kai_morich.serial_usb_terminal&hl=en)
I'm answering my own question here. Maybe someone else will stumble upon this.
There were a couple of issues that I was not aware of:
The Xiaomi USB port will not continue to communicate with the sensor if it is set to USB Debugging Mode;
Again, even if the Xiaomi USB Debugging mode was turned off, it will not work. To make it work I had to restart the device.
The conclusion being that if you are working with Xiaomi Mi Box S make sure the hardware connection is working before you start changing anything on your code.
To make all of this work:
Make sure you understand how Java <-> C# communication works;
You will need to be a little bit familiar with Android Studio library builds;
Follow the instructions on the repo I posted.
You should be good to go at this point. Let me know if you need any further help.

Using fallback sync URL in SymmetricDS with Java Extension

I have a situation where I have a mobile system that when it's on the road in will connect to symmetricDS via the default sync URL specified in the engines file. But when that device is back at home base, it ends up on the same internal network as the master symmetricDS server. So it cannot resolve the outside hostname from inside the network.
Anyway.... I want to setup an symmetricDS extension so that it can try the default URL and then fallback to a secondary IP of 192.168.0.5 if it doesn't work. This is the code snippet that I think I need to start with. I have never done any java, so I'm a little lost reading this.
import java.net.URI;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.transport.ISyncUrlExtension;
import org.jumpmind.symmetric.ext.ISymmetricEngineAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SyncUrlRewrite implements ISymmetricEngineAware, ISyncUrlExtension {
protected Logger log = LoggerFactory.getLogger(getClass());
ISymmetricEngine engine;
#Override
public String resolveUrl(URI url) {
return url.toString();
}
#Override
public void setSymmetricEngine(ISymmetricEngine engine) {
this.engine = engine;
}
}
You might try looking at and implementing the ISyncUrlExtension interface with the resolveUrl() method. To get it to work, configure the sync_url for a node with the protocol of ext://beanName. beanName is the name you give your extension point in the extension xml file.
Then, you can implement logic to have the resolveUrl() method parse, for example, a list of URLs for your mobile client to try in the sync_url string.
public String resolveUrl(URI uri) {
if (uri.toString().startsWith("ext")) {
Map<String, String> params = getParameters(uri);
//Do some logic with your parsed parameters and return a url
} else {
else return uri.toString();
}
}
The HttpBandwidthSelector class can give you an example implementation.

getSerial() method on Marshmallow

I'm new with Java and android and i need to basically retrieve hardware serial number from my device.
I've tried the following:
import android.content.*;
import android.os.Build;
public static String recup_android()
{
String androidid;
String SerialNumber;
androidid=android.os.Build.MODEL;
SerialNumber=android.os.Build.getserial;
return SerialNumber;
}
I'm facing the following error:
java:40: error: cannot find symbol
SerialNumber=android.os.Build.getserial;
^
symbol: variable getserial
location: class Build
1 error
:compileDebugJavaWithJavac FAILED
What am i missing there?
If I return androidid (MODEL) it then works OK.
Maybe something to have with the class declaration??
Thanks in advance for your precious help
Elie
You are using getSerial incorrectly. Its a method not variable and available from API 26 or higher. For older versions, use Build.SERIAL
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
// Todo Don't forget to ask the permission
SerialNumber = Build.getSerial();
}
else
{
SerialNumber = Build.SERIAL;
}
Make sure you have READ_PHONE_STATE permission before calling getSerial(). Otherwise your app will crash.
Check this tutorial for asking permissions.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && PermissionsUtility.checkPermission
(PermissionsUtility.PHONE_STATE_PERMISSION, getActivity())) {
SerialNumber = Build.getSerial();
} else {
SerialNumber = Build.SERIAL;
}
The best way is to surround it with permission check before calling Build.getSerial() even though you have already asked permission from the user.
This is the clean way to do. This will not crash and works smoothly.
Make sure you have added the permission in the manifest file.
Here PermissionsUtility is the utility class to check permission which returns
returns
public static final String PHONE_STATE_PERMISSION =
Manifest.permission.READ_PHONE_STATE;
public static boolean checkPermission(String permission, Activity activity) {
return ContextCompat.checkSelfPermission(activity, permission) ==
PackageManager.PERMISSION_GRANTED;
}
This simply checks if your app has the permission or not. In Permission pass PHONE_STATE_PERMISSION.
In Android 9, Build.SERIAL is always set to "UNKNOWN" to protect users' privacy.
If your app needs to access a device's hardware serial number, you should instead request the READ_PHONE_STATE permission, then call getSerial().
Build.getSerial() method can be used from API26 and requires READ_PHONE_STATE.
To get more information, please refer to following link:https://developer.android.com/about/versions/pie/android-9.0-changes-28

Global class to register listeners for all the activities in android

I have two Activity classes. Activity A and Activity B. Later on I added Activity C which gets launched when user shakes the device being on Activity A or Activity B. Now if I register the ShakeListener in the Activity A and Acitivity B, I can achieve my goal
But what I want now, is a different thing, I do not want to change Activity A and Activity B. I want to write a different class, which runs for the whole app, and registers the ShakeListener for all the activities in the app. How can I do that? What kind of class should that be?
I tried extending BroadcastReceiver and registering the ShakeListener in the onReceive method, but used BOOT_EVENT which gets fired only when the device boots and not the starting of the application. So could not achieve my goal.
Then I was suggested by an SO user, to extend the Application class and registering the listener there. Now the listener gets registered, but now I need the currently running Activity and context to be passed to the Activity C. Here I'm back to zero again, because I don't want to add code in the Activity A or B. Also, AFAIK, the Application class is called before any of the Activity gets initiated, so is it possible to get the currently running Activity in the foreground?
Then I thought to move the code to find the activity in the Listener itself. Here also I needed to get the current activity and context. The context was the application context and then I tried to access all the currently open activities, following this thread. Based on the version the code is a bit different. And this is not the recommended way and gives me error.
This is the class:
package com.something.someotherthing;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.List;
/**
* Created by Inquisitive on 20/5/15.
*/
public class ShakeEventListener implements SensorEventListener {
private Context context;
private Activity activity;
private String[] getPreLollipop() {
try {
#SuppressWarnings("deprecation")
List<ActivityManager.RunningTaskInfo> tasks =
activityManager().getRunningTasks(1);
ActivityManager.RunningTaskInfo currentTask = tasks.get(0);
ComponentName currentActivity = currentTask.topActivity;
return new String[]{currentActivity.getClassName()};
}
catch(Exception e){
Log.d("version","Exception" +e.getClass());
String str[]= {"abc","def"};
return str;
}
}
private String[] getLollipop() {
final int PROCESS_STATE_TOP = 2;
try {
Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
List<ActivityManager.RunningAppProcessInfo> processes =
activityManager().getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo process : processes) {
if (
// Filters out most non-activity processes
process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&&
// Filters out processes that are just being
// _used_ by the process with the activity
process.importanceReasonCode == 0
) {
int state = processStateField.getInt(process);
if (state == PROCESS_STATE_TOP)
/*
If multiple candidate processes can get here,
it's most likely that apps are being switched.
The first one provided by the OS seems to be
the one being switched to, so we stop here.
*/
return process.pkgList;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return new String[] { };
}
private ActivityManager activityManager() {
return (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
}
public String[] get() {
if (Build.VERSION.SDK_INT < 21) {
Log.d("Version","Pre-lollipop");
for(String str:getPreLollipop())
Log.d("Version",str);
return getPreLollipop();
}
else {
Log.d("Version","Lollipop");
for(String str:getLollipop())
Log.d("Version",str);
return getLollipop();
}
}
public ShakeEventListener(Context context){
Log.d("ACCELEROMETER","inside the constructor of shake event listener");
this.context=context;
String str[] = get();
try {
Class<?> myClass = Class.forName(str[0]);
activity = (Activity) myClass.newInstance();
}catch(Exception e){
//do something
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy){
}
public void onSensorChanged(SensorEvent se){
Log.d("SENSOR","On sensor changed");
//need the activity and context here..
}
}
When I try with a device having version below lollipop. It catches the SecurityException. With a device having version above lollipop, it gives the package name of my application, but not the particular class.
1 what is the correct way to achieve my goal? Whatever approach I'm following is correct? in that case, I will try to debug the code to find activity
2 If not, what are the other alternatives by which I can achieve my goal of having a global listener that listens from any activity within the app.
3 Is it achievable at all, without changing the activities?
Please help.
In your case, you need to implement a Service to listen to sensors in the background. Go through documentation:Service
You cannot just use Broadcastreceiver to accomplish this task.
Reference:
Android sensor listening when app in background
Refer this link for help:
http://code.tutsplus.com/tutorials/android-barometer-logger-acquiring-sensor-data--mobile-10558
Create a main activity class which will be extended by Activity A, B and C.
You can register your ShakeListener in this main activity.
You can define your own ActivityStack in Application class, properly manage by adding and removing activities. Finally refer to this stack from activity C.
There is example how ActivityStack works:
Back Stack Example

solution for getSystemService function at Android

I want to test something about usb devices, I try to write a small program, I am sure that it is wrong but this is not the point of this question.
I am sure that my imports are OK but the Android Studio refused to build this class with an error about the GetSystemService(). I have the message:
Error:(65, 43) error: cannot find symbol method getSystemService(String).
I used also an example from http://android-er.blogspot.de/2013/10/list-attached-usb-devices-in-usb-host.html and the Android Studio also has the same error but if i install the Apk from this website then it is running on my device, so i supposed that something is wrong at Android Studio.
Any good idea?
OFFTOPIC "QT Creator is light years better"
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbAccessory;
import android.os.ParcelFileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileDescriptor;
import android.util.Log;
public class DeviceOpenActivity {
private static final String TAG = "DeviceOpenActivity";
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;
public static int fibonacci(int n) {
if (n<2) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
private void openAccessory() {
Log.d(TAG, "openAccessory: " + mAccessory);
mFileDescriptor = usbManager.openAccessory(mAccessory);
if (mFileDescriptor != null) {
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
}
}
}
If you check the example that you provided, you will verify that there is a MainActivity class that extends Activity class, which by its turn extends indirectly from Context. In order to call getSystemService() you need to have an available Context. If you make your DeviceOpenActivity extend Activity, Android Studio will not complain anymore about your call.
Just leave your class declaration like this:
public class MainActivity extends Activity
Don't forget that you need to provide a XML layout for your Activity, as well as the Activity methods, like onCreate().

Categories