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

Commit 8669a1c7 authored by Hai Zhang's avatar Hai Zhang
Browse files

Use a boolean Consumer instead of RoleManagerCallback.

And use RemoteCallback for it under the hood. This removes one AIDL
interface and allows using lambda, while aligning with what
PermissionControllerService does.

Bug: 127691087
Test: manual
Change-Id: I38de545c88467f313791c92e5393e4f41e1e3c99
parent 983fed3e
Loading
Loading
Loading
Loading
+119 −231
Original line number Diff line number Diff line
@@ -16,27 +16,22 @@

package com.android.packageinstaller.role.service;

import android.Manifest;
import android.app.AppOpsManager;
import android.app.role.RoleControllerService;
import android.app.role.RoleManager;
import android.app.role.RoleManagerCallback;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;

import com.android.packageinstaller.permission.utils.ArrayUtils;
@@ -64,204 +59,17 @@ public class RoleControllerServiceImpl extends RoleControllerService {
    private RoleManager mRoleManager;
    private AppOpsManager mAppOpsManager;

    private HandlerThread mWorkerThread;
    private Handler mWorkerHandler;

    @Override
    public void onCreate() {
        super.onCreate();

        mRoleManager = getSystemService(RoleManager.class);
        mAppOpsManager = getSystemService(AppOpsManager.class);

        mWorkerThread = new HandlerThread(RoleControllerServiceImpl.class.getSimpleName());
        mWorkerThread.start();
        mWorkerHandler = new Handler(mWorkerThread.getLooper());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        mWorkerThread.quitSafely();
    }

    @Override
    public void onGrantDefaultRoles(@NonNull RoleManagerCallback callback) {
        enforceCallerSystemUid("onGrantDefaultRoles");
        if (callback == null) {
            Log.e(LOG_TAG, "callback cannot be null");
            return;
        }
        mWorkerHandler.post(() -> grantDefaultRoles(callback));
    }

    @Override
    public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName, int flags,
            @NonNull RoleManagerCallback callback) {
        enforceCallerSystemUid("onAddRoleHolder");
        if (callback == null) {
            Log.e(LOG_TAG, "callback cannot be null");
            return;
        }
        if (TextUtils.isEmpty(roleName)) {
            Log.e(LOG_TAG, "roleName cannot be null or empty: " + roleName);
            callback.onFailure();
            return;
        }
        if (TextUtils.isEmpty(packageName)) {
            Log.e(LOG_TAG, "packageName cannot be null or empty: " + roleName);
            callback.onFailure();
            return;
        }
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            callback.onFailure();
            return;
        }
        mWorkerHandler.post(() -> addRoleHolder(roleName, packageName, flags, callback));
    }

    @Override
    public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName, int flags,
            @NonNull RoleManagerCallback callback) {
        enforceCallerSystemUid("onRemoveRoleHolder");
        if (callback == null) {
            Log.e(LOG_TAG, "callback cannot be null");
            return;
        }
        if (TextUtils.isEmpty(roleName)) {
            Log.e(LOG_TAG, "roleName cannot be null or empty: " + roleName);
            callback.onFailure();
            return;
        }
        if (TextUtils.isEmpty(packageName)) {
            Log.e(LOG_TAG, "packageName cannot be null or empty: " + roleName);
            callback.onFailure();
            return;
        }
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            callback.onFailure();
            return;
        }
        mWorkerHandler.post(() -> removeRoleHolder(roleName, packageName, flags, callback));
    }

    @Override
    public void onClearRoleHolders(@NonNull String roleName, int flags,
            @NonNull RoleManagerCallback callback) {
        enforceCallerSystemUid("onClearRoleHolders");
        if (callback == null) {
            Log.e(LOG_TAG, "callback cannot be null");
            return;
        }
        if (TextUtils.isEmpty(roleName)) {
            Log.e(LOG_TAG, "roleName cannot be null or empty: " + roleName);
            callback.onFailure();
            return;
        }
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            callback.onFailure();
            return;
        }
        mWorkerHandler.post(() -> clearRoleHolders(roleName, flags, callback));
    }

    @Override
    public void onSmsKillSwitchToggled(boolean enabled) {
        enforceCallerSystemUid("onSmsKillSwitchToggled");
        mWorkerHandler.post(() -> {
            PackageManager packageManager = getPackageManager();
            List<PackageInfo> installedPackages = getPackageManager().getInstalledPackages(0);
            for (int i = 0, size = installedPackages.size(); i < size; i++) {
                PackageInfo pkg = installedPackages.get(i);
                onSmsKillSwitchToggled(enabled, pkg, Utils.getPlatformPermissionsOfGroup(
                        packageManager, android.Manifest.permission_group.SMS));
                onSmsKillSwitchToggled(enabled, pkg, Utils.getPlatformPermissionsOfGroup(
                        packageManager, android.Manifest.permission_group.CALL_LOG));
            }

            grantDefaultRoles(null);
        });
    }

    @Override
    public boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
            @NonNull String packageName) {
        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            return false;
        }
        if (!role.isAvailable(this)) {
            return false;
        }
        return role.isPackageQualified(packageName, this);
    }

    @Override
    public boolean onIsRoleVisible(@NonNull String roleName) {
        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            return false;
        }
        if (!role.isAvailable(this)) {
            return false;
        }
        return role.isVisibleAsUser(Process.myUserHandle(), this);
    }

    private void enforceCallerSystemUid(@NonNull String methodName) {
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("Only the system process call " + methodName + "()");
        }
    }

    void onSmsKillSwitchToggled(boolean smsRestrictionEnabled, PackageInfo pkg,
            List<PermissionInfo> permissions) {
        PackageManager pm = getPackageManager();
        int uid = pkg.applicationInfo.uid;

        for (int i = 0, permissionsSize = permissions.size(); i < permissionsSize; i++) {
            PermissionInfo permission = permissions.get(i);
            try {
                int permFlags =
                        pm.getPermissionFlags(permission.name, pkg.packageName,
                                Process.myUserHandle());

                if ((permFlags
                        & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
                        | PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0) {
                    continue;
                }

                if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
                    pm.updatePermissionFlags(permission.name, pkg.packageName,
                            PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0, Process.myUserHandle());
                }

                String appOp = AppOpsManager.permissionToOp(permission.name);
                if (appOp != null) {
                    mAppOpsManager.setUidMode(appOp, uid,
                            smsRestrictionEnabled
                                    ? AppOpsManager.MODE_DEFAULT
                                    : AppOpsManager.MODE_ALLOWED);
                }

                if (!smsRestrictionEnabled
                        && pkg.applicationInfo.targetSdkVersion
                        > Build.VERSION_CODES.LOLLIPOP_MR1) {
                    pm.revokeRuntimePermission(
                            pkg.packageName, permission.name, Process.myUserHandle());
                }
            } catch (Exception e) {
                Log.e(LOG_TAG, "Unexpected exception while cleaning up state for package "
                        + pkg.packageName + " & " + permission.name, e);
                continue;
            }
        }
    }

    @WorkerThread
    private void grantDefaultRoles(@Nullable RoleManagerCallback callback) {
    public boolean onGrantDefaultRoles() {
        if (DEBUG) {
            Log.i(LOG_TAG, "Granting default roles, user: " + UserHandle.myUserId());
        }
@@ -371,9 +179,7 @@ public class RoleControllerServiceImpl extends RoleControllerService {

        updateUserSensitive();

        if (callback != null) {
            callback.onSuccess();
        }
        return true;
    }

    /**
@@ -422,26 +228,28 @@ public class RoleControllerServiceImpl extends RoleControllerService {
        }
    }

    @Override
    @WorkerThread
    private void addRoleHolder(@NonNull String roleName, @NonNull String packageName, int flags,
            @NonNull RoleManagerCallback callback) {
    public boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
            int flags) {
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            return false;
        }

        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            Log.e(LOG_TAG, "Unknown role: " + roleName);
            callback.onFailure();
            return;
            return false;
        }
        if (!role.isAvailable(this)) {
            Log.e(LOG_TAG, "Role is unavailable: " + roleName);
            callback.onFailure();
            return;
            return false;
        }

        if (!role.isPackageQualified(packageName, this)) {
            Log.e(LOG_TAG, "Package does not qualify for the role, package: " + packageName
                    + ", role: " + roleName);
            callback.onFailure();
            return;
            return false;
        }

        boolean added = false;
@@ -461,8 +269,7 @@ public class RoleControllerServiceImpl extends RoleControllerService {
                boolean removed = removeRoleHolderInternal(role, currentPackageName, false);
                if (!removed) {
                    // TODO: Clean up?
                    callback.onFailure();
                    return;
                    return false;
                }
            }
        }
@@ -470,76 +277,77 @@ public class RoleControllerServiceImpl extends RoleControllerService {
        boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);
        added = addRoleHolderInternal(role, packageName, dontKillApp, true, added);
        if (!added) {
            callback.onFailure();
            return;
            return false;
        }

        role.onHolderSelectedAsUser(this, Process.myUserHandle());

        callback.onSuccess();
        return true;
    }

    @Override
    @WorkerThread
    private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName, int flags,
            @NonNull RoleManagerCallback callback) {
    public boolean onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
            int flags) {
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            return false;
        }

        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            Log.e(LOG_TAG, "Unknown role: " + roleName);
            callback.onFailure();
            return;
            return false;
        }
        if (!role.isAvailable(this)) {
            Log.e(LOG_TAG, "Role is unavailable: " + roleName);
            callback.onFailure();
            return;
            return false;
        }

        boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);
        boolean removed = removeRoleHolderInternal(role, packageName, dontKillApp);
        if (!removed) {
            callback.onFailure();
            return;
            return false;
        }

        // TODO: Should we consider this successful regardless?
        boolean fallbackSuccessful = addFallbackRoleHolderMaybe(role);
        if (!fallbackSuccessful) {
            callback.onFailure();
            return;
            return false;
        }

        callback.onSuccess();
        return true;
    }

    @Override
    @WorkerThread
    private void clearRoleHolders(@NonNull String roleName, int flags,
            @NonNull RoleManagerCallback callback) {
    public boolean onClearRoleHolders(@NonNull String roleName, int flags) {
        if (!checkFlags(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP)) {
            return false;
        }

        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            Log.e(LOG_TAG, "Unknown role: " + roleName);
            callback.onFailure();
            return;
            return false;
        }
        if (!role.isAvailable(this)) {
            Log.e(LOG_TAG, "Role is unavailable: " + roleName);
            callback.onFailure();
            return;
            return false;
        }

        boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);
        boolean cleared = clearRoleHoldersInternal(role, dontKillApp);
        if (!cleared) {
            callback.onFailure();
            return false;
        }

        // TODO: Should we consider this successful regardless?
        boolean fallbackSuccessful = addFallbackRoleHolderMaybe(role);
        if (!fallbackSuccessful) {
            callback.onFailure();
            return;
            return false;
        }

        callback.onSuccess();
        return true;
    }

    @WorkerThread
@@ -639,6 +447,86 @@ public class RoleControllerServiceImpl extends RoleControllerService {
        return addRoleHolderInternal(role, fallbackPackageName, true);
    }

    @WorkerThread
    @Override
    public void onSmsKillSwitchToggled(boolean enabled) {
        PackageManager packageManager = getPackageManager();
        List<PackageInfo> installedPackages = getPackageManager().getInstalledPackages(0);
        for (int i = 0, size = installedPackages.size(); i < size; i++) {
            PackageInfo packageInfo = installedPackages.get(i);

            onSmsKillSwitchToggled(enabled, packageInfo, Utils.getPlatformPermissionsOfGroup(
                    packageManager, Manifest.permission_group.SMS));
            onSmsKillSwitchToggled(enabled, packageInfo, Utils.getPlatformPermissionsOfGroup(
                    packageManager, Manifest.permission_group.CALL_LOG));
        }

        onGrantDefaultRoles();
    }

    @WorkerThread
    private void onSmsKillSwitchToggled(boolean enabled, @NonNull PackageInfo packageInfo,
            @NonNull List<PermissionInfo> permissions) {
        PackageManager packageManager = getPackageManager();
        int uid = packageInfo.applicationInfo.uid;
        for (int i = 0, permissionsSize = permissions.size(); i < permissionsSize; i++) {
            PermissionInfo permission = permissions.get(i);

            try {
                int permissionFlags = packageManager.getPermissionFlags(permission.name,
                        packageInfo.packageName, Process.myUserHandle());
                if ((permissionFlags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
                        | PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0) {
                    continue;
                }
                if ((permissionFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
                    packageManager.updatePermissionFlags(permission.name, packageInfo.packageName,
                            PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0, Process.myUserHandle());
                }

                String appOp = AppOpsManager.permissionToOp(permission.name);
                if (appOp != null) {
                    mAppOpsManager.setUidMode(appOp, uid, enabled ? AppOpsManager.MODE_DEFAULT
                            : AppOpsManager.MODE_ALLOWED);
                }

                if (!enabled && packageInfo.applicationInfo.targetSdkVersion
                        >= Build.VERSION_CODES.M) {
                    packageManager.revokeRuntimePermission(packageInfo.packageName, permission.name,
                            Process.myUserHandle());
                }
            } catch (Exception e) {
                Log.e(LOG_TAG, "Unexpected exception while cleaning up state for package "
                        + packageInfo.packageName + " & " + permission.name, e);
            }
        }
    }

    @Override
    public boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
            @NonNull String packageName) {
        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            return false;
        }
        if (!role.isAvailable(this)) {
            return false;
        }
        return role.isPackageQualified(packageName, this);
    }

    @Override
    public boolean onIsRoleVisible(@NonNull String roleName) {
        Role role = Roles.get(this).get(roleName);
        if (role == null) {
            return false;
        }
        if (!role.isAvailable(this)) {
            return false;
        }
        return role.isVisibleAsUser(Process.myUserHandle(), this);
    }

    private static boolean checkFlags(int flags, int allowedFlags) {
        if ((flags & allowedFlags) != flags) {
            Log.e(LOG_TAG, "flags is invalid, flags: 0x" + Integer.toHexString(flags)
+7 −13
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.packageinstaller.role.ui;

import android.app.role.RoleManager;
import android.app.role.RoleManagerCallback;
import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
@@ -26,6 +25,7 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;

import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * {@link LiveData} for the state of managing a role holder.
@@ -71,17 +71,14 @@ public class ManageRoleHolderStateLiveData extends LiveData<Integer> {

        RoleManager roleManager = context.getSystemService(RoleManager.class);
        Executor executor = context.getMainExecutor();
        RoleManagerCallback callback = new RoleManagerCallback() {
            @Override
            public void onSuccess() {
        Consumer<Boolean> callback = successful -> {
            if (successful) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Package " + (add ? "added" : "removed")
                            + " as role holder, role: " + roleName + ", package: " + packageName);
                }
                setValue(STATE_SUCCESS);
            }
            @Override
            public void onFailure() {
            } else {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Failed to " + (add ? "add" : "remove")
                            + " package as role holder, role: " + roleName + ", package: "
@@ -120,16 +117,13 @@ public class ManageRoleHolderStateLiveData extends LiveData<Integer> {

        RoleManager roleManager = context.getSystemService(RoleManager.class);
        Executor executor = context.getMainExecutor();
        RoleManagerCallback callback = new RoleManagerCallback() {
            @Override
            public void onSuccess() {
        Consumer<Boolean> callback = successful -> {
            if (successful) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Cleared role holders, role: " + roleName);
                }
                setValue(STATE_SUCCESS);
            }
            @Override
            public void onFailure() {
            } else {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Failed to clear role holders, role: " + roleName);
                }