Android服务是否可以检测到设备何时被锁定?

特雷弗·威利

我有一个Android服务,想在设备锁定时采取任何措施。

我想澄清一下:

  • 我对屏幕的开/关状态不感兴趣。
  • 我知道如何使用带有Intent.ACTION_USER_PRESENT和KeyguardManager.inKeyguardRestrictedInputMode的BroadcastReceiver来检查设备何时解锁。
  • 我对需要不断检查锁定状态的解决方案不感兴趣。
  • 我不想自己锁定或解锁设备。
  • 我不能依靠处于前台的Activity来处理onResume。

我补充这些要点,是因为我发现没有人问同样的问题并得到其中的一种答复。我对他们的所作所为感到高兴,因为答案很有用和/或具有教育意义,但它们并不是我现在想要的。

特雷弗·威利

我已经提出了一个潜在的解决方案,但是随之而来的是一些主要警告。

通用方法:检测“屏幕关闭”事件,然后定期检查设备是否已锁定。这远非理想,但似乎没有任何方法可以检测设备何时被锁定。基本上,“没有正确的方法来执行此操作,因此您需要一起破解一些东西”。

致谢这是基于@Markus在评论中的建议,并结合了所链接问题的答案中的一些代码以及我自己的一些额外工作。

注意事项

  • 其他制造商可能具有不同的锁定时间。
  • 在我们先前确定设备在该期间内不会锁定(例如:设备可能突然锁定并且几分钟内无法检测到)之后,设备政策(例如:Android for Work)可能会更改为强制执行期限。
  • 设备可以被Android设备管理器远程锁定。
  • 设备可能被另一个应用程序锁定(例如:基于蓝牙的锁定机制)。
  • 未经测试,但我怀疑如果用户多次快速打开和关闭设备,此代码中会有问题。
  • 未经打Do测试。
  • 未经测试,但怀疑切换用户可能存在问题。
  • 我没有在愤怒中对此进行过测试,很可能还有其他问题。
  • 实际使用这种方法的任何人都应该做一些重新架构。下面介绍的只是概念证明。

AndroidManifest.xml

添加启动活动:

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

添加广播接收器:

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

添加主要服务:

<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>

添加权限:

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

res / values / styles.xml

添加透明样式:

<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

添加透明颜色:

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

StartLockMonitorActivity.java

这是主要的切入点,它可以启动服务:

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

设备重新启动时,这将重新启动服务。首次启动服务时,它会添加一些其他过滤器(请参阅LockMonitor.java说明中为何未在清单中进行说明的注释)。

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;
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档