Activity.startLockTask() иногда вызывает исключение IllegalArgumentException

В настоящее время у меня есть периодическая проблема, когда я получаю IllegalArgumentException при вызове Activity.startLockTask(). В моем приложении установлено приложение владельца устройства, которое позволило моему пакету автоматически подключаться.

Ниже приведен код, удостоверяющий, что мой пакет может заблокировать себя. Если он может тогда, он сам зацикливается.

код:

if (dpm.isLockTaskPermitted(getPackageName())) {
    super.startLockTask();
}

Logcat:

java.lang.IllegalArgumentException: Invalid task, not in foreground
    at android.os.Parcel.readException(Parcel.java:1544)
    at android.os.Parcel.readException(Parcel.java:1493)
    at android.app.ActivityManagerProxy.startLockTaskMode(ActivityManagerNative.java:5223)
    at android.app.Activity.startLockTask(Activity.java:6163)

Проблема заключается в том, что мое приложение должно периодически перезапускаться. Таким образом, мы освобождаем, завершаем работу и начинаем ее с новой задачи, а затем выходим из нашего процесса. Когда активность возвращается, он пытается закрепить себя - иногда это работает - иногда это не так. Я верю, что, как мы перезапускаем, вероятно, причина, по которой выбрано исключение, но это не имеет значения, так как новая активность IS на переднем плане и сосредоточена на ОС.

Как только действие не будет выполнено, он будет продолжать сбой, пока он пытается: если я сижу там и пытаюсь установить связь каждые 5 секунд, он будет продолжать сбой каждый раз. Я попытался привязать в onCreate, onWindowFocusChanged, onResume и onStart.

Кто-нибудь знает, в чем проблема?

Для справки:
Строка 8853: https://android.googlesource.com/platform/frameworks/base/+/android-5.0.2_r1/services/core/java/com/android/server/am/ActivityManagerService.java

Ответ 1

У меня такая же проблема, я еще не нашел подходящего решения. Но это то, что я сейчас делаю.

Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        try {
            if (dpm.isLockTaskPermitted(getPackageName())) {
                super.startLockTask();
            }
        }catch (Exception exception) {
            Log.v("KioskActivity","startLockTask - Invalid task, not in foreground");
        }
    }
},1500);

Кажется, что приложение, запрашивающее блокировку, еще не получило фокуса, даже если onWindowFocusChanged уволен. Задерживая вызов startLocktask, он будет работать. Как бы там ни было небольшое время, когда приложение не будет закреплено/заблокировано. Я решил это с помощью некоторых дополнительных мер безопасности (у меня есть длинная служба в фоновом режиме, которая предотвращает выпадение оттенков уведомлений и закрывает окно настроек, если оно открыто).

Кстати, вы когда-нибудь могли решить это надлежащим образом?

Ответ 2

У меня была эта проблема и она была решена с использованием логики, взятой из ответов в этом сообщении: Как вы можете определить, когда макет был нарисован?

В основном это просто гарантирует, что пользовательский интерфейс был сначала нарисован, а затем попытается вывести его.

Пример кода (поместите это в свой метод onCreate):

> findViewById(R.id.your_view).post( new Runnable() {
>             @Override
>             public void run() {
> 
>                 // Run pinning code here
>             }
>         });

Ответ 3

Ошибка говорит, что приложение не на переднем плане, поэтому я сделал цикл в методе onStart, который проверяет, находится ли приложение на переднем плане

 boolean completed = false;
                while (!completed)
                    if (isAppInForeground(this)) {
                        startLockTask();
                        completed = true;
                    }

Функция isAppInForeground

  private boolean isAppInForeground(Context context) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
        ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
        String foregroundTaskPackageName = foregroundTaskInfo.topActivity.getPackageName();

        return foregroundTaskPackageName.toLowerCase().equals(context.getPackageName().toLowerCase());
    } else {
        ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
        ActivityManager.getMyMemoryState(appProcessInfo);
        if (appProcessInfo.importance == IMPORTANCE_FOREGROUND 
                || appProcessInfo.importance == IMPORTANCE_VISIBLE) {
            return true;
        }

        KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
        // App is foreground, but screen is locked, so show notification
        return km.inKeyguardRestrictedInputMode();
    }
}

Ответ 4

Это похоже на ответ @Schtibb, однако я не чувствовал себя очень комфортно с жестким кодированием только одной задержки 1500 мс без какой-либо логики повторения. Это могло все еще иногда терпеть неудачу.

Но то, что я обнаружил, startLockTask() сделать try при вызове startLockTask(), и, если он потерпит неудачу, просто перехватить ошибку и повторить попытку через короткую задержку, пока она не преуспеет:

void startLockTaskDelayed () {
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            // start lock task mode if its not already active
            try {
                ActivityManager am = (ActivityManager) getSystemService(
                        Context.ACTIVITY_SERVICE);

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (am.getLockTaskModeState() ==
                            ActivityManager.LOCK_TASK_MODE_NONE) {
                        startLockTask();
                    }
                }

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (!am.isInLockTaskMode()) {
                        startLockTask();
                    }
                }
            } catch(IllegalArgumentException e) {
                Log.d("SVC0", "Was not in foreground yet, try again..");
                startLockTaskDelayed();
            }
        }
    }, 10);
}

Я чувствую, что этот подход немного более динамичен и прикрепляет экран (почти) как можно скорее.

Ответ 5

Я знаю, что это довольно старый вопрос, но я столкнулся с ним, и вот как я решил его. Ключом является это предложение в документах для startLockTask() (то же самое относится и к stopLockTask())

Примечание: этот метод может быть вызван только тогда, когда действие находится на переднем плане. То есть между onResume() и onPause()

У меня было несколько путей кода, которые в итоге пытались вызвать startLockTask() до onResume(). Я исправил это, обеспечив правильное состояние активности (используя жизненный цикл AndroidX)

if(lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
    startLockTask()
}

Этого было достаточно для моего случая. Возможно, вам потребуется выполнить дополнительную логику, например, такую (псевдокод):

if(not yet resumed) {
    doWhenResumed { // Implement this as a helper function that executes when your Activity is resumed
        startLockTask()
    }
}

Ответ 6

Я создал отдельную активность для режима киоска

Основная идея заключается в том, чтобы избавить эту задачу от других действий и сохранить задачу блокировки всегда в корне стека

View.post(...) и аналогичные с магической задержкой у меня не работают

onResume() с isFinishing() работает нормально, но будьте осторожны с четкой задачей и/или с прозрачным верхом

<style name="AppTheme.Transparent" parent="android:style/Theme.Translucent.NoTitleBar.Fullscreen">
    <item name="android:colorPrimary">@color/colorPrimary</item>
    <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="android:colorAccent">@color/colorAccent</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android: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>
class LockActivity : Activity() {

    private lateinit var adminManager: AdminManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        adminManager = AdminManager(applicationContext)
    }

    override fun onStart() {
        super.onStart()
        when (activityManager.lockTaskModeState) {
            ActivityManager.LOCK_TASK_MODE_NONE -> {
                if (/*IT IS NEEDED*/) {
                    if (adminManager.setKioskMode(true)) {
                        return
                    } else {
                        toast("HAVE NO OWNER RIGHTS")
                    }
                }
            }
            ActivityManager.LOCK_TASK_MODE_LOCKED -> {
                if (/*IT IS NOT NEEDED*/) {
                    if (adminManager.setKioskMode(false)) {
                        stopLockTask()
                    } else {
                        toast("HAVE NO OWNER RIGHTS")
                    }
                }
            }
        }
        startActivity<LoginActivity>()
        finish()
    }

    override fun onResume() {
        super.onResume()
        if (!isFinishing) {
            startLockTask()
            startActivity<LoginActivity>()
        }
    }

    override fun onBackPressed() {}
}
class AdminManager(context: Context) {

    private val adminComponent = ComponentName(context, AdminReceiver::class.java)

    private val deviceManager = context.devicePolicyManager

    private val packageName = context.packageName

    @Suppress("unused")
    val isAdmin: Boolean
        get() = deviceManager.isAdminActive(adminComponent)

    val isDeviceOwner: Boolean
        get() = deviceManager.isDeviceOwnerApp(packageName)

    fun setKioskMode(enable: Boolean): Boolean {
        if (isDeviceOwner) {
            setRestrictions(enable)
            setKeyGuardEnabled(enable)
            setLockTask(enable)
            return true
        }
        return false
    }

    /**
     * @throws SecurityException if {@code admin} is not a device or profile owner.
     */
    private fun setRestrictions(disallow: Boolean) {
        arrayOf(
            UserManager.DISALLOW_FACTORY_RESET,
            UserManager.DISALLOW_SAFE_BOOT,
            UserManager.DISALLOW_USER_SWITCH,
            UserManager.DISALLOW_ADD_USER
        ).forEach {
            if (disallow) {
                deviceManager.addUserRestriction(adminComponent, it)
            } else {
                deviceManager.clearUserRestriction(adminComponent, it)
            }
        }
    }

    /**
     * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
     * secondary user that is affiliated with the device.
     */
    private fun setKeyGuardEnabled(enable: Boolean) {
        deviceManager.setKeyguardDisabled(adminComponent, !enable)
    }

    /**
     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
     * affiliated user or profile, or the profile owner when no device owner is set.
     */
    private fun setLockTask(enable: Boolean) {
        if (enable) {
            deviceManager.setLockTaskPackages(adminComponent, arrayOf(packageName))
        } else {
            deviceManager.setLockTaskPackages(adminComponent, arrayOf())
        }
    }
}