I have an app that acts as a service, and the second app needs to connect to it, so I'm using Android Interface Defenition Language (AIDL).
What is the best approach to limit the service accepting only that specific app? and in which method the identity of the client app should happen?
I know a client should have a copy of .aidl file, but I need more ways to check who is connecting to the service.
There are ways to check the identity of the app connecting to your aidl service.
By checking the signing of the apps:
If you want only the apps that are signed by the same key as your app or SystemApps can connect to your service refer kotlin code is below :
Same Key Sign and IsSyetm app:
private fun isSameKeySinged(packageManager: PackageManager, packageNameOfTheOtherApp: String): Boolean {
return packageManager.checkSignatures(
BuildConfig.APPLICATION_ID, packageNameOfTheOtherApp
) == PackageManager.SIGNATURE_MATCH
}
private fun isSystemApp(packageManager: PackageManager, packageNameOfTheOtherApp: String): Boolean {
try {
val applicationInfo = packageManager.getApplicationInfo(
packageNameOfTheOtherApp, 0
)
if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0 || applicationInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0) {
return true
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return false
}
By Checking the caller App: You can retrieve the package name of the caller using getCalledId inside the methods which other app call:
val callingApp = packageManager.getNameForUid(Binder.getCallingUid())
Related
I am using BluetoothLeScanner to scan for BLE devices and get a list of objects representing the devices to show inside my app (not connecting to any of them).
I am interested in doing the same, but using the CompanionDeviceManager now. Its callback CompanionDeviceManager.Callback.onDeviceFound(chooserLauncher: IntentSender?) unfortunately does not return any human readable form of found devices... the closest it gets is the IntentSender.writeToParcel method, but I am not sure how to use it in this situation.
I am not constrained to use the CompanionDeviceManager but I wanted to follow the OS version specific guidelines, we are supposed to use CompanionDeviceManager for Bluetooth devices scanning starting from API 26, but it seems useless in my case... so is there any way to get devices data from that callback, or should I just ditch it and stay with BluetoothLeScanner for all OS versions?
Late answer but it might help someone else. You can create a bluetooth device picker in combination with ActivityResultContracts.StartIntentSenderForResult() in order to get the BluetoothDevice. From there you will have access to all the device info that you need. Recent changes added some Android 12 permissions like android.permission.BLUETOOTH_CONNECT. Your mileage may vary.
val context = LocalContext.current
// Get the device manager instance
val deviceManager: CompanionDeviceManager by lazy {
ContextCompat.getSystemService(
context,
CompanionDeviceManager::class.java
) as CompanionDeviceManager
}
// Create a filter of your choice. Here I just look for specific device names
val deviceFilter: BluetoothDeviceFilter by lazy {
BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile(supportedDevices))
.build()
}
// Create a pairing request with your filter from the last step
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.build()
// Create a picker for discovered bluetooth devices
val bluetoothDevicePicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult(),
onResult = {
val device: BluetoothDevice? =
it.data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
try {
// Now that you have the desired device, do what you need to with it
device?.apply {
when {
name?.matches(Regex(firstDevicePattern)) == true -> {
Log.i(TAG, "${this.name} connected")
onFirstDeviceDiscovered(device)
}
name?.matches(Regex(secondDevicePattern)) == true -> {
Log.i(TAG, "${this.name} connected")
onSecondDeviceDiscovered(device)
}
}
}
} catch (e: SecurityException) {
e.printStackTrace()
//TODO: handle the security exception (this is possibly a bug)
// https://issuetracker.google.com/issues/198986283
}
}
)
// A utility function to centralize calling associate (optional)
val associateDevice: (AssociationRequest) -> Unit = { request ->
// Attempt to associate device(s)
deviceManager.associate(
request,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
val sender = IntentSenderRequest.Builder(chooserLauncher)
.build()
bluetoothDevicePicker.launch(sender)
}
override fun onFailure(error: CharSequence?) {
//TODO: handle association failure
}
}, null
)
}
There was a service in framework level, they are binding and starting that service from the framework only. I need to access that service and consume those APIs from the client (Android) side. I have gone through the most of the examples which are having code for create/start service connection from the client and whenever service connected in that connected listener using IBinder, we can access those APIs.
I tried, like adding aidl file at client side with the same package structure and added following code.
val DevManager =applicationContext.getSystemService("servicename")
val clazz = Class.forName(DevManager.javaClass.name)
val method = DevManager.javaClass.getDeclaredMethod(
"getIDevManager",
Context::class.java
)
method.isAccessible = true
val DevService: IDevManager =
method.invoke(DevManager) as IDevManager
var status = DevManager.devStatus
We are getting NoSuchMethodException.
Please suggest how can we achieve this, thanks in advance.
try {
val testManager = TestApplication.context!!.getSystemService(SERVICE_NAME)
val status = testManager.javaClass.getDeclaredMethod("methodName")
status.isAccessible = true
result = (status.invoke(testManager)).toString()
} catch (e: Exception) {
Log.d(TAG, " exception: ${e.message}")
e.printStackTrace()
}
I am trying to build an app that use google speech cloud api in android kotlin
here is my code
launch {
googleTextToSpeech = TextToSpeechClient.create()
googleTextToSpeech?.let {
viewModel.speakGoogle(googleTextToSpeech!!, totalMessage, player)
}
}
fun speakGoogle(textToSpeech: com.google.cloud.texttospeech.v1.TextToSpeechClient, message: String, player: MediaPlayer) {
val filePath = Environment.getExternalStorageDirectory().absolutePath + "/google_" + System.currentTimeMillis() + ".mp3"
launch {
var msg = android.text.Html.fromHtml(message).toString()
msg = msg.replace("\"", "")
var input = SynthesisInput.newBuilder().setText(msg).build()
var voice = VoiceSelectionParams.newBuilder().setLanguageCode("en-US").setSsmlGender(SsmlVoiceGender.FEMALE).build()
var audio = AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build()
var response = textToSpeech.synthesizeSpeech(input, voice, audio)
response?.let {
try {
val inputStream = response.audioContent.toByteArray()
File(filePath).outputStream().use { inputStream }
player.setDataSource(filePath)
player.prepare()
player.start()
} catch (e: IOException) {
Log.i("AMIRA00000", e.toString())
} catch (e: IllegalStateException) {
Log.i("AMIRA00000", e.toString())
}
}
}
}
I am also having my google-service.json file included in the app
but I am getting the following error
java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
at com.google.auth.oauth2.DefaultCredentialsProvider.getDefaultCredentials(DefaultCredentialsProvider.java:134)
at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:119)
at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:91)
at com.google.api.gax.core.GoogleCredentialsProvider.getCredentials(GoogleCredentialsProvider.java:67)
at com.google.api.gax.rpc.ClientContext.create(ClientContext.java:135)
at com.google.cloud.texttospeech.v1.stub.GrpcTextToSpeechStub.create(GrpcTextToSpeechStub.java:74)
at com.google.cloud.texttospeech.v1.stub.TextToSpeechStubSettings.createStub(TextToSpeechStubSettings.java:100)
at com.google.cloud.texttospeech.v1.TextToSpeechClient.<init>(TextToSpeechClient.java:128)
at com.google.cloud.texttospeech.v1.TextToSpeechClient.create(TextToSpeechClient.java:109)
at com.google.cloud.texttospeech.v1.TextToSpeechClient.create(TextToSpeechClient.java:101)
at com.sbs16.ensofia.view.main.MainFragment$setupBinding$3$$special$$inlined$let$lambda$2.invokeSuspend(MainFragment.kt:186)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
I am also having another json file thathave private key and other credential settings but I do not know how to use it
I'm not an Android developer, but my feeling is that you shouldn't call the TextToSpeech directly from your Android app. You should call it through a backend (On AppEngine for example, or on Firebase Functions).
This backend is authenticated by the TextToSpeech API, and your Android client is authenticated on your backend.
Thereby, you can control who use your app, and your TextToSpeech feature. In any case, never put the service account secret key file in your app, either anybody that download you app can steal the key and perform call on the service account behalf and you will pay the bill!
Google is putting its Android API for accessing Google services (i.E. Google Drive) to rest and is replacing it with REST.
And while there is a 'migration guides', it fails to build a APK package ready for installation, because of 'Duplicate Class definition' or something.
For some reason it is incredibly hard to find some comprehensive information about how to access a Google Service using REST via Android (preferably using methods natively available to the OS).
After a lot of searching, puzzling, scratching my head, occasional swearing and a lot of learning about things I really didn't want to care about, I'd like to share a few pieces of code, that are actually working for me.
Disclaimer: I'm a rookie Android programmer (who really doesn't how to pick his battles), so if there are things in here, that have the real Android wizards shaking their heads, I hope you'll forgive me.
All code samples are written in Kotlin and Android Studio.
Worth noting: Only the 'application data folder' is queried in this little tutorial, you will need to adjust the requested scopes if you want to do something else.
Necessary preparations
Create a project and an OAuth key for your application as described here. Many of the information I gathered for authorization came from that place, so expect to find some similarities.
The Dashboard for your project may be found at https://console.developers.google.com/apis/dashboard
Add implementation "com.google.android.gms:play-services-auth:16.0.1" to your applications gradle file. This dependency will be used for authentication purposes.
Add 'internet' support to your applications manifest
<uses-permission android:name="android.permission.INTERNET"/>
Authenticating
The beginning of our journey is the authentication.
For this purpose, I used the GoogleSignIn Framework.
Create an activity (or use your main activity, your choice) and override the onActivityResult method there.
Add a block like this:
if (requestCode == RC_SIGN_IN) {
GoogleSignIn.getSignedInAccountFromIntent(data)
.addOnSuccessListener(::evaluateResponse)
.addOnFailureListener { e ->
Log.w(RecipeList.TAG, "signInResult:failed =" + e.toString())
evaluateResponse(null)
}
}
RC_REQUEST_CODE is an arbitrarily chosen ID value defined in the companion object as constant.
Once you want to perform authentication (i.E. by clicking of a button), you will need to start the activity we have just declared the callback for.
For this purpose, you need to prepare the authentication request first.
GoogleSignIn.getClient(this, GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("YourClientIDGoesHere.apps.googleusercontent.com")
.requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
.build())
This request gives you a client object you can start using straight away by calling.
startActivityForResult(client.signInIntent, RC_SIGN_IN)
This call will cause the authorization screen to pop up (if necessary), allow the user to select an account and then close itself again, passing the data to onActivityResult
To fetch the previously signed in user (without starting a new activity), you can also use the GoogleSignIn.getLastSignedInAccount(this); method in the background.
On failure either of these methods return null, so be ready to deal with that.
Now that we have an authenticated user, what do we do with it?
We ask for an auth token.
Right now, we only have an idToken in our account object, which is absolutely useless for what we want to do, because it doesn't allow us to call the API.
But Google comes to the rescue once more and supplies us with the GoogleAuthUtil.getToken(this, account.account, "oauth2:https://www.googleapis.com/auth/drive.appdata") call.
This call will forward the account information and return a String if all goes right: The auth token we need.
To be noted: This method performs a network request, meaning that it will throw up in your face, if you attempt to execute it in your UI thread.
I created a helper class which mimics the behavior (and API) of Googles 'Task' object, which takes care of the nitty gritty of calling a method on a thread and notifying the calling thread that it is done.
Save the auth token somewhere you can find it again, authorization is (finally) done with.
Querying the API
This part is far more straightforward than the previous one and goes hand in hand with the Google Drive REST API
All network requests need to be executed on a 'non-UI' thread, which is why I wrapped them up in my helper class to notify me once there is data to display.
private fun performNet(url: String, method: String, onSuccess: (JSONObject) -> Unit)
{
ThreadedTask<String>()
.addOnSuccess { onSuccess(JSONObject(it)) }
.addOnFailure { Log.w("DriveSync", "Sync failure $it") }
.execute(executor) {
val url = URL(url)
with (url.openConnection() as HttpURLConnection)
{
requestMethod = method
useCaches = false
doInput = true
doOutput = false
setRequestProperty("Authorization", "Bearer $authToken")
processNetResponse(responseCode, this)
}
}
}
private fun processNetResponse(responseCode: Int, connection: HttpURLConnection) : String
{
var responseData = "No Data"
val requestOK = (responseCode == HttpURLConnection.HTTP_OK)
BufferedReader(InputStreamReader(if (requestOK) connection.inputStream else connection.errorStream))
.use {
val response = StringBuffer()
var inputLine = it.readLine()
while (inputLine != null) {
response.append(inputLine)
inputLine = it.readLine()
}
responseData = response.toString()
}
if (!requestOK)
throw Exception("Bad request: $responseCode ($responseData)")
return responseData
}
This block of code is a rather generic helper function I put together from various sources and essentially just takes the URL to query, the method to perform (GET, POST, PATCH, DELETE) and constructs a HTTP request from it.
The auth token we got earlier during the authorization is passed as a header to the request to authenticate and identify ourselves as 'the user' to Google.
Google will, if everything is OK, reply with HTTP_OK (200) and onSuccess will be called, which will translate the JSON reply to a JSONObject, which will then be passed to the evaluation function we registered earlier.
Fetching the list of files
performNet("https://www.googleapis.com/drive/v3/files?spaces=appDataFolder", "GET")
The spaces parameter serves to tell Google, that we don't want to see the root folder but the application data folder. Without this parameter, the request would fail, because we only requested access to the appDataFolder.
The response should contain a JSONArray under the files key, which you then can parse and draw whatever information you want.
The ThreadTask class
This helper class encapsulates the steps necessary to perform an operation on a different context and perform a callback on the instantiating thread upon completion.
I am not claiming that this is THE way to this, it's just my 'Simply doesn't know any better'-way.
import android.os.Handler
import android.os.Looper
import android.os.Message
import java.lang.Exception
import java.util.concurrent.Executor
class ThreadedTask<T> {
private val onSuccess = mutableListOf<(T) -> Unit>()
private val onFailure = mutableListOf<(String) -> Unit>()
private val onComplete = mutableListOf<() -> Unit>()
fun addOnSuccess(handler: (T) -> Unit) : ThreadedTask<T> { onSuccess.add(handler); return this; }
fun addOnFailure(handler: (String) -> Unit) : ThreadedTask<T> { onFailure.add(handler); return this; }
fun addOnComplete(handler: () -> Unit) : ThreadedTask<T> { onComplete.add(handler);return this; }
/**
* Performs the passed code in a threaded context and executes Success/Failure/Complete handler respectively on the calling thread.
* If any (uncaught) exception is triggered, the task is considered 'failed'.
* Call this method last in the chain to avoid race conditions while adding the handlers.
*
*/
fun execute(executor: Executor, code: () -> T)
{
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
publishResult(msg.what, msg.obj)
}
}
executor.execute {
try {
handler.obtainMessage(TASK_SUCCESS, code()).sendToTarget()
} catch (exception: Exception) {
handler.obtainMessage(TASK_FAILED, exception.toString()).sendToTarget()
}
}
}
private fun publishResult(returnCode: Int, returnValue: Any)
{
if (returnCode == TASK_FAILED)
onFailure.forEach { it(returnValue as String) }
else
onSuccess.forEach { it(returnValue as T) }
onComplete.forEach { it() }
// Removes all handlers, cleaning up potential retain cycles.
onFailure.clear()
onSuccess.clear()
onComplete.clear()
}
companion object {
private const val TASK_SUCCESS = 0
private const val TASK_FAILED = 1
}
}
The order of execution is important in this case.
You first need to add the callbacks to the class object and at the end you need to call execute and supply it with the executor you want to run the thread with and of course the code you want to execute.
It is not everything you can do with Google Drive, but it's a start and I hope this little compilation will save someone else some grief in the future.
I'm creating an app that needs to behave differently if it's running in the work profile.
There is any possibility to know that?
The documentation has nothing about it and I already tried to add a restriction that is only available in the work profile and it works, but I need a solution without any action from the administrator.
Android for work information:
http://www.android.com/work/
Android for work documentation:
https://developer.android.com/training/enterprise/index.html
I found a solution :
if isProfileOwnerApp return true for one package name, it means that your app (badged) is running on work profile.
if your app is running in normal mode (no badge) isProfileOwnerApp return false for all admins even if there is a Work profile.
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
List<ComponentName> activeAdmins = devicePolicyManager.getActiveAdmins();
if (activeAdmins != null){
for (ComponentName admin : activeAdmins){
String packageName= admin.getPackageName();
Log.d(TAG, "admin:"+packageName);
Log.d(TAG, "profile:"+ devicePolicyManager.isProfileOwnerApp(packageName));
Log.d(TAG, "device:"+ devicePolicyManager.isDeviceOwnerApp(packageName));
}
}
I took the response from #earlypearl into a Kotlin class:
class UserProfile(context: Context) {
private val weakContext = WeakReference(context)
val isInstalledOnWorkProfile: Boolean
get() {
return weakContext.get()?.let {
val devicePolicyManager =
it.getSystemService(AppCompatActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val activeAdmins = devicePolicyManager.activeAdmins
activeAdmins?.any { devicePolicyManager.isProfileOwnerApp(it.packageName) } ?: false
} ?: false
}
}