How to know if Android TalkBack is active? - android

I'm developing an application that uses TalkBack to guide people through it. However, in those situations I want to have some subtile differences in the layout of the application so navigation is easier and also have extra voice outputs (with TextToSpeech) to help guide the user.
My problem is that I only want those changes and extra outputs if the user has TalkBack active.
Is there any way to know if it is? I didn't find anything specific to access TalkBack settings directly, but I was hoping there was some form of accessing general phone settings that could let me know what I need.

The recommended way of doing this is to query the AccessibilityManager for the enabled state of accessibility services.
AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = am.isEnabled();
boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();

Novoda have released a library called accessibilitools which does this check. It queries the accessibility manager to check if there are any accessibility services enabled that support the "spoken feedback" flag.
AccessibilityServices services = AccessibilityServices.newInstance(context);
services.isSpokenFeedbackEnabled();
public boolean isSpokenFeedbackEnabled() {
List<AccessibilityServiceInfo> enabledServices = getEnabledServicesFor(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
return !enabledServices.isEmpty();
}
private List<AccessibilityServiceInfo> getEnabledServicesFor(int feedbackTypeFlags) {
return accessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
}

You can create an inline function in kotlin like:
fun Context.isScreenReaderOn():Boolean{
val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
if (am != null && am.isEnabled) {
val serviceInfoList =
am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
if (!serviceInfoList.isEmpty())
return true
}
return false}
And then you can just call it whenever you need it like:
if(context.isScreenReaderOn()){
...
}
Tested and works fine for now.

For an example, look at isScreenReaderActive() in HomeLauncher.java file in the Eyes-Free shell application (via groups thread).
To sum up: you detect all screen readers with Intents, then query the status provider of each to see if it is active.
If you really want to limit it to TalkBack only, you could try checking the ResolveInfo.serviceInfo.packageName for each result returned from queryIntentServices() to see if it matches the TalkBack package.

AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (am != null && am.isEnabled()) {
List<AccessibilityServiceInfo> serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
if (!serviceInfoList.isEmpty())
return true;
}
return false;

For me, I solved this problem in this way , it works well in my project:
use getEnabledAccessibilityServiceList() to get all Accessibility service, a service whose status is open will be in this list
Talkback contain a activity named com.android.talkback.TalkBackPreferencesActivity, you can traversing the list to find whether the talkback service is open
The detailed code below:
private static final String TALKBACK_SETTING_ACTIVITY_NAME = "com.android.talkback.TalkBackPreferencesActivity";
public static boolean accessibilityEnable(Context context) {
boolean enable = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
AccessibilityManager manager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> serviceList = manager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
for (AccessibilityServiceInfo serviceInfo : serviceList) {
String name = serviceInfo.getSettingsActivityName();
if (!TextUtils.isEmpty(name) && name.equals(TALKBACK_SETTING_ACTIVITY_NAME)) {
enable = true;
}
}
} catch (Exception e) {
if (Logging.isDebugLogging()) {
e.printStackTrace();
}
}
}
return enable;
}

Thanks to #david-z answer (https://stackoverflow.com/a/41357058/2713403) I made this approach to know if the Android Accessibility Suite by Google is enabled
/**
* This method checks if Google Talkback is enabled by using the [accessibilityManager]
*/
private fun isGoogleTalkbackActive(accessibilityManager : AccessibilityManager) : Boolean
{
val accessibilityServiceInfoList = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
for (accessibilityServiceInfo in accessibilityServiceInfoList)
{
if ("com.google.android.marvin.talkback".equals(accessibilityServiceInfo.resolveInfo.serviceInfo.processName))
{
return true
}
}
return false
}
Remember to register the google URI as constant :) and get the Accessibility Manager instance as #caseyburkhardt says (https://stackoverflow.com/a/12362545/2713403). The difference with the #david-z answer is that I got the Android Accessibility Suite package name instead of its app name because it is more secure. If you want to review if another accessibility suite is enabled (like the Samsung Screen Reader) after this check, you can check
if (accessibilityManager.isTouchExplorationEnabled)
Cheers!

If you are working with compose you can add this utility extension on Context:
#Composable
internal fun Context.collectIsTalkbackEnabledAsState(): State<Boolean> {
val accessibilityManager =
this.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?
fun isTalkbackEnabled(): Boolean {
val accessibilityServiceInfoList =
accessibilityManager?.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
return accessibilityServiceInfoList?.any {
it.resolveInfo.serviceInfo.processName.equals(TALKBACK_PACKAGE_NAME)
} ?: false
}
val talkbackEnabled = remember { mutableStateOf(isTalkbackEnabled()) }
val accessibilityManagerEnabled = accessibilityManager?.isEnabled ?: false
var accessibilityEnabled by remember { mutableStateOf(accessibilityManagerEnabled) }
accessibilityManager?.addAccessibilityStateChangeListener { accessibilityEnabled = it }
LaunchedEffect(accessibilityEnabled) {
talkbackEnabled.value = if (accessibilityEnabled) isTalkbackEnabled() else false
}
return talkbackEnabled
}
private const val TALKBACK_PACKAGE_NAME = "com.google.android.marvin.talkback"
And then use it in the target composable:
#Composable
fun SomeComposable() {
val talkbackEnabled by LocalContext.current.collectIsTalkbackEnabledAsState()
if (talkbackEnabled) {
/** do something here **/
}
}

Open system setting and go to accessibility and tap to off Talk back option

Related

Android Permissions Helper Functions

I have an activity that requires camera permission.
this activity can be called from several user configurable places in the app.
The rationale dialog and permission dialog themselves should be shown before the activity opens.
right now I am trying to handle these dialogs in some kind of extension function.
fun handlePermissions(context: Context, required_permissions: Array<String>, activity: FragmentActivity?, fragment: Fragment?): Boolean {
var isGranted = allPermissionsGranted(context, required_permissions)
if (!isGranted) {
//null here is where I used to pass my listener which was the calling fragment previously that implemented an interface
val dialog = DialogPermissionFragment(null, DialogPermissionFragment.PermissionType.QR)
activity?.supportFragmentManager?.let { dialog.show(it, "") }
//get result from dialog? how?
//if accepted launch actual permission request
fragment?.registerForActivityResult(ActivityResultContracts.RequestPermission()) { success ->
isGranted = success
}?.launch(android.Manifest.permission.CAMERA)
}
return isGranted
}
But I am having trouble to get the dialog results back from the rationale/explanation dialog.
My research until now brought me to maybe using a higher order function, to pass a function variable to the dialog fragment that returns a Boolean value to the helper function. But I am absolutely unsure if thats the right thing.
I thought using my own code would be cleaner and less overhead, could I achieve this easier when using a framework like eazy-permissions? (is Dexter still recommendable since its no longer under development)
is that even a viable thing I am trying to achieve here?
One approach that I've implemented and seems viable to use is this:
Class PermissionsHelper
class PermissionsHelper(private val activity: Activity) {
fun requestPermissionsFromDevice(
arrayPermissions: Array<String>, permissionsResultInterface: PermissionsResultInterface
) {
setPermissionResultInterface(permissionsResultInterface)
getMyPermissionRequestActivity().launch(arrayPermissions)
}
fun checkPermissionsFromDevice(permission: String): Boolean {
return ContextCompat.checkSelfPermission(activity, permission) ==
PackageManager.PERMISSION_GRANTED
}
}
Class PermissionsResultInterface to the helper class be able to communicate with the activity.
interface PermissionsResultInterface {
fun onPermissionFinishResult(permissions: MutableMap<String, Boolean>)
}
Usage with this approach to remove all files from app directory:
private fun clearFiles(firstCall: Boolean = false) {
if (verifyStoragePermissions(firstCall)) {
val dir = File(getExternalFilesDir(null).toString())
removeFileOrDirectory(dir)
Toast.makeText(
applicationContext,
resources.getString(R.string.done),
Toast.LENGTH_SHORT
).show()
}
}
private fun verifyStoragePermissions(firstCall: Boolean = false): Boolean {
val arrayListPermissions = arrayOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
for (permission in arrayListPermissions) {
if (!PermissionsHelper(this).checkPermissionsFromDevice(permission)) {
if (firstCall) PermissionsHelper(this)
.requestPermissionsFromDevice(arrayListPermissions, this)
else PermissionsDialogs(this).showPermissionsErrorDialog()
return false
}
}
return true
}
override fun onPermissionFinishResult(permissions: MutableMap<String, Boolean>) {
clearFiles()
}
With this approach you are able to call the permissions helper and using the result interface, after each of the answers from user, decide wether you still need to make a call for permissions or show a dialog to him.
If you need any help don't hesitate to contact me.

Android: How to check internet connectivity on API level 29 or higher in android studio?

Before i was using these few lines of codes to check if the device is connected to the internet or not:
fun isOnline(): Boolean {
val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
But getActiveNetworkInfo() was deprecated in Android 10 and official documentation is suggesting to use NetworkCallbacks instead for apps that target Android 10 (API level 29) and higher.
So here's the problem, i didn't find any resources that might help to implement the NetworkCallbacks except the one here: on medium.com. But it's too complicated.
Is there any better or simpler way to implement this or i must go through the whole lengthy process?
You can use NetworkCapablities class for checking internet connectivity before making the network request.
fun internetCheck(c: Context): Boolean {
val cmg = c.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+
cmg.getNetworkCapabilities(cmg.activeNetwork)?.let { networkCapabilities ->
return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|| networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
}
} else {
return cmg.activeNetworkInfo?.isConnectedOrConnecting == true
}
return false
}
But if your activity needs to listen to Network connectivity changes, you should implement NetworkCallbacks.
You can check it by getting all networks with getAllNetworks() and loop through each of them to see if any is connected.
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun isOnline(): Boolean {
val connectivityMgr = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val allNetworks: Array<Network> = connectivityMgr.allNetworks // added in API 21 (Lollipop)
for (network in allNetworks) {
val networkCapabilities = connectivityMgr!!.getNetworkCapabilities(network)
return (networkCapabilities!!.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|| networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)))
}
}
return false
}
Also add the below permission in manifest:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
UPDATE
Can you tell me some other possible internet providers other than TRANSPORT_CELLULAR, TRANSPORT_WIFI and TRANSPORT_ETHERNET?
The answer to this can change over time; although, to the date, these transport technologies are sufficient for accessing the internet on mobile sets in terms of the technology capabilities provided nowadays.
Although there are other transport methods that might be utilized by device manufactures in the future.
For instance: TRANSPORT_WIFI_AWARE which is a technology that enable devices running Android 8.0 (API level 26) and higher to discover and connect directly to each other without any other type of connectivity between them. So, it's a transport method between devices over the Wi-Fi; but so far it doesn't pass internet between devices; maybe that can be used in the future.
Similarly, TRANSPORT_LOWPAN allows Android devices to communicate directly with other peer devices, even ultra-low power battery operated nodes that may not have WiFi or Bluetooth connectivity (such as door locks and window sensors). Again today TRANSPORT_LOWPAN doesn't pass the internet between those devices, but it facilitates the communication locally among them.
TRANSPORT_VPN Allows the communication between devices over a private network which should be explicitly coded by you; unless you do that, you don't have to use it to check the internet availability.
you Can create an extension like this
fun Context.isOnline(): Boolean {
return try {
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
//should check null because in airplane mode it will be null
netInfo != null && netInfo.isConnected
} catch (e: NullPointerException) {
e.printStackTrace()
false
}
}
And create BroadcastReceiver like this
class Connection : BroadcastReceiver() {
lateinit var dialog: Dialog
override fun onReceive(context: Context, intent: Intent) {
dialog = Dialog(context)
dialog.apply {
setContentView(R.layout.dialog_connection)
setCancelable(false)
window!!.setBackgroundDrawableResource(android.R.color.transparent)
window!!.setGravity(Gravity.CENTER)
window!!.attributes.windowAnimations = R.style.AnimationPopup
}
check(context = context) {
if (!it) {
dialog.show()
} else {
dialog.cancel()
}
}
}
private fun check(context: Context, result: (Boolean) -> Unit) {
try {
if (context.isOnline()) {
result(true)
} else {
result(false)
}
} catch (e: java.lang.NullPointerException) {
e.printStackTrace()
}
}
}
public boolean checkNetworkConnection(Context context){
ConnectivityManager connectivityManager =
(ConnectivityManager)context.getSystemService(ConnectivityManager.class);
Network currentNetwork = connectivityManager.getActiveNetwork();
if(currentNetwork==null){
return false;
}
else {
return true;
}
}

Android dataBinding does not refresh ui

I have some trouble with Android data-binding.
I have a class like this:
class AppConfig private constructor() : BaseObservable() {
#Bindable
var title = ""
fun updateTitle(newTitle: String) {
title = newTitle
notifyPropertyChanged(BR.title)
}
......
}
When the app is in background, the app received an update push and function updateTitle is called. Then I turn to my app, I can see the title has changed. Then I push another update, the title doesn't change. Then I press the home button and bring the app to front again, the title is updated.
I have read the ViewDataBinding source code:
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (mLifecycleOwner != null) {
Lifecycle.State state = mLifecycleOwner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
The condition !state.isAtLeast(Lifecycle.State.STARTED) failed at the first time, and variable mPendingRebind is set true. It seems that only when mRebindRunnable or mFrameCallback runs, variable mPendingRebind will be set false again. So the UI will never refresh.
I've seen this issue Data binding - XML not updating after onActivityResult. I try to use SingleLiveEvent, and I call updateObserver.call() in Activity's onResume. It doesn't work.
I've also tried to use reflect to set mPendingRebind false forcibly. It works but I think this is not a good way. What should I do?
Try this
var title = ""
#Bindable get() = title

Any way to monitor discovered BLE peripherals without connecting?

Is there any way of being notified if a discovered BLE peripheral moves out of range or otherwise drops out of sight? I'm using rxBleClient.scanBleDevices() to build a list of devices in the area that are advertising, but before shipping this list to the main application I'd like to be sure that all the devices are still reachable. What's the best way of doing this?
The vanilla Android Scan API allows for scanning BLE devices with callback types of:
/**
* A result callback is only triggered for the first advertisement packet received that matches
* the filter criteria.
*/
public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
/**
* Receive a callback when advertisements are no longer received from a device that has been
* previously reported by a first match callback.
*/
public static final int CALLBACK_TYPE_MATCH_LOST = 4;
The same API is available via RxBleClient.scanBleDevices(ScanSettings, ScanFilter...)
The CALLBACK_TYPE_FIRST_MATCH and CALLBACK_TYPE_MATCH_LOST are flags that can be put into ScanSettings.
The timeout after which the CALLBACK_TYPE_MATCH_LOST is triggered is somewhere around 10 seconds. This may be an indication that a particular device is no longer in range/available.
You can create a Transformer that will collect scanned devices and emit a list, that is kept up to date, depending on how long ago the device was recently seen.
Robert, that may not be exactly what you expect but treat it as an example. My Transformer is emitting a list of items whenever it has been changed, either because an update from the scanner or the eviction happened (checked every second).
class RollingPairableDeviceReducer(
private val systemTime: SystemTime,
private val evictionTimeSeconds: Long,
private val pairableDeviceFactory: PairableDeviceFactory
) : Observable.Transformer<ScannedDevice, List<PairableDevice>> {
override fun call(source: Observable<ScannedDevice>): Observable<List<PairableDevice>> {
val accumulator: MutableSet<PairableDevice> = Collections.synchronizedSet(mutableSetOf())
return source
.map { createPairableDevice(it) }
.map { pairableDevice ->
val added = updateOrAddDevice(accumulator, pairableDevice)
val removed = removeOldDevices(accumulator)
added || removed
}
.mergeWith(checkEvictionEverySecond(accumulator))
.filter { addedOrRemoved -> addedOrRemoved == true }
.map { accumulator.toList() }
}
private fun createPairableDevice(scannedDevice: ScannedDevice)
= pairableDeviceFactory.create(scannedDevice)
private fun updateOrAddDevice(accumulator: MutableSet<PairableDevice>, emittedItem: PairableDevice): Boolean {
val existingPairableDevice = accumulator.find { it.deviceIdentifier.hardwareId == emittedItem.deviceIdentifier.hardwareId }
return if (existingPairableDevice != null) {
accumulator.remove(existingPairableDevice)
existingPairableDevice.updateWith(emittedItem)
accumulator.add(existingPairableDevice)
false
} else {
accumulator.add(emittedItem)
true
}
}
private fun checkEvictionEverySecond(collector: MutableSet<PairableDevice>): Observable<Boolean>
= Observable.interval(1, TimeUnit.SECONDS)
.map { removeOldDevices(collector) }
private fun removeOldDevices(accumulator: MutableSet<PairableDevice>): Boolean {
val currentTimeInMillis = systemTime.currentTimeInMillis()
val evictionTimeMillis = TimeUnit.SECONDS.toMillis(evictionTimeSeconds)
return accumulator.removeAll { (currentTimeInMillis - it.lastSeenTime) >= evictionTimeMillis }
}
}

Android for work - How to check if my application is running in the work profile?

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
}
}

Categories

Resources