How to determine if any system dialog is displayed? - android

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.

Related

Disable split screen mode for all apps in Android

What I Want:
Disable user to use split screen mode for any application in his phone.
What I've already done:
To disable split screen mode, I need to detect which method is called and in that method I can further add a functionality to draw a custom view over it or quickly pull down split screen window.
I'm looking into AccessibilityEvents as well, might be I need to parse and filter some keywords to get to split screen detection.
So what can be that method in which Android will tell that user has just started to use split screen mode. And how can I then quickly pull down split screen window?
You can detect when any application goes to split screen mode if you have asked AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED event when registering for accessibility service.
Possible way to detect Split screen mode:
In the onAccessibilityEvent(AccessibilityEvent event) function we need to write event.getSource().getContentDescription(); and search for "Split" or "Dismiss" or other keywords in the string, depends upon various custom roms. Whenever application comes in split screen mode, its content description is set as 'Split Whatsapp' etc. That's how we can detect when any particular application comes in split screen mode.
Possible way to block usage of split screen mode for any app:
After detecting you need to add this line in order to make it impossible for the user to utilize split screen mode. It will just dock the current application window.
performGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
There are other global events as well to perform an action like:
GLOBAL_ACTION_BACK
GLOBAL_ACTION_HOME
GLOBAL_ACTION_LOCK_SCREEN
GLOBAL_ACTION_NOTIFICATIONS
GLOBAL_ACTION_POWER_DIALOG
GLOBAL_ACTION_QUICK_SETTINGS
GLOBAL_ACTION_RECENTS
GLOBAL_ACTION_TAKE_SCREENSHOT
GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN
But most suitable for this scenario is: GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN
public class AppAccessibility extends AccessibilityService {
#Override
protected void onServiceConnected() {
super.onServiceConnected();
AccessibilityServiceInfo config = new AccessibilityServiceInfo();
config.eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
if (Build.VERSION.SDK_INT >= 16) {
config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
}
setServiceInfo(config);
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event != null && event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
if (event.getSource() != null && event.getSource().getContentDescription() != null) {
if (event.getSource().getContentDescription().toString().contains("Split")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
performGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN));
}
}
}
}
}

How to trace the activities or tasks launched by me is in forground or not in Android?

I am writing an Android service to monitor the user to check if that user left the activities or applications (and their child activities) launched by the service. I tried to use Activitymanager.getRunningTasks() to get the package name and task ID of the foreground activity and check if it is the same package that my service launched. This method works if all the child activities are stay in the same task.
However, if some activities in external applications is launched as a new task, then the above method is not working.
Is there something like "task stacks" or "application stacks" for me to trace if the foreground activity is launched by me or my child activities?
The code I tried so far, periodicCheck() is called periodically:
int myTaskId = -1;
String myPackageName = "application.launched.by.me";
void periodicCheck() {
android.app.ActivityManager activityManager = (android.app.ActivityManager) SystemManagerService.this.getSystemService(Context.ACTIVITY_SERVICE);
List<android.app.ActivityManager.RunningTaskInfo> taskInfo = activityManager.getRunningTasks(10);
String currentApplicationPackageName = taskInfo.get(0).baseActivity.getPackageName();
int currentApplicationTaskId = taskInfo.get(0).id;
if (currentApplicationPackageName.equals(myPackageName) || myTaskId == currentApplicationTaskId) {
// user is staying in our target application
// update the task ID
myTaskId = currentApplicationTaskId;
} else {
// user has left our target application, do something
}
}

Is there way to make talkback speak?

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.

Android navigation button up on Jelly Bean

I developed an application that contains a homescreen with an article list.
If you click on it, you access the detail in another screen.
I implemented the ActionBarSherlock, so I used the "up" button pattern for this activity.
Then I added a widget to this application. When you click on the widget, you access directly the detail activity.
The "up" button has been implemented following the Google recommandations (http://developer.android.com/training/implementing-navigation/ancestral.html).
My problem is that on API Level 15 and below, it works perfectly. It calls the following code :
#Override
public boolean shouldUpRecreateTask(Activity activity, Intent targetIntent) {
String action = activity.getIntent().getAction();
return action != null && !action.equals(Intent.ACTION_MAIN);
}
But on JellyBean, the code used is :
public boolean shouldUpRecreateTask(Intent targetIntent) {
try {
PackageManager pm = getPackageManager();
ComponentName cn = targetIntent.getComponent();
if (cn == null) {
cn = targetIntent.resolveActivity(pm);
}
ActivityInfo info = pm.getActivityInfo(cn, 0);
if (info.taskAffinity == null) {
return false;
}
return !ActivityManagerNative.getDefault().targetTaskAffinityMatchesActivity(mToken, info.taskAffinity);
} catch (RemoteException e) {
return false;
} catch (NameNotFoundException e) {
return false;
}
}
The first part of the method retrieves information on the activity that should be loaded if stack must be recreated.
But I still don't understand what does the line :
!ActivityManagerNative.getDefault().targetTaskAffinityMatchesActivity(mToken, info.taskAffinity);
Can anyone help me on this line, I really need to find out how to obtain true by initializing everything well ?
Its a boolean method it has to return something. If it needs to return a true boolean variable for it work, you have to do so!
From the official Documentation:
Returns true if the app should recreate the task when navigating 'up'
from this activity by using targetIntent.
If this method returns false the app can trivially call navigateUpTo(Intent)
using the same parameters to correctly perform up navigation.
If this method returns false, the app should synthesize a new task stack by using
TaskStackBuilder or another similar mechanism to perform up navigation.
The affinity indicates which task an activity prefers to belong to. By default, all the activities from the same application have an affinity for each other. So, by default, all activities in the same application prefer to be in the same task. However, you can modify the default affinity for an activity. Activities defined in different applications can share an affinity, or activities defined in the same application can be assigned different task affinities.

Possible to include an Android App Widget inside an existing app (AppWidgetHost), without the Select Widgets list?

We're trying to host a 3rd party widget inside our own app, and we're trying to figure out how it can be added upon our app's installation, without having the user need to select it from the Select Widgets list. The reason being, our client needs their users to choose this specific widget (and we're trying to keep the code separate so they can update this specific widget, inside our app, on their own), and it's not a great UX for a user to be able to choose a different one by accident. Is there a way to filter the Select Widget list even? Or just have it show the one we need it to show? And since we have access to this 3rd party widget's source, would that help?
We can tell so far that the only way to add a widget to a widget host is to allocate an id, run ACTION.APPWIDGET_PICK and user can choose from there.. But we really need to find a way for either that list to be filtered somehow, or this third party widget be installed when our app (host) is installed too.
Any ideas?
Thanks!
You cannot filter the list, and you cannot install the app without user clicking it. But you can inspect the results of the user's selection and reject it if they picked the wrong Widget...
This is how you embed a widget in your app without the select widgets list. The user will just see a confirmation dialog.
public boolean createWidget(View view, String packageName, String className) {
// Get the list of installed widgets
AppWidgetProviderInfo newAppWidgetProviderInfo = null;
List<AppWidgetProviderInfo> appWidgetInfos;
appWidgetInfos = mAppWidgetManager.getInstalledProviders();
boolean widgetIsFound = false;
for(int j = 0; j < appWidgetInfos.size(); j++)
{
if (appWidgetInfos.get(j).provider.getPackageName().equals(packageName) && appWidgetInfos.get(j).provider.getClassName().equals(className))
{
// Get the full info of the required widget
newAppWidgetProviderInfo = appWidgetInfos.get(j);
widgetIsFound = true;
break;
}
}
if (!widgetIsFound) {
return false;
} else {
// Create Widget
int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
AppWidgetHostView hostView = mAppWidgetHost.createView(getApplicationContext(), appWidgetId, newAppWidgetProviderInfo);
hostView.setAppWidget(appWidgetId, newAppWidgetProviderInfo);
// Add it to your layout
LinearLayout widgetLayout = view.findViewById(R.id.widget_view);
widgetLayout.addView(hostView);
// And bind widget IDs to make them actually work
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
boolean allowed = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, newAppWidgetProviderInfo.provider);
if (!allowed) {
// Request permission - https://stackoverflow.com/a/44351320/1816603
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, newAppWidgetProviderInfo.provider);
final int REQUEST_BIND_WIDGET = 1987;
startActivityForResult(intent, REQUEST_BIND_WIDGET);
}
}
return true;
}
}

Categories

Resources