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())
}
}
}