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

Commit 984609dd authored by Guojing Yuan's avatar Guojing Yuan Committed by Bharath
Browse files

Fix association XML parsing issues

Bug: 417045321
Test: CTS & atest AssociationDiskStoreTest
Flag: EXEMPT bugfix
Change-Id: I1a5a9e1edbf99067ead614d2ae81c5929de7a393
(cherry picked from commit 6dcb4423)
parent 406b2d0b
Loading
Loading
Loading
Loading
+60 −37
Original line number Diff line number Diff line
@@ -332,7 +332,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);
@@ -348,10 +348,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;
@@ -366,7 +371,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);
@@ -476,13 +481,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);
@@ -512,8 +522,22 @@ public final class AssociationDiskStore {
                XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
        final Icon deviceIcon = byteArrayToIcon(
                readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));

        // Read nested tags
        DeviceId deviceId = null;
        while (true) {
            parser.nextTag();
        final DeviceId deviceId = readDeviceId(parser);
            if (isEndOfTag(parser, XML_TAG_ASSOCIATION)) {
                break;
            }
            if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
                deviceId = readDeviceId(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,
@@ -521,17 +545,16 @@ public final class AssociationDiskStore {
    }

    private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
            throws XmlPullParserException {
        if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
            throws XmlPullParserException, IOException {
        final String customDeviceId = readStringAttribute(
                parser, XML_ATTR_CUSTOM_DEVICE_ID);
        final MacAddress macAddress = stringToMacAddress(
                readStringAttribute(parser, XML_ATTR_MAC_ADDRESS_DEVICE_ID));

            return new DeviceId(customDeviceId, macAddress);
        }
        // Manually move to the END tag of XML_TAG_DEVICE_ID.
        parser.nextTag();

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

    private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -566,13 +589,14 @@ public final class AssociationDiskStore {
        writeByteArrayAttribute(
                serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));

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

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

    private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
            throws XmlPullParserException {
+42 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<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());
    }
}