While making my application accessible, I have a problem - there's no way to make it SPEAK!!
By referencing google's library, I make
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event)
on my customized view and I get right event message - I checked it by using Log.d
However, there's no way to make talkback to speak...
My Application runs from API8 so I can't use also,
onPopulateAccessibilityEvent()
Am I thinking wrong? Please somebody help me...
For people looking to implement #Carter Hudson's code in Java (don't judge me cause I'm still not using Kotlin in 2019):
AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
AccessibilityEvent accessibilityEvent = AccessibilityEvent.obtain();
accessibilityEvent.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
accessibilityEvent.getText().add("Text to be spoken by TalkBack");
if (accessibilityManager != null) {
accessibilityManager.sendAccessibilityEvent(accessibilityEvent);
}
I needed to announce when a button became visible after reloading a RecyclerView's items with a new dataset. RecyclerView being a framework view, it supports talkback / accessibility out-of-the-box. After loading new data, talkback announces "showing items x through y of z" automatically. Utilizing the TTS API to solve the use case I mentioned introduces the following pitfalls:
TTS instance initialization and management is cumbersome and questionable for the following reasons:
Managing TTS instance lifecycle with onInit listener
Managing Locale settings
Managing resources via shutdown() ties you to an Activity's lifecycle per documentation
An Activity's onDestroy is not guaranteed to be called, which seems like a poor mechanism for calling shutdown() in order to deallocate TTS resources.
An easier, more maintainable solution is to play nicely with TalkBack and utilize the Accessibility API like so:
class AccessibilityHelper {
companion object {
#JvmStatic
fun announceForAccessibility(context: Context, announcement: String) {
context
.getSystemService(ACCESSIBILITY_SERVICE)
.let { it as AccessibilityManager }
.let { manager ->
AccessibilityEvent
.obtain()
.apply {
eventType = TYPE_ANNOUNCEMENT
className = context.javaClass.name
packageName = context.packageName
text.add(announcement)
}
.let {
manager.sendAccessibilityEvent(it)
}
}
}
}
}
Call the above from wherever you need (I added a method to my base activity that forwards to the helper). This will insert the announcement into the queue of messages for TalkBack to announce out loud and requires no handling of TTS instances. I ended up adding a delay parameter and mechanism into my final implementation to separate these events from ongoing ui-triggered events as they sometimes tend to override manual announcements.
Very this is tool, can use it everywhere with guard
public static void speak_loud(String str_speak) {
if (isGoogleTalkbackActive()) {
AccessibilityManager accessibilityManager = (AccessibilityManager) getDefaultContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
AccessibilityEvent accessibilityEvent = AccessibilityEvent.obtain();
accessibilityEvent.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
accessibilityEvent.getText().add(str_speak);
if (accessibilityManager != null) {
accessibilityManager.sendAccessibilityEvent(accessibilityEvent);
}
}
}
public static boolean isGoogleTalkbackActive() {
AccessibilityManager am = (AccessibilityManager) getDefaultContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (am != null && am.isEnabled()) {
List<AccessibilityServiceInfo> serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
if (!serviceInfoList.isEmpty())
return true;
}
return false;
}
If you want it to speak, use the TextToSpeech API. It takes a string and reads it outloud.
announceForAccessibility method defined in the View class probably serves the purpose here. It was introduced in API level 16. More details here.
Related
I need to check if the Android phone my app runs on is using casting which is enabled outside of my app.
It seems CastSession or SessionManager can provide the session related to my app which is not helpful for me.
For example, I can start casting with an app called xx which will cast or mirror the entire screen of my phone. Now, I need to notify when I open my app that the phone's screen is casting/mirroring so I can prevent showing specific content on my app.
I checked it with the code below:
val isCastingEnabledLiveData = MutableLiveData<Boolean>()
fun isCastingEnabled(context: Context): Boolean {
val mediaRouter = MediaRouter.getInstance(context)
if (mediaRouter.routes.size <= 1) {
isCastingEnabledLiveData.value = false
return
}
val selector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
mediaRouter.addCallback(selector, object : MediaRouter.Callback() {
override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
super.onRouteChanged(router, route)
isCastingEnabledLiveData.value = if (route != mediaRouter.defaultRoute) {
route?.connectionState != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
} else false
}
})
}
you can check whether the phone screen is casting or not by using the MediaRouter class.
Here is an example of how you could check if the phone screen is casting:
MediaRouter mediaRouter = (MediaRouter)
getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
if(route.isDefault()){
// Screen is not casting
} else {
// Screen is casting
}
This code uses the getSelectedRoute() method of the MediaRouter class to get the currently selected route. If the returned RouteInfo object is the default route, then the screen is not casting, otherwise it is.
Please note that this code uses the getSystemService(Context.MEDIA_ROUTER_SERVICE) method to get an instance of the MediaRouter class, so it should be called from an Activity or Service context.
Additionally, you could also use MediaRouter.Callback and MediaRouter.addCallback to set a callback to monitor the state of the casting, so you could get the updates on the casting state change as well.
I'm recently learning Android LiveData and its observer pattern. But I am confused by the meaning of ObserverWrapper.shouldBeActive() when reading LiveData.considerNotify():
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
I would like to know the differences between mActive and shouldBeActive(), and what is the purpose of implementing the latter function. Thank you.
I have been looking at the new methods available for Accessibility in Android O. I ran across this new method called getAccessibilityButtonController, I am unsure precisely what it does and an intended use. I know that in Android O there is a navigation button that can be used for an accessibility service. Does this accessibility button only launch the accessibility service, or could it have other functionality within the service such as to do specific tasks? I am curious possible uses for the accessibility and the getAccessibilityButtonController methods. Thank you for your time.
It can do pretty much anything you want it to. From the android accessibility doc, the button allows you to register a callback that has an onClicked method. If you enable the button and provide said callback you can execute whatever you'd like in the context of that callback.
Edit: The android documentation has been updated so the following should no longer be necessary.
Note that if you read the doc there's currently an example that has a call to getAccessibilityButtonController() within onCreate(). This is incorrect because the controller isn't valid until onServiceConnected is called. I've modified the example below to show something that should work.
private AccessibilityButtonController mAccessibilityButtonController;
private AccessibilityButtonController
.AccessibilityButtonCallback mAccessibilityButtonCallback;
private boolean mIsAccessibilityButtonAvailable;
#Override
protected void onServiceConnected() {
mAccessibilityButtonController = getAccessibilityButtonController();
mIsAccessibilityButtonAvailable =
mAccessibilityButtonController.isAccessibilityButtonAvailable();
if (!mIsAccessibilityButtonAvailable) {
return;
}
AccessibilityServiceInfo serviceInfo = getServiceInfo();
serviceInfo.flags
|= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
setServiceInfo(serviceInfo);
mAccessibilityButtonCallback =
new AccessibilityButtonController.AccessibilityButtonCallback() {
#Override
public void onClicked(AccessibilityButtonController controller) {
Log.d("MY_APP_TAG", "Accessibility button pressed!");
// Add custom logic for a service to react to the
// accessibility button being pressed.
}
#Override
public void onAvailabilityChanged(
AccessibilityButtonController controller, boolean available) {
if (controller.equals(mAccessibilityButtonController)) {
mIsAccessibilityButtonAvailable = available;
}
}
};
if (mAccessibilityButtonCallback != null) {
mAccessibilityButtonController.registerAccessibilityButtonCallback(
mAccessibilityButtonCallback, null);
}
}
How to check if any system dialog (like the one below or USSD) is displayed in Android ?
Programmatic way or cmd root way?
Any variants.
You can theoretically do this using the AccessibilityService, but it is rather complicated and may or may not work on different devices. Users will need to manually enable accessibility features for your application. You can get callbacks from Android whenever any window is opened and you can then interrogate the window to determine if it has specific text in it or belongs to a specific package, etc. This is a "brute force" approach, but it can be useful in some situations.
A system dialog is an activity. You can detect it by the top activity class name using ActivityManager.
final ActivityManager manager = (ActivityManager) context
.getSystemService(Activity.ACTIVITY_SERVICE);
In devices with API Level less than 23 (M):
final List<ActivityManager.RunningTaskInfo> runningTasks = manager.getRunningTasks(1);
final ComponentName componentName = runningTasks.get(0).topActivity;
final String className = componentName.getClassName();
if (className.equals("YOUR_EXPECTED_ACTIVITY_CLASS_NAME")) {
// do something
}
In newer devices:
final List<ActivityManager.AppTask> appTasks = manager.getAppTasks();
final ComponentName componentName = appTasks.get(0).getTaskInfo().topActivity;
final String className = componentName.getClassName();
if (className.equals("YOUR_EXPECTED_ACTIVITY_CLASS_NAME")) {
// do something
}
Or in this case, you can check if the device is in airplane mode before starting the activity:
private boolean isAirplaneModeOn(final Context context) {
final int airplaneMode = Settings.System.getInt(
context.getContentResolver(),
Settings.System.AIRPLANE_MODE_ON,
0
);
return airplaneMode != 0;
}
...
if (!isAirplaneModeOn(this)) {
// do something
}
Your question made me think of a solution in use by the permissions management in Android 6+. Have you ever seen the error message if a Toast or system alert dialog opens up when trying to set permissions?
Android "Screen Overlay Detected" message if user is trying to grant a permission when a notification is showing
The way they did it is by overriding the dispatchTouchEvent method in Activity. This can check if anything is 'in the way' intercepting touch events. You can use your special Activity as a base class for any Activity in your app that you wish to detect any overlays on it.
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
mObscured = (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0;
return super.dispatchTouchEvent(event);
}
Add a public method to check at any given time if your activity is obscured
public boolean isObscured() {
return mObscured;
}
You should be careful - as it's not clear from the question - if a second Activity from a system or privileged app is at the front of the stack then your own activity will no longer be receiving touch events. This is to capture the fragments, toasts, floating widgets and other items that may share the view hierarchy.
I have a small code sample that returns some data from api or from database, depending on with one from above contains any data.
Now I want to add another condition based on Network status. I use ReactiveNetwork library for this but I can't make it work, this is what I tried:
#Override public Observable<List<Tactic>> getTactics(boolean forceRefresh) {
Observable<List<Tactic>> diskObservable =
disk.get().getTactics().compose(RxDataUtils.applyLog(SourceType.DISK));
Observable<List<Tactic>> apiObservable = api.get()
.getTactics()
.doOnNext(tactics -> disk.get().save(tactics))
.compose(RxDataUtils.applyLog(SourceType.API));
Subscription connectivityStatusSubscription = new ReactiveNetwork().observeConnectivity(context)
.compose(RxUtils.applyStandardSchedulers())
.subscribe(connectivityStatus -> {
isNetworkAvailable = RxDataUtils.isConnected(connectivityStatus);
Timber.v("ConnectivityChanged: " + isNetworkAvailable);
});
if (isNetworkAvailable) return forceRefresh ?
apiObservable : Observable.concat(diskObservable, apiObservable).first(
tactics -> (tactics != null && tactics.size() > 0));
return diskObservable;
}
It didn't log anything at all
You should be combining the status stream with the others, not creating two separate subscriptions - otherwise it doesn't really react to the network connectivity.
What you probably want to use is switchMap. Every time the network connectivity changes it can update which stream you're using.
In broad strokes, it'd go something like this:
connectivityObservable.switchMap(isConnected -> {
if (isConnected) {
return networkObservable;
}
return diskObservable;
}
(It looks like yours would be a bit more complex, I just want to lay out the general idea here.)