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

Commit 6dcb4423 authored by Guojing Yuan's avatar Guojing Yuan
Browse files

[CDM] Fix association XML parsing issues

Bug: 417045321
Test: CTS & atest AssociationDiskStoreTest
Flag: EXEMPT bugfix
Change-Id: I1a5a9e1edbf99067ead614d2ae81c5929de7a393
parent edef3e0f
Loading
Loading
Loading
Loading
+84 −57
Original line number Diff line number Diff line
@@ -348,7 +348,7 @@ public final class AssociationDiskStore {
    }

    @NonNull
    private static Associations readAssociationsFromInputStream(@UserIdInt int userId,
    public static Associations readAssociationsFromInputStream(@UserIdInt int userId,
            @NonNull InputStream in, @NonNull String rootTag)
            throws XmlPullParserException, IOException {
        final TypedXmlPullParser parser = Xml.resolvePullParser(in);
@@ -364,10 +364,15 @@ public final class AssociationDiskStore {
            case 1:
                while (true) {
                    parser.nextTag();
                    if (isEndOfTag(parser, rootTag)) {
                        break;
                    }
                    if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
                        associations = readAssociationsV1(parser, userId);
                    } else if (isEndOfTag(parser, rootTag)) {
                        break;
                    } else {
                        Slog.e(TAG, "Unexpected tag " + parser.getName()
                                + " inside <" + rootTag + "> for user " + userId);
                        XmlUtils.skipCurrentTag(parser);
                    }
                }
                break;
@@ -382,7 +387,7 @@ public final class AssociationDiskStore {
        writeToFileSafely(file, out -> {
            final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
            serializer.startDocument(null, true);
            serializer.startDocument("UTF-8", true);
            serializer.startTag(null, XML_TAG_STATE);
            writeIntAttribute(serializer,
                    XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
@@ -492,13 +497,18 @@ public final class AssociationDiskStore {

        while (true) {
            parser.nextTag();
            if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
            if (!isStartOfTag(parser, XML_TAG_ASSOCIATION)) continue;

            if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) {
                break;
            }
            if (isStartOfTag(parser, XML_TAG_ASSOCIATION)) {
                AssociationInfo association = readAssociationV1(parser, userId);
                associations.addAssociation(association);

                maxId = Math.max(maxId, association.getId());
            } else {
                Slog.e(TAG, "Unexpected tag " + parser.getName()
                        + " inside <" + XML_TAG_ASSOCIATIONS + "> for user " + userId);
                XmlUtils.skipCurrentTag(parser);
            }
        }

        associations.setMaxId(maxId);
@@ -528,30 +538,43 @@ public final class AssociationDiskStore {
                XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
        final Icon deviceIcon = byteArrayToIcon(
                readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
        final DeviceId deviceId = readDeviceId(parser);
        final List<String> packagesToNotify = readPackagesToNotify(parser);

        // Read nested tags
        DeviceId deviceId = null;
        List<String> packagesToNotify = null;
        while (true) {
            parser.nextTag();
            if (isEndOfTag(parser, XML_TAG_ASSOCIATION)) {
                break;
            }
            if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
                deviceId = readDeviceId(parser);
            } else if (isStartOfTag(parser, XML_TAG_PACKAGES_TO_NOTIFY)) {
                packagesToNotify = readPackagesToNotify(parser);
            } else {
                Slog.e(TAG, "Unexpected tag " + parser.getName()
                        + " inside <" + XML_TAG_ASSOCIATION + "> for user " + userId);
                XmlUtils.skipCurrentTag(parser);
            }
        }

        return new AssociationInfo(associationId, userId, appPackage, macAddress, displayName,
                profile, null, selfManaged, notify, revoked, pending, timeApproved,
                lastTimeConnected, systemDataSyncFlags, deviceIcon, deviceId, packagesToNotify);
    }

    private static List<String> readPackagesToNotify(
            @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
        parser.nextTag();
        List<String> packageToNotify = null;

        if (isStartOfTag(parser, XML_TAG_PACKAGES_TO_NOTIFY) && parser.getAttributeCount() > 0) {
    private static List<String> readPackagesToNotify(@NonNull TypedXmlPullParser parser)
            throws XmlPullParserException, IOException {
        String packageNames = readStringAttribute(parser, XML_ATTR_PACKAGE_TO_NOTIFY);
            packageToNotify = deserializePackagesToNotify(packageNames);
        }

        return packageToNotify;
        // Manually move to the END tag of XML_TAG_PACKAGES_TO_NOTIFY.
        parser.nextTag();

        return deserializePackagesToNotify(packageNames);
    }

    private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
            throws XmlPullParserException, IOException {
        parser.nextTag();
        if (isStartOfTag(parser, XML_TAG_DEVICE_ID) && parser.getAttributeCount() > 0) {
        final String customDeviceId = readStringAttribute(
                parser, XML_ATTR_CUSTOM_DEVICE_ID);
        final MacAddress macAddress = stringToMacAddress(
@@ -560,11 +583,12 @@ public final class AssociationDiskStore {
        if (id == null) {
            id = generateRandom128BitKey();
        }

        // Manually move to the END tag of XML_TAG_DEVICE_ID.
        parser.nextTag();

        return new DeviceId(customDeviceId, macAddress, id);
    }
        return null;
    }

    private static void writeAssociations(@NonNull XmlSerializer parent,
            @NonNull Associations associations)
@@ -598,8 +622,12 @@ public final class AssociationDiskStore {
        writeByteArrayAttribute(
                serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));

        if (a.getDeviceId() != null) {
            writeDeviceId(serializer, a);
        }
        if (a.getPackagesToNotify() != null && !a.getPackagesToNotify().isEmpty()) {
            writePackagesToNotify(serializer, a);
        }
        serializer.endTag(null, XML_TAG_ASSOCIATION);
    }

@@ -607,16 +635,13 @@ public final class AssociationDiskStore {
            XmlSerializer parent, @NonNull AssociationInfo a) throws IOException {
        final XmlSerializer serializer = parent.startTag(null, XML_TAG_PACKAGES_TO_NOTIFY);
        String packagesToNotify = serializePackagesToNotify(a.getPackagesToNotify());
        if (!packagesToNotify.isEmpty()) {
        writeStringAttribute(serializer, XML_ATTR_PACKAGE_TO_NOTIFY, packagesToNotify);
        }
        serializer.endTag(null, XML_TAG_PACKAGES_TO_NOTIFY);
    }

    private static void writeDeviceId(XmlSerializer parent, @NonNull AssociationInfo a)
            throws IOException {
        final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
        if (a.getDeviceId() != null) {
        writeStringAttribute(
                serializer,
                XML_ATTR_CUSTOM_DEVICE_ID,
@@ -632,7 +657,6 @@ public final class AssociationDiskStore {
                XML_ATTR_KEY_DEVICE_ID,
                a.getDeviceId().getKey()
        );
        }
        serializer.endTag(null, XML_TAG_DEVICE_ID);
    }

@@ -675,6 +699,9 @@ public final class AssociationDiskStore {
    }

    private static List<String> deserializePackagesToNotify(String serializedString) {
        if (serializedString == null) {
            return null;
        }
        String[] stringArray = serializedString.split(REGEX);
        return Arrays.asList(stringArray);
    }
+1 −0
Original line number Diff line number Diff line
<state persistence-version="1">
    <associations>
        <association
            id="100001"
            profile="android.app.role.COMPANION_DEVICE_WATCH"
            package="com.example.watchapp"
            tag="my_watch_tag"
            mac_address="AA:BB:CC:DD:EE:00"
            display_name="John's Watch"
            self_managed="false"
            notify_device_nearby="true"
            revoked="false"
            time_approved="1678886400000"
            last_time_connected="1678972800000"
            system_data_sync_flags="3"/>
        <association
            id="100002"
            package="com.example.carapp"
            tag="my_car_tag"
            mac_address="11:22:33:44:55:66"
            display_name="My Car"
            self_managed="true"
            notify_device_nearby="false"
            revoked="false"
            time_approved="1678890000000"
            last_time_connected="9223372036854775807"
            system_data_sync_flags="0"/>
    </associations>
    <previously-used-ids>
        <package package_name="com.example.watchapp">
            <id>1</id>
            <id>5</id>
        </package>
        <package package_name="com.example.carapp">
            <id>2</id>
        </package>
        <package package_name="com.another.old.app">
            <id>10</id>
        </package>
    </previously-used-ids></state>
 No newline at end of file
+49 −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.server.companion.utils;

import static org.junit.Assert.assertEquals;

import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;

import androidx.test.platform.app.InstrumentationRegistry;

import com.android.frameworks.servicestests.R;
import com.android.server.companion.association.AssociationDiskStore;
import com.android.server.companion.association.Associations;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;

@Presubmit
@RunWith(AndroidTestingRunner.class)
public class AssociationDiskStoreTest {

    @Test
    public void readLegacyFileByNewLogic() throws XmlPullParserException, IOException {
        InputStream legacyXmlStream = InstrumentationRegistry.getInstrumentation().getContext()
                .getResources().openRawResource(R.raw.companion_android14_associations);

        Associations associations = AssociationDiskStore.readAssociationsFromInputStream(
                0, legacyXmlStream, "state");
        assertEquals(2, associations.getAssociations().size());
    }
}