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

Commit bf0fa11b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add AppFuntionAllow List Updation." into main

parents 3bfe0cc6 60f0cc7b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.appfunctions.AppFunctionAccessServiceInterface;
import android.app.appfunctions.AppFunctionManagerConfiguration;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.permission.flags.Flags;

import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -44,6 +45,13 @@ public class AppFunctionManagerService extends SystemService {
        }
    }

    @Override
    public void onBootPhase(int phase) {
        if (Flags.appFunctionAccessServiceEnabled()) {
            mServiceImpl.onBootPhase(phase);
        }
    }

    @Override
    public void onUserUnlocked(@NonNull TargetUser user) {
        mServiceImpl.onUserUnlocked(user);
+69 −1
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SignedPackage;
import android.content.pm.SigningInfo;
import android.os.Binder;
import android.os.CancellationSignal;
@@ -69,6 +70,8 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.permission.flags.Flags;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -77,12 +80,15 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
@@ -92,6 +98,9 @@ import java.util.concurrent.Executor;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    private static final String TAG = AppFunctionManagerServiceImpl.class.getSimpleName();
    private static final String ALLOWLISTED_APP_FUNCTIONS_AGENTS =
            "allowlisted_app_functions_agents";
    private static final String NAMESPACE_MACHINE_LEARNING = "machine_learning";

    private final RemoteServiceCaller<IAppFunctionService> mRemoteServiceCaller;
    private final CallerValidator mCallerValidator;
@@ -144,7 +153,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    /** Called when the user is unlocked. */
    public void onUserUnlocked(TargetUser user) {
        Objects.requireNonNull(user);

        registerAppSearchObserver(user);
        trySyncRuntimeMetadata(user);
        PackageMonitor pkgMonitorForUser =
@@ -191,6 +199,66 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                .exec(this, in, out, err, args, callback, resultReceiver);
    }

    private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
            new DeviceConfig.OnPropertiesChangedListener() {

                @Override
                public void onPropertiesChanged(DeviceConfig.Properties properties) {
                    if (Flags.appFunctionAccessServiceEnabled()) {
                        if (properties.getKeyset().contains(ALLOWLISTED_APP_FUNCTIONS_AGENTS)) {
                            final String signaturesString =
                                    properties.getString(ALLOWLISTED_APP_FUNCTIONS_AGENTS, "");
                            Slog.d(TAG, "onPropertiesChanged signatureString " + signaturesString);
                            try {
                                final List<SignedPackage> allowedSignedPackages =
                                        SignedPackageParser.parseList(signaturesString);
                                // TODO(b/416661798): Calls new
                                // AppFunctionAccessService#updateAgentAllowlist API to update
                                // the allowlist
                            } catch (Exception e) {
                                Slog.e(
                                        TAG,
                                        "Cannot parse signature string: " + signaturesString,
                                        e);
                            }
                        }
                    }
                }
            };

    /**
     * Called during different phases of the system boot process.
     *
     * <p>This method is used to initialize AppFunctionManagerService components that depend on
     * other system services being ready. Specifically, it handles reading DeviceConfig properties
     * related to allowed agent package signatures and registers a listener for changes to these
     * properties.
     *
     * @param phase The current boot phase, as defined in {@link SystemService}. This method
     *     specifically acts on {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
     */
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            final String signatureString =
                    DeviceConfig.getString(
                            NAMESPACE_MACHINE_LEARNING, ALLOWLISTED_APP_FUNCTIONS_AGENTS, "");
            try {
                final List<SignedPackage> allowedSignedPackages =
                        SignedPackageParser.parseList(signatureString);

                // TODO(b/416661798): Similar to the callback, update the allowlist with
                // AppFunctionAccessService#updateAgentAllowlist API.

            } catch (Exception e) {
                Slog.e(TAG, "Cannot parse signature string: " + signatureString, e);
            }
            DeviceConfig.addOnPropertiesChangedListener(
                    NAMESPACE_MACHINE_LEARNING,
                    BackgroundThread.getExecutor(),
                    mDeviceConfigListener);
        }
    }

    @Override
    public ICancellationSignal executeAppFunction(
            @NonNull ExecuteAppFunctionAidlRequest requestInternal,
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.appfunctions;

import android.annotation.NonNull;
import android.content.pm.Signature;
import android.content.pm.SignedPackage;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;

public class SignedPackageParser {
    private static final String SIGNED_PACKAGE_SEPARATOR = ";";
    private static final String CERTIFICATE_SEPARATOR = ":";
    private static final String TAG = SignedPackageParser.class.getSimpleName();

    /**
     * Parse a {@link SignedPackage} from a string input.
     *
     * @param input the package name, optionally followed by a colon and a signing certificate
     *     digest
     * @return the parsed {@link SignedPackage}, or {@code null} if the input is invalid
     */
    private static SignedPackage parse(@NonNull String input) {
        String packageName;
        byte[] certificate;
        int certificateSeparatorIndex = input.indexOf(CERTIFICATE_SEPARATOR);
        if (certificateSeparatorIndex != -1) {
            packageName = input.substring(0, certificateSeparatorIndex);
            String certificateString = input.substring(certificateSeparatorIndex + 1);
            try {
                certificate = new Signature(certificateString).toByteArray();
            } catch (IllegalArgumentException e) {
                Slog.w(TAG, "Cannot parse the signed package input of: " + input, e);
                return null;
            }
        } else {
            packageName = input;
            certificate = null;
        }
        return new SignedPackage(packageName, certificate);
    }

    /**
     * Parse a list of {@link SignedPackage}s from a string input.
     *
     * @param input the package names, each optionally followed by a colon and a signing certificate
     *     digest
     * @return the parsed list of valid {@link SignedPackage}s
     */
    @NonNull
    public static List<SignedPackage> parseList(@NonNull String input) throws Exception {
        List<SignedPackage> signedPackages = new ArrayList<>();
        if (input.isEmpty()) {
            return signedPackages;
        }

        for (String signedPackageInput : input.split(SIGNED_PACKAGE_SEPARATOR)) {
            SignedPackage signedPackage = parse(signedPackageInput);
            if (signedPackage == null) {
                throw new IllegalArgumentException(
                        "Cannot parse the signed package input of: " + signedPackageInput);
            } else {
                signedPackages.add(signedPackage);
            }
        }
        return signedPackages;
    }
}
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.appfunctions

import android.content.pm.Signature
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class SignedPackageParserTest {

    @Test
    fun parseList_emptyInput_returnsEmptyList() {
        val input = ""

        val result = SignedPackageParser.parseList(input)

        assertThat(result).isEmpty()
    }

    @Test
    fun parseList_singlePackageName_returnsCorrectList() {
        val input = TEST_PACKAGE_NAME_1

        val result = SignedPackageParser.parseList(input)

        assertThat(result).hasSize(1)
        assertThat(result[0].packageName).isEqualTo(TEST_PACKAGE_NAME_1)
        assertThat(result[0].certificateDigest).isNull()
    }

    @Test
    fun parseList_singlePackageNameWithCertificate_returnsCorrectList() {
        val input = "$TEST_PACKAGE_NAME_1:$TEST_CERTIFICATE_STRING_1"

        val result = SignedPackageParser.parseList(input)

        assertThat(result).hasSize(1)
        assertThat(result[0].packageName).isEqualTo(TEST_PACKAGE_NAME_1)
        assertThat(result[0].certificateDigest).isEqualTo(TEST_CERTIFICATE_DIGEST_1)
    }

    @Test
    fun parseList_multiplePackages_returnsCorrectList() {
        val input = "$TEST_PACKAGE_NAME_1:$TEST_CERTIFICATE_STRING_1;$TEST_PACKAGE_NAME_2"

        val result = SignedPackageParser.parseList(input)

        assertThat(result).hasSize(2)
        assertThat(result[0].packageName).isEqualTo(TEST_PACKAGE_NAME_1)
        assertThat(result[0].certificateDigest).isEqualTo(TEST_CERTIFICATE_DIGEST_1)
        assertThat(result[1].packageName).isEqualTo(TEST_PACKAGE_NAME_2)
        assertThat(result[1].certificateDigest).isNull()
    }

    @Test
    fun parseList_multiplePackagesWithCertificates_returnsCorrectList() {
        val input =
            "$TEST_PACKAGE_NAME_1:$TEST_CERTIFICATE_STRING_1;" +
                "$TEST_PACKAGE_NAME_2:$TEST_CERTIFICATE_STRING_2"

        val result = SignedPackageParser.parseList(input)

        assertThat(result).hasSize(2)
        assertThat(result[0].packageName).isEqualTo(TEST_PACKAGE_NAME_1)
        assertThat(result[0].certificateDigest).isEqualTo(TEST_CERTIFICATE_DIGEST_1)
        assertThat(result[1].packageName).isEqualTo(TEST_PACKAGE_NAME_2)
        assertThat(result[1].certificateDigest).isEqualTo(TEST_CERTIFICATE_DIGEST_2)
    }

    @Test
    fun parseList_invalidCertificate_throwsException() {
        val input =
            "$TEST_PACKAGE_NAME_1:$INVALID_CERTIFICATE_STRING;" +
                "$TEST_PACKAGE_NAME_2:$TEST_CERTIFICATE_STRING_2"

        assertFailsWith<IllegalArgumentException> { SignedPackageParser.parseList(input) }
    }

    @Test
    fun parseList_trailingSeparator_handlesPotentialEmptyLastSegment() {
        val input = "$TEST_PACKAGE_NAME_1:$TEST_CERTIFICATE_STRING_1;"

        val result = SignedPackageParser.parseList(input)

        assertThat(result).hasSize(1)
        assertThat(result[0].packageName).isEqualTo(TEST_PACKAGE_NAME_1)
        assertThat(result[0].certificateDigest).isEqualTo(TEST_CERTIFICATE_DIGEST_1)
    }

    @Test
    fun parseList_onlySeparators_returnsEmptyList() {
        val input = ";;"

        val result = SignedPackageParser.parseList(input)

        assertThat(result).isEmpty()
    }

    private companion object {
        const val TEST_PACKAGE_NAME_1 = "com.example.test1"
        const val TEST_PACKAGE_NAME_2 = "com.example.test2"
        const val TEST_CERTIFICATE_STRING_1 = "abcdef0123456789"
        val TEST_CERTIFICATE_DIGEST_1 = Signature(TEST_CERTIFICATE_STRING_1).toByteArray()
        const val TEST_CERTIFICATE_STRING_2 = "9876543210fedcba"
        val TEST_CERTIFICATE_DIGEST_2 = Signature(TEST_CERTIFICATE_STRING_2).toByteArray()

        const val INVALID_CERTIFICATE_STRING = "invalid_certificate_string"
    }
}