Android: How to know when device is locked and unlocked - android

I'm developing an app which has background processes, few of the features in my app shall only work if user turn on the screen and also unlock the "Lock Screen". There are actions for SCREEN_ON, SCREEN_OFF and USER_PRESENT.
I reset a flag on SCREEN_OFF event and set it on USER_PRESENT event, this works fine but here I have another issue;
In settings there is an options "Lock instantly with power button", if it is unchecked, device will not lock until 5 seconds of sleep.
Now, SCREEN_OFF event is called and USER_PRESENT event is never called, if user turn off screen and turn it on within 5 seconds.
Is there is any action for USER_NOT_PRESENT or DEVICE_LOCKED so I can reset my flag there?
NOTE: I can work around if I know the settings "lock_screen_lock_after_timeout" and "power_button_instantly_locks". However I can get "lock_screen_lock_after_timeout" settings as follow but don't know how to get "power_button_instantly_locks"
ContentResolver mResolver = ctx.getContentResolver();
long timeout = Settings.Secure.getLong(mResolver, "lock_screen_lock_after_timeout", 0);
Thank you. Any other approach shall also be appreciated.

Use reflection to get the setting value of power_button_instantly_locks.
See the code below:
/**
*This function is used to detect the system setting: Power button instantly locks. To see whether this setting is enabled or not.
* #return true if Power button instantly locks is enabled, otherwise, false.
*/
private boolean isPowerButtonInstantlyLocksOn(){
String LOCK_PATTERN_UTILS="com.android.internal.widget.LockPatternUtils";
String POWER_BUTTON_INSTANTLY_LOCKS="getPowerButtonInstantlyLocks";
boolean isPowerButtonInstantlyLocks=false;
try{
Class<?> lockPatternUtilsClass=Class.forName(LOCK_PATTERN_UTILS);
Object lockPatternUtils=lockPatternUtilsClass.getConstructor(Context.class).newInstance(this);
// According to the source code of Android 6, the function getPowerButtonInstantlyLocks is declared with a parameter "int userId"
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
Method method = lockPatternUtilsClass.getMethod(POWER_BUTTON_INSTANTLY_LOCKS,new Class[]{int.class});
isPowerButtonInstantlyLocks=Boolean.valueOf(String.valueOf(method.invoke(lockPatternUtils,userID())));
}else {
Method method = lockPatternUtilsClass.getMethod(POWER_BUTTON_INSTANTLY_LOCKS);
isPowerButtonInstantlyLocks=Boolean.valueOf(String.valueOf(method.invoke(lockPatternUtils,null)));
}
}catch (Exception e){
Crashlytics.logException(e);
e.printStackTrace();
}
return isPowerButtonInstantlyLocks;
}
/**
* This function is used to check the current userID
* #return
*/
private int userID(){
// myUserId is a hidden function in Class UserHandle that we want to use to return the current user ID
String MY_USER_ID="myUserId";
int userId=0;
try{
// android.os.Process.myUserHandle() is called to get a UserHandle instance
// myUserHandle requires API 17, but it is OK, cause the function userID() will only be called at API 23
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.JELLY_BEAN){
UserHandle userHandle=android.os.Process.myUserHandle();
Method method = UserHandle.class.getMethod(MY_USER_ID);
userId=Integer.valueOf(String.valueOf(method.invoke(userHandle,null)));
}
}catch (Exception e){
Crashlytics.logException(e);
e.printStackTrace();
}
return userId;
}

Related

How to determine if any system dialog is displayed?

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.

Why does Android AppOps (privacy manager) send duplicate events when user change his/her privacy settings?

When a user changes his/her privacy settings through AppOps (e.g. denying an application access to phone contacts), AppOpsManager sends to anyone who listens what the users have changed (i.e. the package name and the operation (e.g. Read contacts)).
So I wrote a listener to do so. However, we the user make only one change, I receive too many duplicate events (e.g. 10 events that the user decided to deny Angry Bird access to his/her location) and then the app crashes.
Here is my code to register listners for each pair of package & operation:
public void startWatchingOperations(AppOpsManager appOps, List<AppOpsManager.PackageOps> opsforapps) {
SharedPreferences myAppListnerPreferences = getSharedPreferences(APP_OPS_PREFERENCES, Activity.MODE_PRIVATE);
for (AppOpsManager.PackageOps o:opsforapps) {
List<OpEntry> opEntry = o.getOps();
//if I already assigned a listener to this pari of package & operation, then skip
if (myAppListnerPreferences.getBoolean(o.getPackageName(), false)==false) {
for (OpEntry entry:opEntry) {
//for each pair of package & operation, assign a new listener
ChangePrivacySettingsListener opsListner = new ChangePrivacySettingsListener(getApplicationContext());
appOps.startWatchingMode(entry.getOp(),o.getPackageName(),opsListner);
}
myAppListnerPreferences.edit().putBoolean(o.getPackageName(), true).apply();
}
}
}
Here is a snippet of the listener
public class ChangePrivacySettingsListener implements AppOpsManager.Callback {
public void opChanged(int op, String packageName) {
AppOpsManager appOps= (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
PackageManager pkg = context.getPackageManager();
try {
//this is an object to store the event: package name,
// the operation that has been changed, & time stamp
PrivacySetting privacySetting = new PrivacySetting();
privacySetting.setPackageName(packageName);
privacySetting.setOperation(OPERATIONS_STRINGS[op]);
privacySetting.setDecisionTime(Calendar.getInstance(TimeZone.getDefault()).getTimeInMillis());
privacySetting.setUserId(userId);
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Her is the part of AppOpsManager.java that allows me to listen to user's changes.
public class AppOpsManager {
final HashMap<Callback, IAppOpsCallback> mModeWatchers
= new HashMap<Callback, IAppOpsCallback>();
public void startWatchingMode(int op, String packageName, final Callback callback) {
synchronized (mModeWatchers) {
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
public void opChanged(int op, String packageName) {
callback.opChanged(op, packageName);
}
};
mModeWatchers.put(callback, cb);
}
try {
mService.startWatchingMode(op, packageName, cb);
} catch (RemoteException e) {
}
}
}
I double checked to ensure that I've never assigned more than one listener to each pair of package & operation.
I would appreciate hints about potential causes.
Here is a link to AppOpsManager.java
Try moving the deceleration of ChangePrivacySettingsListener opsListner to be out side of the for block:
public void startWatchingOperations(AppOpsManager appOps, List<AppOpsManager.PackageOps> opsforapps) {
ChangePrivacySettingsListener opsListner;
SharedPreferences myAppListnerPreferences = getSharedPreferences(APP_OPS_PREFERENCES, Activity.MODE_PRIVATE);
for (AppOpsManager.PackageOps o:opsforapps) {
List<OpEntry> opEntry = o.getOps();
//if I already assigned a listener to this pari of package & operation, then skip
if (myAppListnerPreferences.getBoolean(o.getPackageName(), false)==false) {
for (OpEntry entry:opEntry) {
//for each pair of package & operation, assign a new listener
opsListner = new ChangePrivacySettingsListener(getApplicationContext());
appOps.startWatchingMode(entry.getOp(),o.getPackageName(),opsListner);
}
myAppListnerPreferences.edit().putBoolean(o.getPackageName(), true).apply();
}
}
}
And please let me know what happened?
Just in case this is helpful to someone, up to at least Android Oreo, calling AppOpsManager.startWatchingMode(op, packageName, callback) will cause callback to be invoked when the setting is changed (1) for the op with any package, AND (2) for any AppOps setting changes with packageName. This can be seen from the AppOpsService.java source, particularly AppOpsService.startWatchingMode() which registers the callback, AppOpsService.setMode() which calls the callback when the AppOps setting is changed.
For example, if you register a callback with startWatchingMode(appOps1, package1, callback) and startWatchingMode(appOps2, package1, callback),
when there is a change in the setting for appOps3 for package1, the callback will be called twice since you have registered for package1 two times. If there is a change in appOps1 for package1, the callback will be invoked 3 times, because you have registered once for appOps1, and twice for package1.
The solution is to register either for the set of AppOps you are interested in (without duplications), with the packageName parameter set to null, or register for the set of packages you are interested in, with op parameter set to AppOpsManager.OP_NONE.
Also you need to ensure that all listeners are unregistered (e.g. in onDestroy of your activity) using stopWatchingMode. Otherwise, the callback entries will accumulate across Activity lifecycles (until the app is terminated) and you will start getting duplicates. This also means that you should keep references to all the listeners created.

Listener for enable/disable mobile data (not connected or disconnnected)

I test some actions (see below).
ConnectivityManager.CONNECTIVITY_ACTION
WifiManager.NETWORK_STATE_CHANGED_ACTION
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE (it is not actually action)
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE (it is not actually action)
But they listen only state (connected or disconnected).
When wifi disconnected, It can listen (enable mobile data -> connnected -> broadcast - > listener)
When wifi copnnnected, It cannot listen (enable mobile data -> connetivity does not changed!)
I need wheather mobile data settings is enable or not
Can I listen mobile data enabled or disabled event?
While there's no broadcast by the system for this, we can actually use a ContentObserver to get notified of when the user toggles the Mobile Data setting.
e.g:
ContentObserver mObserver = new ContentObserver(new Handler()) {
#Override
public void onChange(boolean selfChange, Uri uri) {
// Retrieve mobile data value here and perform necessary actions
}
};
...
Uri mobileDataSettingUri = Settings.Secure.getUriFor("mobile_data");
getApplicationContext()
.getContentResolver()
.registerContentObserver(mobileDataSettingUri, true,
observer);
Don't forget to unregister the observer appropriately! E.g.
getContentResolver().unregisterContentObserver(mObserver);
So, after digging into it a bit, it doesn't seem like any broadcasts are sent out when that value is changed. Even the mobile network settings fragment in the Android Settings app doesn't listen for changes; it only checks in onCreate() and onResume(). So it seems you can't listen for changes, but you can get the current state. Unfortunately, it's a private API so you'll have to use reflection:
public static boolean isMobileDataEnabled(Context ctx) {
try {
Class<?> clazz = ConnectivityManager.class;
Method isEnabled = clazz.getDeclaredMethod("getMobileDataEnabled", null);
isEnabled.setAccessible(true);
ConnectivityManager mgr = (ConnectivityManager)
ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
return (Boolean) isEnabled.invoke(mgr, null);
} catch (Exception ex) {
// Handle the possible case where this method could not be invoked
return false;
}
}

Is Android system service listener localized to my app instance?

I am writing a Spell Check client using the sample code in the SDK as an example. I have the following code (not actual implementation, but an accurate sample representation):
public class HelloSpellChecker implements SpellCheckerSessionListener {
private SpellCheckerSession mSpellCheckService;
private void bindService() {
final TextServicesManager tsm = (TextServicesManager) getSystemService(
Context.TEXT_SERVICES_MANAGER_SERVICE);
mSpellCheckService = tsm.newSpellCheckerSession(null, null, this, true);
}
public void getSuggestions(String word) {
mSpellCheckService.getSuggestions(new TextInfo("tgis"), 3);
}
#Override
public void onGetSentenceSuggestions(final SentenceSuggestionsInfo[] arg0) {
Log.d(TAG, "onGetSentenceSuggestions");
// Process suggestions
}
}
What I want to know is will onGetSentenceSuggestions only be fired when my application calls getSuggestions, or will it be fired any time the system service receives a request to getSuggestions?
If it is the latter, what is the best way to ensure my app only processes suggestions which it requested?
I would say Android system service listener is localized through the session in this case.
onGetSentenceSuggestions method is fired by any request to getSuggestions method. However, you don't have to worry about processing suggestions which your app requested since the spellchecker session takes care of it. Your app only gets the suggestions requested by the session your app created to interact with the spell checker service.
Hope this helps.
References:
http://developer.android.com/reference/android/view/textservice/SpellCheckerSession.html
http://developer.android.com/guide/topics/text/spell-checker-framework.html

Freeze activity/method execution until get a response from an dialog

I am trying to implement an administration mode when the user tap 7 times on back button.
Then, A dialog message will be called with editText asking for a password. if the password matches with the one in the database, I set passwordMacthes as TRUE and return it to whatever calls the dialog setTapCount and to start administration mode activity.
However, return passordMatches always return FALSE because it finishes the execution before the dialogMessage verifies the password and be dismissed or canceled by the user.
here is my setTapCount method :
public static boolean setTapCount(Context context){
tapCount = tapCount + 1;
if(tapCount == 7){
tapCount = 0;
dialogMessage(context);
return passwordMatches;
}else{
return false;
}
}
Does anyone know how to call the line below of dialogMessage(context) only once it is finished ?
thanks
Move that line to the handler for the accept button in the dialog, and rewrite your app to be event-driven (e.g., have setTapCount() accept a result listener object that can be notified of what the user did, rather than return a boolean).

Categories

Resources