Randy
Randy

Reputation: 4381

Activity.startLockTask() occasionally throws IllegalArgumentException

I currently have a periodic issue where I get an IllegalArgumentException when I call Activity.startLockTask(). My app has a device owner app installed which has allowed my package to automatically pin itself.

The code below is checking to make sure my package can lock itself. If it can then it pins itself.

Code:

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)

The issue is my app needs to occasionally restart itself. So we unpin, finish the activity and start it again with a new task, and then exit our process. When the activity comes back up it tries to pin itself - sometimes it works - sometimes it doesn't. I believe how we restart is probably the reason the exception is thrown but it shouldn't matter since the new activity IS in the foreground and IS focused.

Once the activity fails to pin it will continue to fail as long as it tries: If I sit there and try and pin the task every 5 seconds it will continue to fail each time. I've tried pinning in onCreate, onWindowFocusChanged, onResume, and onStart.

Does anyone know what the issue might be?

For reference:
Line 8853: https://android.googlesource.com/platform/frameworks/base/+/android-5.0.2_r1/services/core/java/com/android/server/am/ActivityManagerService.java

Upvotes: 16

Views: 6827

Answers (6)

Vlad
Vlad

Reputation: 8543

I have created a separate Activity for kiosk mode

The main idea is to deliver this task from other activities and keep the lock task always in the root of stack

View.post(...) and similar with magic delay is not working for me

onResume() method with checking isFinishing() works fine, but be careful with clear task flag

<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 (true/*IT IS NEEDED*/) {
                    if (adminManager.setKioskMode(true)) {
                        // need startLockTask on resume
                        return
                    } else {
                        //toast("HAVE NO OWNER RIGHTS")
                    }
                }
                launchActivity()
                finish()
            }
            ActivityManager.LOCK_TASK_MODE_LOCKED -> {
                if (false/*IT IS NOT NEEDED*/) {
                    if (adminManager.setKioskMode(false)) {
                        stopLockTask()
                        launchActivity()
                        finish()
                        return
                    } else {
                        //toast("HAVE NO OWNER RIGHTS")
                    }
                }
                launchActivity()
            }
            else -> finishAffinity()
        }
    }

    override fun onResume() {
        super.onResume()
        if (!isFinishing) {
            if (activityManager.lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE) {
                startLockTask()
                launchActivity()
            }
        }
    }

    private fun launchActivity() {
        // todo startActivity depending on business logic
    }

    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)
            deviceManager.setKeyguardDisabled(adminComponent, enable)
            setLockTask(enable)
            return true
        }
        return false
    }

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

    /**
     * @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())
        }
    }
}

Upvotes: 0

curioustechizen
curioustechizen

Reputation: 10672

I know this is quite an old question, but I ran into it as well and here's how I solved it. The key is this sentence in the docs for startLockTask() (the same goes for stopLockTask() too)

Note: this method can only be called when the activity is foreground. That is, between onResume() and onPause()

I had some code paths that ended up trying to call startLockTask() before onResume(). I fixed it by ensuring the right activity state (using AndroidX lifecycle)

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

This was sufficient for my case. You might need to do some additional logic like this (pseudocode):

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

Upvotes: 1

Logic1
Logic1

Reputation: 1847

This is similar to @Schtibb answer, However I did not feel very comfortable with hard coding only a single 1500ms delay without any retry logic. It could still fail occasionally.

But what I found that did do the trick was to use a try when calling startLockTask(), and if it fails just catch the error and try again after a short delay until it succeeds:

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

I feel this approach is slightly more dynamic and pins the screen (almost) as soon as possible.

Upvotes: 0

liv3dota
liv3dota

Reputation: 1

The error say the app is not in foreground so i have make a loop in onStart method that check if app is in foreground

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

The function 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();
    }
}

Upvotes: 0

C. Carter
C. Carter

Reputation: 291

I had this issue and resolved it using the logic taken from the answers in this post: How can you tell when a layout has been drawn?

Basically it just ensures that the UI has been drawn first then attempts to pin.

Example Code (Put this in your onCreate method):

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

Upvotes: 3

Schtibb
Schtibb

Reputation: 161

I have the same issue, I haven't found a proper solution yet. But this is what I currently do.

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

It seems that the application requesting the lock hasn't yet received focus even though the onWindowFocusChanged is fired. By delaying the call to startLocktask by some time it will work. How ever there is a small period of time where the app won't be pinned/locked. I've solved this by some additional security measures (I have a long running service in the background that prevents notification shade pull downs and will close the settings window if opened).

Btw, did you ever manage to solve this in a adequate way?

Upvotes: 8

Related Questions