So I Have a Zebra MC330M device.
I created earliar an application, and I would like to use the PDA built in barcode scanner.
If is run my app in debug mode, everything working well, I can read the barcodes, but If I create an staged or relase version apk, the barcode reader inactive, so the red light doesn't light if I press the button.
I created a simple,to demonstrate the problem, but you can reproduce the problem if you device has EMDK, else you get an exception.
Project
I implemented all of stuff by this tutorial: https://techdocs.zebra.com/emdk-for-android/11-0/tutorial/tutBasicScanningAPI
So I added this into gradle: compileOnly 'com.symbol:emdk:7.6.+'
I added thease two lines to the manfest:
<uses-permission android:name="com.symbol.emdk.permission.EMDK" />
<uses-library android:name="com.symbol.emdk" android:required="false"/>
I tried to add the dependency with implementation instead of compileOnly , but after that I got an Exception in this line: EMDKManager.getEMDKManager(context,object : EMDKManager.EMDKListener...
[UPDATE] When I push the scanner button, I get this error:
E/ActivityManager: Sending non-protected broadcast com.symbol.button.R1 from system 1208:system/1000 pkg android
java.lang.Throwable
at com.android.server.am.ActivityManagerService.checkBroadcastFromSystem(ActivityManagerService.java:18150)
at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:18728)
at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:18819)
at android.app.ContextImpl.sendBroadcastAsUser(ContextImpl.java:1040)
at com.android.server.RemoteKeyEventService.broadcastPublicKeyIntent(RemoteKeyEventService.java:672)
at com.android.server.RemoteKeyEventService.handleKeyEvent(RemoteKeyEventService.java:526)
at com.android.server.wm.InputMonitor.interceptKeyBeforeQueueing(InputMonitor.java:464)
at com.android.server.input.InputManagerService.interceptKeyBeforeQueueing(InputManagerService.java:1874)
My app is targeting API Level 30, and I put the permission inside the manifest.
I also tried to log the error, but there is no error, it seems the two listeners (status, and data) not triggered, or can not enable the scanner, but in this case I should get an error message.
Barcode Reader Class:
class ZebraBarcodeReader(
private val context: Context
){
private var emdkManager: EMDKManager? = null
private var barcodeManager: BarcodeManager? = null
private val barcodeLiveData = MutableLiveData<EventWrapper<String?>>()
init {
val result = EMDKManager.getEMDKManager(context,object : EMDKManager.EMDKListener{
override fun onOpened(p0: EMDKManager?) {
opened(p0)
}
override fun onClosed() {
closed()
}
})
if(result.statusCode != EMDKResults.STATUS_CODE.SUCCESS) {
Timber.d("$TIMBER_TAG ZebraBarcodeReader failed to connect")
}else{
Timber.d("$TIMBER_TAG EMDKManager object initialization in progress...")
}
}
fun getData(): LiveData<EventWrapper<String?>> = barcodeLiveData
private fun opened(manager: EMDKManager?){
emdkManager = manager
initBarcodeManager()
initScanner()
}
private var scanner: Scanner? = null
private fun initScanner() {
scanner = barcodeManager?.getDevice(BarcodeManager.DeviceIdentifier.DEFAULT)
if(scanner == null){
Timber.d("$TIMBER_TAG Scanner not found")
}else{
scanner?.addDataListener {scanDataCollection->
if(scanDataCollection != null && (scanDataCollection.result) == ScannerResults.SUCCESS){
val scanData = scanDataCollection.scanData
scanData.forEach {
val barcode = it.data
barcodeLiveData.postValue(EventWrapper(barcode))
}
}
}
scanner?.addStatusListener { statusData->
val state = statusData.state
var statusString = ""
when(state){
StatusData.ScannerStates.IDLE-> {
statusString = statusData.friendlyName + "is enabled and IDLE"
setConfig()
try {
scanner?.read()
}catch (e: ScannerException){
Timber.d(e)
}
}
StatusData.ScannerStates.WAITING-> {
statusString = "Scanner is waiting for trigger press"
}
StatusData.ScannerStates.SCANNING-> {
statusString = "Scanning"
}
StatusData.ScannerStates.DISABLED-> {
statusString = "Scanner is disabled"
}
StatusData.ScannerStates.ERROR-> {
statusString="An error occured"
}
else -> {
statusString="Another thing in else branch"
}
}
Timber.d("$TIMBER_TAG $statusString")
}
try {
scanner?.enable()
} catch (e: ScannerException) {
Timber.d("$TIMBER_TAG Scanner can not be enabled")
}
}
}
private fun setConfig() {
scanner?.let { s->
try {
val config = s.config
if(config.isParamSupported("config.scanParams.decodeHapticFeedback"))
config.scanParams.decodeHapticFeedback = true
s.config = config
}catch (e: Exception){
Timber.d(e)
}
}
}
private fun initBarcodeManager() {
barcodeManager = emdkManager?.getInstance(EMDKManager.FEATURE_TYPE.BARCODE) as BarcodeManager?
if(barcodeManager == null){
Timber.d("$TIMBER_TAG Barcode scanning not supported")
}
}
private fun closed(){
emdkManager?.let { m->
m.release()
emdkManager = null
}
Timber.d("$TIMBER_TAG : closed unexpectedly!")
}
fun destroy(){
emdkManager?.let { m->
m.release()
emdkManager = null
}
}
Please add the following to your manifest under the uses-permission tag:
<queries>
<package android:name="com.symbol.emdk.emdkservice" />
</queries>
Little bit weird for me, but I solved this problem.
I don't know why, but firstly I installed a debug version of my apk, after that I deleted it, and then I installed the staged/release version. I didn't work.
After that when I deleted the debug version, I restarted the device, and then installed release version, and it is works. I don't no why it was necessary, but I'm happy.
As an update, with A11, if you are using the EMDK to obtain the device's serial number, mac address, etc., then you should use:
<queries>
<package android:name="com.zebra.zebracontentprovider" />
<package android:name="com.symbol.dataanalytics" />
<package android:name="com.symbol.emdk.emdkservice" />
</queries>
Related
I have a application where I can press and hold and then create a shortcut of that particular deck/folder but when I delete the folder from the app itself, the icon(shortcut) stays there leading to a crash when taped upon. I placed a check for NPE so now its not crashing but I want to know if we can delete the shortcut on the folder deletion itself. Is there a way in Android Studio(kotlin). Below if the code to create that shortcut
fun createIcon(context: Context, did: DeckId) {
// This code should not be reachable with lower versions
val shortcut = ShortcutInfoCompat.Builder(this, did.toString())
.setIntent(
Intent(context, Reviewer::class.java)
.setAction(Intent.ACTION_VIEW)
.putExtra("deckId", did)
)
.setIcon(IconCompat.createWithResource(context, R.mipmap.ic_launcher))
.setShortLabel(Decks.basename(col.decks.name(did)))
.setLongLabel(col.decks.name(did))
.build()
try {
val success = ShortcutManagerCompat.requestPinShortcut(this, shortcut, null)
// User report: "success" is true even if Vivo does not have permission
if (AdaptionUtil.isVivo) {
showThemedToast(this, getString(R.string.create_shortcut_error_vivo), false)
}
if (!success) {
showThemedToast(this, getString(R.string.create_shortcut_failed), false)
}
} catch (e: Exception) {
Timber.w(e)
showThemedToast(this, getString(R.string.create_shortcut_error, e.localizedMessage), false)
}
}
/** Disables the shortcut of the deck whose id is [did] */
fun disableDeckShortcut(did: DeckId) {
val childDids = col.decks.childDids(did, col.decks.childMap()).map { it.toString() }
val deckTreeDids = listOf(did.toString(), *childDids.toTypedArray())
val errorMessage: CharSequence = getString(R.string.deck_shortcut_doesnt_exist)
ShortcutManagerCompat.disableShortcuts(this, deckTreeDids, errorMessage)
}
Above is how I solved the issue if anyone wants to read the same in detail here is the link
UPDATE: Added Main Activity code which contains Bluetooth permissions logic
I'm trying to utilize Android's CompanionDeviceManager API to find nearby bluetooth (non LE) devices on my Pixel 5 running Android 13, but it only ever seems to find nearby WiFi networks. I'm suspicious that the deviceFilter isn't working properly.
Initially, my code to configure the BluetoothDeviceFilter looked like this:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
// Match only Bluetooth devices whose name matches the pattern
.setNamePattern(Pattern.compile("(?i)\\b(Certain Device Name)\\b"))
.build()
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// Find only devices that match our request filter
.addDeviceFilter(deviceFilter)
// Don't stop scanning as soon as one device matching the filter is found.
.setSingleDevice(false)
.build()
With this code, however, no devices ever appear within the system generated Companion Device Pairing screen. The spinner spins until timeout
Thinking maybe my regex was unintentionally too restrictive, I changed the filter to use a regexp that allows everything, like so:
.setNamePattern(Pattern.compile(".*"))
But even this filter fails to allow any nearby bluetooth devices to appear in the Pairing screen.
When I intentionally don't add any filter all I see are WiFi networks, so the Companion Device Manager can work, it's just seemingly misconfigured for Bluetooth results.
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// No filter, let's see it all!
.setSingleDevice(false)
.build()
Using the Android OS's system Bluetooth menu I clearly see there are Bluetooth devices within range of my device, and I can even connect to them, but the same devices never appear within my app.
What am I doing wrong that's causing no nearby Bluetooth devices to appear in my CompanionDeviceManager Pairing Screen?
Code below:
HomeFragment.kt
class HomeFragment : Fragment() {
//Filter visible Bluetooth devices so only Mozis within range are displayed
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
// Match only Bluetooth devices whose name matches the pattern.
.setNamePattern(Pattern.compile(BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR))
.build()
private val pairingRequest: AssociationRequest = AssociationRequest.Builder()
// Find only devices that match this request filter.
.addDeviceFilter(deviceFilter)
// Don't stop scanning as soon as one device matching the filter is found.
.setSingleDevice(false)
.build()
private val deviceManager: CompanionDeviceManager by lazy {
requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
}
private val executor: Executor = Executor { it.run() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setupPairingButton()
}
/**
* This callback listens for the result of connection attempts to our Mozi Bluetooth devices
*/
#Deprecated("Deprecated in Java")
#SuppressLint("MissingPermission")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when (resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
deviceToPair?.createBond()
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
private fun setupPairingButton() {
binding.buttonPair.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
/**
* This is the approach to show a pairing dialog for Android 33+
*/
deviceManager.associate(pairingRequest, executor,
object : CompanionDeviceManager.Callback() {
// Called when a device is found. Launch the IntentSender so the user
// can select the device they want to pair with
override fun onAssociationPending(intentSender: IntentSender) {
intentSender.let { sender ->
activity?.let { fragmentActivity ->
startIntentSenderForResult(
fragmentActivity,
sender,
SELECT_DEVICE_REQUEST_CODE,
null,
0,
0,
0,
null
)
}
}
}
override fun onAssociationCreated(associationInfo: AssociationInfo) {
// Association created.
// AssociationInfo object is created and get association id and the
// macAddress.
var associationId = associationInfo.id
var macAddress: MacAddress? = associationInfo.deviceMacAddress
}
override fun onFailure(errorMessage: CharSequence?) {
// Handle the failure.
showBluetoothErrorMessage(errorMessage)
}
})
} else {
/**
* This is the approach to show a pairing dialog for Android 32 and below
*/
// When the app tries to pair with a Bluetooth device, show the
// corresponding dialog box to the user.
deviceManager.associate(
pairingRequest,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(
chooserLauncher,
SELECT_DEVICE_REQUEST_CODE,
null,
0,
0,
0,
null
)
}
override fun onFailure(error: CharSequence?) {
// Handle the failure.
showBluetoothErrorMessage(error)
}
}, null
)
}
}
}
companion object {
private const val SELECT_DEVICE_REQUEST_CODE = 0
private const val BLUETOOTH_DEVICE_NAME_REGEX_TO_FILTER_FOR = "(?i)\\bCertain Device Name\\b"
}}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
private var bluetoothEnableResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
binding.loadingSpinner.hide()
when (result.resultCode) {
Activity.RESULT_OK -> {
Snackbar.make(
binding.root,
resources.getString(R.string.bluetooth_enabled_lets_pair_with_your_mozi),
Snackbar.LENGTH_SHORT
).show()
}
Activity.RESULT_CANCELED -> {
Snackbar.make(
binding.root,
getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi),
Snackbar.LENGTH_INDEFINITE
)
.setAction(resources.getString(R.string._retry)) {
ensureBluetoothIsEnabled()
}
.show()
}
}
}
private val requestBluetoothPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
bluetoothEnableResultLauncher.launch(enableBluetoothIntent)
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
Snackbar.make(
binding.root,
getString(R.string.without_bluetooth_you_cant_pair_with_your_mozi),
Snackbar.LENGTH_INDEFINITE
)
.setAction(resources.getString(R.string._retry)) {
ensureBluetoothIsEnabled()
}
.show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupViews()
ensureBluetoothIsEnabled()
}
private fun setupViews() {
//Here we setup the behavior of the button in our rationale dialog: basically we need to
// rerun the permissions check logic if it was already denied
binding.bluetoothPermissionsRationaleDialogButton.setOnClickListener {
binding.permissionsRationaleDialog.animateShow(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH)
}
}
}
private fun ensureBluetoothIsEnabled() {
binding.loadingSpinner.show()
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
if (bluetoothAdapter == null) {
// Device doesn't support Bluetooth
binding.loadingSpinner.hide()
Snackbar.make(
binding.root,
resources.getString(R.string.you_need_a_bluetooth_enabled_device),
Snackbar.LENGTH_INDEFINITE
).show()
}
if (bluetoothAdapter?.isEnabled == false) {
// Check if Bluetooth permissions have been granted before we try to enable the
// device
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_CONNECT //TODO: test if this needs variant for legacy devices
) != PackageManager.PERMISSION_GRANTED
) {
/**
* We DON'T have Bluetooth permissions. We have to get them before we can ask the
* user to enable Bluetooth
*/
binding.loadingSpinner.hide()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
binding.permissionsRationaleDialog.animateShow(true)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT)
}
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH)) {
binding.permissionsRationaleDialog.animateShow(true)
} else {
requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH)
}
}
return
} else {
/**
* We DO have Bluetooth permissions. Now let's prompt the user to enable their
* Bluetooth radio
*/
binding.loadingSpinner.hide()
bluetoothEnableResultLauncher.launch(enableBluetoothIntent)
}
} else {
/**
* Bluetooth is enabled, we're good to continue with normal app flow
*/
binding.loadingSpinner.hide()
}
}
}
Android Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Bluetooth Permissions -->
<uses-feature android:name="android.software.companion_device_setup" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can strongly assert that your app
doesn't derive physical location. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags= "neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
...
</manifest>
You could try using an empty BluetoothDeviceFilter like this:
private val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder().build()
to signal to the API that you want Bluetooth devices, and see if at least the phone sees your device.
Then you could try again with the name filter, this time adding a service UUID filter with BluetoothDeviceFilter.Builder.addServiceUuid.
If you don't know the UUID of your device or don't want to use it as a filter, you can use an arbitrary one and set the mask to all zeros (the docs suggest that it might also work using null values).
This is a hackish solution, but it might help you move a step further
It might be a permission issue.
In the docs, I read:
The BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT, and BLUETOOTH_SCAN permissions are runtime permissions. Therefore, you must explicitly request user approval in your app before you can look for Bluetooth devices, make a device discoverable to other devices, or communicate with already-paired Bluetooth devices.
So you could to add the following code in your HomeFragment class:
private val requestMultiplePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
Log.d("Permission Request", "${it.key} = ${it.value}")
}
}
private val requestBluetooth = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
// granted
} else {
// denied
}
}
and in the onCreateView method:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestMultiplePermissions.launch(arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
))
} else {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
requestBluetooth.launch(enableBtIntent)
}
to request the permissions at runtime.
The documentation does not mention it, but it appears that even with the CompanionDeviceManager the location access must be enabled on the device.
The app does not need the location permission anymore, but it must be enabled.
I have successfully integrated Linphone SDK in my project with their dependency.
implementation 'org.linphone:linphone-sdk-android:5.1.59'
// Adding this dependency allows the linphone-sdk to automatically handle audio focus
implementation 'androidx.media:media:1.6.0'
And It is working completely ok when using credentials of linphone.But When I am trying to use our sip credentials of PBX It throws io error
I have tested our credentials of our local network in Linphone Android App It works fine. But when try to login in my app It throws error.
I have added this code for login in SIP.
fun login(domain: String, username: String, password: String) {
val mgr: ConnectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val listAddress: MutableList<String> = ArrayList()
mgr.getLinkProperties(mgr.activeNetwork)?.let{network->
network.dnsServers.forEach {
it.hostAddress?.let { it1 -> listAddress.add(it1) }
}
}
core.setDnsServers(listAddress.map { it }.toTypedArray())
val authInfo =
Factory.instance().createAuthInfo(username, null, password, null, null, domain, null)
val params = core.createAccountParams()
val senderUri = "sip:$username#$domain"
val identity = Factory.instance().createAddress(senderUri)
params.identityAddress = identity
val address = Factory.instance().createAddress("sip:$domain")
address?.transport = TransportType.Tls
params.serverAddress = address
params.isOutboundProxyEnabled = true
params.isRegisterEnabled = true
val account = core.createAccount(params)
getInstance().core.addAuthInfo(authInfo)
getInstance().core.addAccount(account)
getInstance().core.defaultAccount = account
core.start()
account.addListener { _, state, message ->
Log.e(TAG, "login: state $state $message" )
if ("$state" == "Failed") {
Utils().showShortToast(getInstance(), "Registration Failed")
} else if ("$state" == "Ok") {
Utils().showShortToast(getInstance(), "Registration Success")
}
}
}
I think your issue is that you try to manually set the DNS servers.
Try removing this part of your code:
val mgr: ConnectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val listAddress: MutableList<String> = ArrayList()
mgr.getLinkProperties(mgr.activeNetwork)?.let{network->
network.dnsServers.forEach {
it.hostAddress?.let { it1 -> listAddress.add(it1) }
}
}
core.setDnsServers(listAddress.map { it }.toTypedArray())
Linphone-SDK already handles that part.
Otherwise it looks OK. If issue persists enable debug logs
Factory.instance().setLogCollectionPath(context.filesDir.absolutePath)
Factory.instance().enableLogCollection(LogCollectionState.Enabled)
Factory.instance().setLoggerDomain(appName)
Factory.instance().enableLogcatLogs(true)
Factory.instance().loggingService.setLogLevel(LogLevel.Message)
and attach them.
I am trying to install an app from my fragment. I want the unknown source permission message to be displayed and then the installation process to take place. The problem is that on my first installation, app seems to be crashed.
Of course, next time (when installing another apk) this problem disappears. I did the following steps:
In my viewModel :
fun installApp(uri: Uri) {
viewModelScope.launch(context = exceptionHandler + DEFAULT) {
val promptInstall = Intent(Intent.ACTION_VIEW, uri)
promptInstall.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
promptInstall.setDataAndType(uri, "application/vnd.android" + ".package-archive")
promptInstall.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
promptInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityMutableLiveData.postValue(promptInstall.toOneTimeEvent())
}
}
and then in my fragment :
viewModel.startActivityLiveData.observe(viewLifecycleOwner, Observer { oneTimeEvent ->
(oneTimeEvent.getValue() as Intent).startActivityFromIntent(requireActivity())})
And finally this is my extension function :
fun Intent.startActivityFromIntent(context: Context) = (context as Activity).startActivity(this)
Well, it seems that the process I mentioned above has a problem(it crashed!) in Android 10 and above. Here is the solution I found:
private fun Uri.installApp(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!context.packageManager.canRequestPackageInstalls()) {
startForResult.launch(Intent(ACTION_MANAGE_UNKNOWN_APP_SOURCES))
Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
} else {
viewModel.installApp(this)
}
} else {
viewModel.installApp(this)
}
and we must do as below :
private val startForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
// your logic...
}
RESULT_CANCELED -> {
//do something
}
else -> {
}
}
}
I am developing an Android app that, among other things, launches other apps on the device. Here is the code that I use when I would like to launch an external app:
fun openApp(context: Context, appName: String, packageName: String) {
if (isAppInstalled(context, packageName))
if (isAppEnabled(context, packageName)) {
context.startActivity(context.packageManager.getLaunchIntentForPackage(packageName))
}
else
Toast.makeText(context, "$appName app is not enabled.", Toast.LENGTH_SHORT).show()
else
Toast.makeText(context, "$appName app is not installed.", Toast.LENGTH_SHORT).show()
}
private fun isAppInstalled(context: Context, packageName: String): Boolean {
val pm = context.packageManager
try {
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
return true
} catch (ignored: PackageManager.NameNotFoundException) {
}
return false
}
private fun isAppEnabled(context: Context, packageName: String): Boolean {
var appStatus = false
try {
val ai = context.packageManager.getApplicationInfo(packageName, 0)
if (ai != null) {
appStatus = ai.enabled
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return appStatus
}
}
Certain apps will be launched without a problem and others (e.g. Google Play Music) will cause the following error:
Attempt to invoke virtual method 'boolean android.content.Intent.migrateExtraStreamToClipData()' on a null object reference
I have found some other discussion related to this issue here and here which suggest it has to do with Google Play Services but was apparently fixed many versions ago (all apps are update to date). Does anyone of any insight as to what would make some apps launch properly and not others?
Edit: As I linked above, I am aware of the fixes that have been discussed previously but all of my apps (including Google Play services at version 12.8.74) are update so those fixes do not help.