Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7530d18 authored by Pedro Paniago's avatar Pedro Paniago
Browse files

Implement the `DisableSupervisionActivity` for disabling supervision.

This activity allows the system supervision role holder to disable supervision on the device. It will respond to `DISABLE_SUPERVISION` intent.

It may be necessary to also delete make the activity delete the PIN in a followup.

Bug: 397687772
Flag: android.app.supervision.flags.supervision_manager_apis
Test: atest DisableSupervisionActivityTest
Change-Id: I63ed5079b1e77a08c26b9a40526a8378f2453647
parent dbe0f4ca
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -2856,6 +2856,17 @@
            </intent-filter>
        </activity>

        <activity android:name=".supervision.DisableSupervisionActivity"
            android:exported="true"
            android:excludeFromRecents="true"
            android:theme="@style/Transparent"
            android:featureFlag="android.app.supervision.flags.supervision_manager_apis">
            <intent-filter>
                <action android:name="android.app.supervision.action.DISABLE_SUPERVISION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity android:name=".supervision.SetupSupervisionActivity"
            android:excludeFromRecents="true"
            android:exported="false" />
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settings.supervision

import android.app.supervision.SupervisionManager
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.FragmentActivity
import com.android.settingslib.supervision.SupervisionLog.TAG

/**
 * Activity for disabling device supervision.
 *
 * This activity is only available to the system supervision role holder. It disables device
 * supervision and finishes the activity with `Activity.RESULT_OK`.
 */
class DisableSupervisionActivity : FragmentActivity() {

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(TAG, "onCreate for DisableSupervisionActivity")

        if (!isCallerSystemSupervisionRoleHolder()) {
            Log.w(TAG, "Caller is not the system supervision role holder. Finishing activity.")
            setResult(RESULT_CANCELED)
            finish()
            return
        }

        val supervisionManager = getSystemService(SupervisionManager::class.java)
        if (supervisionManager == null) {
            Log.e(TAG, "SupervisionManager is null. Finishing activity.")
            setResult(RESULT_CANCELED)
            finish()
            return
        }

        supervisionManager.setSupervisionEnabled(false)
        setResult(RESULT_OK)
        finish()
    }

    private fun isCallerSystemSupervisionRoleHolder(): Boolean {
        return callingPackage == supervisionPackageName
    }
}
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settings.supervision

import android.app.Activity
import android.app.role.RoleManager
import android.app.supervision.SupervisionManager
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController
import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowActivity
import org.robolectric.shadows.ShadowContextImpl
import org.robolectric.shadows.ShadowRoleManager

@RunWith(RobolectricTestRunner::class)
class DisableSupervisionActivityTest {
    private val mockSupervisionManager = mock<SupervisionManager>()
    private val context = ApplicationProvider.getApplicationContext<Context>()
    private val currentUser = context.user
    private lateinit var mActivity: DisableSupervisionActivity
    private lateinit var mActivityController: ActivityController<DisableSupervisionActivity>

    private lateinit var shadowActivity: ShadowActivity

    private val callingPackage = "com.fake.caller"

    @Before
    fun setUp() {
        ShadowRoleManager.reset()

        // Note, we have to use ActivityController (instead of ActivityScenario) in order to access
        // the activity before it is created, so we can set up various mocked responses before they
        // are referenced in onCreate.
        mActivityController = Robolectric.buildActivity(DisableSupervisionActivity::class.java)
        mActivity = mActivityController.get()

        shadowActivity = shadowOf(mActivity)
        shadowActivity.setCallingPackage(callingPackage)
        Shadow.extract<ShadowContextImpl>(mActivity.baseContext).apply {
            setSystemService(Context.SUPERVISION_SERVICE, mockSupervisionManager)
        }
    }

    @Test
    fun onCreate_callerHasSupervisionRole_disablesSupervision() {
        ShadowRoleManager.addRoleHolder(
            RoleManager.ROLE_SYSTEM_SUPERVISION,
            callingPackage,
            currentUser,
        )

        mActivityController.create()

        verify(mockSupervisionManager).setSupervisionEnabled(false)
        assertThat(shadowActivity.resultCode).isEqualTo(Activity.RESULT_OK)
        assertThat(mActivity.isFinishing).isTrue()
    }

    @Test
    fun onCreate_callerWithoutSupervisionRole_doesNotDisableSupervision() {
        ShadowRoleManager.addRoleHolder(
            RoleManager.ROLE_SYSTEM_SUPERVISION,
            "com.other.package",
            currentUser,
        )

        mActivityController.create()

        verifyNoInteractions(mockSupervisionManager)
        assertThat(shadowActivity.resultCode).isEqualTo(Activity.RESULT_CANCELED)
        assertThat(mActivity.isFinishing).isTrue()
    }
}