Android Detect Phone Lock Event

Android detect phone lock event

Have a Broadcast Receiver

android.intent.action.SCREEN_ON

and

android.intent.action.SCREEN_OFF

Related: Read CommonsWare's Answer Here.

Is there a way for an Android service to detect when the device is locked?

I've put together a potential solution, but there are some major caveats that come with it.

General approach: Detect the "screen off" event then periodically check if the device has become locked. This is far from ideal, but there does not seem to be any manner to detect when the device is locked. Basically, "there is no right way to do this so you need to hack something together".

Credit: This is based on the suggestion from @Markus in the comments combined with bits of code from the answers to the linked questions plus some extra grunt work of my own.

Caveats:

  • Other manufacturers may have different lock periods.
  • Device policy (e.g.: Android for Work) could change to enforce periods after we previously determined that the device would not lock during that period (e.g.: device could suddenly lock and we wouldn't detect for several minutes).
  • Device could be locked remotely by Android Device Manager.
  • Device could be locked by another application (e.g.: bluetooth based lock mechanism).
  • Untested but I suspect there are issues in this code if the user turns device
    on and off quickly several times.
  • Untested with Doze.
  • Untested, but suspect there might be issues around switching users.
  • I have not tested this in anger, there may well be other issues.
  • Anyone actually using this approach should probably do a bit of a re-arch; what is presented below is just a proof of concept.

AndroidManifest.xml

Add a startup activity:

<activity android:name=".StartLockMonitorActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Add a broadcast receiver:

<receiver android:name=".StateReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

Add the main service:

<service
android:name=".LockMonitor"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.sample.screenmonitor.LockMonitor.ACTION_START_SERVICE"/>
</intent-filter>
</service>

Add a permission:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

res/values/styles.xml

Add transparent style:

<style name="Theme.Transparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>

res/values/colors.xml

Add transparent colour:

<color name="transparent">#00000000</color>

StartLockMonitorActivity.java

This is the main entry point, it just kicks the service:

package com.sample.screenmonitor;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

public class StartLockMonitorActivity extends AppCompatActivity {

public static final String TAG = "LockMonitor-SLM";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "Starting service...");
final Intent newIntent = new Intent(this, LockMonitor.class);
newIntent.setAction(LockMonitor.ACTION_CHECK_LOCK);
startService(newIntent);
Toast.makeText(this, "Starting Lock Monitor Service", Toast.LENGTH_LONG).show();
finish();
}
}

StateReceiver.java

This restarts the service when the device reboots. The first time the service starts it adds some additional filters (see the comments in LockMonitor.java describing why this isn't done in the manifest).

package com.sample.screenmonitor;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class StateReceiver extends BroadcastReceiver {

public static final String TAG = "LockMonitor-SR";

@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive: redirect intent to LockMonitor");
final Intent newIntent = new Intent(context, LockMonitor.class);
newIntent.setAction(LockMonitor.ACTION_CHECK_LOCK);
newIntent.putExtra(LockMonitor.EXTRA_STATE, intent.getAction());
context.startService(newIntent);
}
}

LockMonitor.java

package com.sample.screenmonitor;

import android.app.KeyguardManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;

public class LockMonitor extends Service {

public static final String TAG = "LockMonitor";

public static final String ACTION_CHECK_LOCK = "com.sample.screenmonitor.LockMonitor.ACTION_CHECK_LOCK";
public static final String EXTRA_CHECK_LOCK_DELAY_INDEX = "com.sample.screenmonitor.LockMonitor.EXTRA_CHECK_LOCK_DELAY_INDEX";
public static final String EXTRA_STATE = "com.sample.screenmonitor.LockMonitor.EXTRA_STATE";

BroadcastReceiver receiver = null;
static final Timer timer = new Timer();
CheckLockTask checkLockTask = null;

public LockMonitor() {
Log.d(TAG, "LockMonitor constructor");
}

@Override
public void onDestroy() {
Log.d(TAG, "LM.onDestroy");
super.onDestroy();

if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "LM.onStartCommand");

if (intent != null && intent.getAction() == ACTION_CHECK_LOCK) {
checkLock(intent);
}

if (receiver == null) {
// Unlike other broad casted intents, for these you CANNOT declare them in the Android Manifest;
// instead they must be registered in an IntentFilter.
receiver = new StateReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
registerReceiver(receiver, filter);
}

return START_STICKY;
}

void checkLock(final Intent intent) {
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

final boolean isProtected = keyguardManager.isKeyguardSecure();
final boolean isLocked = keyguardManager.inKeyguardRestrictedInputMode();
final boolean isInteractive = powerManager.isInteractive();
final int delayIndex = getSafeCheckLockDelay(intent.getIntExtra(EXTRA_CHECK_LOCK_DELAY_INDEX, -1));
Log.i(TAG,
String.format("LM.checkLock with state=%s, isProtected=%b, isLocked=%b, isInteractive=%b, delay=%d",
intent != null ? intent.getStringExtra(EXTRA_STATE) : "",
isProtected, isLocked, isInteractive, checkLockDelays[delayIndex])
);

if (checkLockTask != null) {
Log.i(TAG, String.format("LM.checkLock: cancelling CheckLockTask[%x]", System.identityHashCode(checkLockTask)));
checkLockTask.cancel();
}

if (isProtected && !isLocked && !isInteractive) {
checkLockTask = new CheckLockTask(this, delayIndex);
Log.i(TAG, String.format("LM.checkLock: scheduling CheckLockTask[%x] for %d ms", System.identityHashCode(checkLockTask), checkLockDelays[delayIndex]));
timer.schedule(checkLockTask, checkLockDelays[delayIndex]);
} else {
Log.d(TAG, "LM.checkLock: no need to schedule CheckLockTask");
if (isProtected && isLocked) {
Log.e(TAG, "Do important stuff here!");
}
}
}

static final int SECOND = 1000;
static final int MINUTE = 60 * SECOND;
// This tracks the deltas between the actual options of 5s, 15s, 30s, 1m, 2m, 5m, 10m
// It also includes an initial offset and some extra times (for safety)
static final int[] checkLockDelays = new int[] { 1*SECOND, 5*SECOND, 10*SECOND, 20*SECOND, 30*SECOND, 1*MINUTE, 3*MINUTE, 5*MINUTE, 10*MINUTE, 30*MINUTE };
static int getSafeCheckLockDelay(final int delayIndex) {
final int safeDelayIndex;
if (delayIndex >= checkLockDelays.length) {
safeDelayIndex = checkLockDelays.length - 1;
} else if (delayIndex < 0) {
safeDelayIndex = 0;
} else {
safeDelayIndex = delayIndex;
}
Log.v(TAG, String.format("getSafeCheckLockDelay(%d) returns %d", delayIndex, safeDelayIndex));
return safeDelayIndex;
}

class CheckLockTask extends TimerTask {
final int delayIndex;
final Context context;
CheckLockTask(final Context context, final int delayIndex) {
this.context = context;
this.delayIndex = delayIndex;
}
@Override
public void run() {
Log.i(TAG, String.format("CLT.run [%x]: redirect intent to LockMonitor", System.identityHashCode(this)));
final Intent newIntent = new Intent(context, LockMonitor.class);
newIntent.setAction(ACTION_CHECK_LOCK);
newIntent.putExtra(EXTRA_CHECK_LOCK_DELAY_INDEX, getSafeCheckLockDelay(delayIndex + 1));
context.startService(newIntent);
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "LM.onBind");
return null;
}
}

How to detect and control the phone lock/unlock on android?

You may want to have a look on Device Policy Manager and Device Admin tutorial. You can lock the screen by a simple sample of code like:

DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);  
dpm.lockNow();

For the detection, you can follow this tutorial http://chandan-tech.blogspot.fr/2010/10/handling-screen-lock-unlock-in-android.html, it's very clear.



Related Topics



Leave a reply



Submit