Check if screen is in MultiWindowMode without activity - android

Is there any way to find out if screen is split if I have no access to Activity? (Structurally I can't call isInMultiWindowMode method.
I see that default Activity#isInMultiWindowMode() implementation is:
public boolean isInMultiWindowMode() {
try {
return ActivityManagerNative.getDefault().isInMultiWindowMode(mToken);
} catch (RemoteException e) {
}
return false;
}
Is there any workaround ?

I think the only way to do this without an Activity is by using an AccessibilityService that has the permissions to get the list of windows currently displayed and check if there's a window whose type is AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER.
For example, you could have the following method to do so:
private boolean inSplitScreenMode(List<AccessibilityWindowInfo> windows) {
for (AccessibilityWindowInfo window : windows) {
if (window.getType() == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER) {
return true;
}
}
return false;
}
check this method when receiving window state changed accessibility events
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if ((event.getEventType() & AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) != 0) {
if (inSplitScreenMode(getWindows()) {
Log.d(TAG, "Split screen mode detected");
} else {
Log.d(TAG, "No split screen");
}
}
}

Inside Fragment you can use
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (((Activity)getContext()).isInMultiWindowMode()){
// ...
}
}

Related

Disable status bar pull in Oreo

The method for kiosking an application by disabling pull and click of the status bar does not work on android 8. As anserwed on How to disable status bar click and pull down in Android?
You can lay a window over the status bar to disable any touch or pulling down.
As described by this answer, this method of doing it does works on android 7 and below however this method does not work on android 8(oreo).
I have tested it on android 7 and less and it works, but the status bar still pulls down when pulled on android 8.
If you have a solution on this please assist.
Thank you all.
For and 8 and above you cant realy fully overylay a view over other apps, so what you have to do is, when you pull the drawer down, return the drawer back up so fast that the user wont be able to click anything on it. This is the code that does that. Make sure you are doing this on an activity.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!hasFocus) {
Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
sendBroadcast(closeDialog);
// Method that handles loss of window focus
new BlockStatusBar(this,false).collapseNow();
}
}
}
Then the helper class that is doing the job of hiding the status bar is as indicated below.
public class BlockStatusBar {
Context context;
// To keep track of activity's window focus
boolean currentFocus;
// To keep track of activity's foreground/background status
boolean isPaused;
public static Handler collapseNotificationHandler;
Method collapseStatusBar = null;
public BlockStatusBar(Context context,boolean isPaused) {
this.context=context;
this.isPaused=isPaused;
collapseNow();
}
public void collapseNow() {
// Initialize 'collapseNotificationHandler'
if (collapseNotificationHandler == null) {
collapseNotificationHandler = new Handler();
}
// If window focus has been lost && activity is not in a paused state
// Its a valid check because showing of notification panel
// steals the focus from current activity's window, but does not
// 'pause' the activity
if (!currentFocus && !isPaused) {
// Post a Runnable with some delay - currently set to 300 ms
collapseNotificationHandler.postDelayed(new Runnable() {
#Override
public void run() {
// Use reflection to trigger a method from 'StatusBarManager'
Object statusBarService = context.getSystemService("statusbar");
Class<?> statusBarManager = null;
try {
statusBarManager = Class.forName("android.app.StatusBarManager");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
// Prior to API 17, the method to call is 'collapse()'
// API 17 onwards, the method to call is `collapsePanels()`
if (Build.VERSION.SDK_INT > 16) {
collapseStatusBar = statusBarManager .getMethod("collapsePanels");
} else {
collapseStatusBar = statusBarManager .getMethod("collapse");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
collapseStatusBar.setAccessible(true);
try {
collapseStatusBar.invoke(statusBarService);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// Check if the window focus has been returned
// If it hasn't been returned, post this Runnable again
// Currently, the delay is 100 ms. You can change this
// value to suit your needs.
if (!currentFocus && !isPaused) {
collapseNotificationHandler.postDelayed(this, 100L);
}
if (!currentFocus && isPaused) {
collapseNotificationHandler.removeCallbacksAndMessages(null);
}
}
}, 1L);
}
}
}

Force stop android applications

After opening application details settings using
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS), how can I force stop application programmatically?
You can use Accessibility to achieve that (but it needs Accessibility for your app turned on by user)
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//TYPE_WINDOW_STATE_CHANGED == 32
if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event
.getEventType()) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> list = nodeInfo
.findAccessibilityNodeInfosByViewId("com.android.settings:id/left_button");
//We can find button using button name or button id
for (AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
list = nodeInfo
.findAccessibilityNodeInfosByViewId("android:id/button1");
for (AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
#Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
}
You can check it out in this example:
AccessibilityTestService.java
You have two ways, a more rude one and a better one
The good practice
If you have only one activity running
the this.finish(); method will be enough
If you have multiple activities running
You gotta call the this.finishAffinity(); method. This is the best practice in general cases, where you can have both a single or multiple activities
The rude way
System.Exit(0);
I added this only for info, but this might not work with multiple activities and this is not a good way for closing apps. It's mostly like the "Hold power button until the pc shuts down".
Clicking an element of another application on runtime is something that will be considered as a security threat. You would need a hack to go past this hurdle.
There is one hack that I recently found out, you can probably make use of it. You can find the source code here: https://github.com/tfKamran/android-ui-automator
You can add the code in here as a module in your app and invoke a service with action com.tf.uiautomator.ACTION_CLICK_ITEM and send the text of the element you want to click on as an extra with key itemText.
You can test it using adb like:
adb shell am startservice -a com.tf.uiautomator.ACTION_CLICK_ITEM -e itemText "OK"
I found one solution for force stop. After force stop how can i go back to my activity page ?
public class DeviceAccessibilityService extends AccessibilityService {
private static final String TAG = "litan";
private boolean isKilled = false;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
isKilled = false;
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()) {
AccessibilityNodeInfo nodeInfo = event.getSource();
Log.i(TAG, "ACC::onAccessibilityEvent: nodeInfo=" + nodeInfo);
if (nodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> list = new ArrayList<>();
if ("com.android.settings.applications.InstalledAppDetailsTop".equals(event.getClassName())) {
if (Build.VERSION.SDK_INT >= 18) {
list = nodeInfo.findAccessibilityNodeInfosByViewId("com.android.settings:id/right_button");
} else if (Build.VERSION.SDK_INT >= 14) {
list = nodeInfo.findAccessibilityNodeInfosByText("com.android.settings:id/right_button");
}
for (AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: left_button " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
} else if ("android.app.AlertDialog".equals(event.getClassName())) {
list = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 18) {
list = nodeInfo.findAccessibilityNodeInfosByViewId("android:id/button1");
} else if (Build.VERSION.SDK_INT >= 14) {
list = nodeInfo.findAccessibilityNodeInfosByText("android:id/button1");
}
for (final AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: button1 " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
return;
}
}
#Override
public void onInterrupt() {
// TODO Auto-generated method stub
Log.i("Interrupt", "Interrupt");
}
#Override
protected void onServiceConnected() {
AccessibilityServiceInfo info = getServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_WINDOWS_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
info.flags = AccessibilityServiceInfo.DEFAULT;
info.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
info.flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
info.flags = AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
info.flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
// We are keeping the timeout to 0 as we don’t need any delay or to pause our accessibility events
info.feedbackType = AccessibilityEvent.TYPES_ALL_MASK;
info.notificationTimeout = 100;
this.setServiceInfo(info);
// Toast.makeText(getApplicationContext(), "onServiceConnected", Toast.LENGTH_SHORT).show();
}
private static void logd(String msg) {
Log.d(TAG, msg);
}
private static void logw(String msg) {
Log.w(TAG, msg);
}
private static void logi(String msg) {
Log.i(TAG, msg);
}
}

how to build a inputConnection between edit text that inside the input method service and the service

I am writing an IME (InputMethodService) and I have an edit text that in the IME itself, but when I want type text into the edit text , the edit text can't get focused and what I type just go to another edit text outside the IME. how can I make the edit text inside the IME work like normal edit text
The way I solved this was by keeping two input connections, one to the original textfield that the user WAS editing, and another to the textfield inside the keyboard. More on the second one later. The first one you should already have, it's where the keyboard sends all of its commands. In my case it was called mIC. So I created another InputConnection variable called mOtherIC on my class that had the InputConnection, and introduced a new method called getIC() which just returned the appropriate one of those two InputConnections to be using at the time (controlled by a boolean in my case, true meant use the textfield in the keyboard, false meant use the textfield that originally presented the keyboard). Every time I had previously accessed mIC, I replaced it with getIC() so it would seamlessly be able to send input to the correct input connection.
The InputConnection for the textfield in the keyboard is the interesting one here, I essentially had to copy the internal Android EditableInputConnection into my own CustomInputConnection class:
public class CustomInputConnection extends BaseInputConnection {
private static final boolean DEBUG = false;
private static final String TAG = "CustomInputConnection";
private final TextView mTextView;
// Keeps track of nested begin/end batch edit to ensure this connection always has a
// balanced impact on its associated TextView.
// A negative value means that this connection has been finished by the InputMethodManager.
private int mBatchEditNesting;
public CustomInputConnection(TextView textview) {
super(textview, true);
mTextView = textview;
}
#Override
public Editable getEditable() {
TextView tv = mTextView;
if (tv != null) {
return tv.getEditableText();
}
return null;
}
#Override
public boolean beginBatchEdit() {
synchronized(this) {
if (mBatchEditNesting >= 0) {
mTextView.beginBatchEdit();
mBatchEditNesting++;
return true;
}
}
return false;
}
#Override
public boolean endBatchEdit() {
synchronized(this) {
if (mBatchEditNesting > 0) {
// When the connection is reset by the InputMethodManager and reportFinish
// is called, some endBatchEdit calls may still be asynchronously received from the
// IME. Do not take these into account, thus ensuring that this IC's final
// contribution to mTextView's nested batch edit count is zero.
mTextView.endBatchEdit();
mBatchEditNesting--;
return true;
}
}
return false;
}
// #Override
// protected void reportFinish() {
// super.reportFinish();
//
// synchronized(this) {
// while (mBatchEditNesting > 0) {
// endBatchEdit();
// }
// // Will prevent any further calls to begin or endBatchEdit
// mBatchEditNesting = -1;
// }
// }
#Override
public boolean clearMetaKeyStates(int states) {
final Editable content = getEditable();
if (content == null) return false;
KeyListener kl = mTextView.getKeyListener();
if (kl != null) {
try {
kl.clearMetaKeyState(mTextView, content, states);
} catch (AbstractMethodError e) {
// This is an old listener that doesn't implement the
// new method.
}
}
return true;
}
#Override
public boolean commitCompletion(CompletionInfo text) {
if (DEBUG) Log.v(TAG, "commitCompletion " + text);
mTextView.beginBatchEdit();
mTextView.onCommitCompletion(text);
mTextView.endBatchEdit();
return true;
}
/**
* Calls the {#link TextView#onCommitCorrection} method of the associated TextView.
*/
#Override
public boolean commitCorrection(CorrectionInfo correctionInfo) {
if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
mTextView.beginBatchEdit();
mTextView.onCommitCorrection(correctionInfo);
mTextView.endBatchEdit();
return true;
}
#Override
public boolean performEditorAction(int actionCode) {
if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
mTextView.onEditorAction(actionCode);
return true;
}
#Override
public boolean performContextMenuAction(int id) {
if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
mTextView.beginBatchEdit();
mTextView.onTextContextMenuItem(id);
mTextView.endBatchEdit();
return true;
}
#Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
if (mTextView != null) {
ExtractedText et = new ExtractedText();
if (mTextView.extractText(request, et)) {
if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
// mTextView.setExtracting(request);
}
return et;
}
}
return null;
}
#Override
public boolean performPrivateCommand(String action, Bundle data) {
mTextView.onPrivateIMECommand(action, data);
return true;
}
#Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (mTextView == null) {
return super.commitText(text, newCursorPosition);
}
if (text instanceof Spanned) {
Spanned spanned = ((Spanned) text);
SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
// mIMM.registerSuggestionSpansForNotification(spans);
}
// mTextView.resetErrorChangedFlag();
boolean success = super.commitText(text, newCursorPosition);
// mTextView.hideErrorIfUnchanged();
return success;
}
#Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
// It is possible that any other bit is used as a valid flag in a future release.
// We should reject the entire request in such a case.
final int KNOWN_FLAGS_MASK = InputConnection.CURSOR_UPDATE_IMMEDIATE |
InputConnection.CURSOR_UPDATE_MONITOR;
final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK;
if (unknownFlags != 0) {
if (DEBUG) {
Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." +
" cursorUpdateMode=" + cursorUpdateMode +
" unknownFlags=" + unknownFlags);
}
return false;
}
return false;
// if (mIMM == null) {
// // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
// // TODO: Return some notification code rather than false to indicate method that
// // CursorAnchorInfo is temporarily unavailable.
// return false;
// }
// mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
// if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
// if (mTextView == null) {
// // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
// // TODO: Return some notification code for the input method that indicates
// // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
// } else if (mTextView.isInLayout()) {
// // In this case, the view hierarchy is currently undergoing a layout pass.
// // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
// // pass is finished.
// } else {
// // This will schedule a layout pass of the view tree, and the layout event
// // eventually triggers IMM#updateCursorAnchorInfo.
// mTextView.requestLayout();
// }
// }
// return true;
}
}
The class looks pretty bad because I commented out code that wouldn't build, presumably because it accesses internal Android APIs. That said, with a few caveats (cursor probably needs to be set manually when you switch to this input method, capitalization kind of carries-over between the two InputConnections), it works. All you have to do now is at some point set mOtherIC from my first paragraph to a new CustomInputConnection(yourTextfield) where yourTextfield is the textfield inside the keyboard.

Is there an Application level OnStop method, rather than just the Activity level?

I want set my current user to not authenticated every time the phone goes to sleep or the app is switched off (i.e. either goes to the desktop or another app), so that they always have to authenticate when the app comes back on again.
I don't want to do this in the OnStop or OnPause methods in each activity, only when the app isn't currently active.
Ideally there would be an OnStop method in the Application base object or some other global context, similar to this:
public class MyApp : Application
{
public override void OnCreate()
{
base.OnCreate();
}
}
but unfortunately this doesn't exist. Is this possible?
It turns out there isn't. The solution was to test in an inactivity timer, e.g.:
private void InactivityTimer_Elapsed(object sender, ElapsedEventArgs e)
{
_secondsElapsed += 1;
if (_screenEventReceiver.IsScreenOff || IsApplicationSentToBackground(this.ApplicationContext))
{
// do things that you would OnStop here
}
}
public static bool IsApplicationSentToBackground(Context context)
{
try
{
var am = (ActivityManager)Context.GetSystemService(Context.ActivityService);
var tasks = am.GetRunningTasks(1);
if (tasks.Count > 0)
{
var topActivity = tasks[0].TopActivity;
if (topActivity.PackageName != context.PackageName)
{
return true;
}
}
}
catch (System.Exception ex)
{
Errors.Handle(Context, ex);
throw;
}
return false;
}
private class ScreenEventReceiver : BroadcastReceiver
{
public bool IsScreenOff { get; private set; }
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == Intent.ActionScreenOff)
{
IsScreenOff = true;
}
else
{
IsScreenOff = true;
}
}
}

How to check programmatically if data roaming is enabled/disabled?

I'm trying to check if the user has enabled/disabled data roaming. All I found so far is that you can check whether or not the user is currently IN roaming, using TelephonyManager.isNetworkRoaming() and NetworkInfo.isRoaming(), but they are not what I need.
Based on Nippey's answer, the actual piece of code that worked for me is:
public Boolean isDataRoamingEnabled(Context context) {
try {
// return true or false if data roaming is enabled or not
return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.DATA_ROAMING) == 1;
}
catch (SettingNotFoundException e) {
// return null if no such settings exist (device with no radio data ?)
return null;
}
}
You can request the state of the Roaming-Switch via
ContentResolver cr = ContentResolver(getCurrentContext());
Settings.Secure.getInt(cr, Settings.Secure.DATA_ROAMING);
See: http://developer.android.com/reference/android/provider/Settings.Secure.html#DATA_ROAMING
public static final Boolean isDataRoamingEnabled(final Context application_context)
{
try
{
if (VERSION.SDK_INT < 17)
{
return (Settings.System.getInt(application_context.getContentResolver(), Settings.Secure.DATA_ROAMING, 0) == 1);
}
return (Settings.Global.getInt(application_context.getContentResolver(), Settings.Global.DATA_ROAMING, 0) == 1);
}
catch (Exception exception)
{
return false;
}
}
Updated function to account for API deprecation. It is now replaced with:
http://developer.android.com/reference/android/provider/Settings.Global.html#DATA_ROAMING
public static boolean IsDataRoamingEnabled(Context context) {
try {
// return true or false if data roaming is enabled or not
return Settings.Global.getInt(context.getContentResolver(), Settings.Global.DATA_ROAMING) == 1;
}
catch (SettingNotFoundException e) {
return false;
}
}

Categories

Resources