Added transparency-metadata display.

This feature enables 'App Details' to support MBAs with metadata file and display in an offline UI. This change includes a new fragment and user view that sources data from within any MBAs apk file that contains application-metadata.xml.

Test: make -j64 RunSettingsRoboTests , AVD manual test
Change-Id: If7822100a90f5fb8d33ce5d85958391fc33ecbee
Bug: 244215932
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6c0dbff..ec5e227 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4605,6 +4605,17 @@
             android:exported="false">
         </activity>
 
+        <activity
+            android:name="com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity"
+            android:label="@string/mobile_bundled_apps_details_title"
+            android:exported="true"
+            android:icon="@drawable/ic_homepage_connected_device">
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.TRANSPARENCY_METADATA" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <!-- This is the longest AndroidManifest.xml ever. -->
     </application>
 </manifest>
diff --git a/res/layout/mobile_bundled_apps_details_fragment.xml b/res/layout/mobile_bundled_apps_details_fragment.xml
new file mode 100644
index 0000000..b2b8a23
--- /dev/null
+++ b/res/layout/mobile_bundled_apps_details_fragment.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_details_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="10dp">
+
+    <TextView
+        android:id="@+id/contains_ads_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_contains_ad_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <TextView
+        android:id="@+id/contains_ads"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/developer_info_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_developer_info_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <LinearLayout
+        android:id="@+id/developer_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
+
+    <TextView
+        android:id="@+id/contact_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_contact_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <TextView
+        android:id="@+id/contact_url"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+    <TextView
+        android:id="@+id/contact_email"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/description_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_description_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/privacy_policy_url_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_privacy_policy_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <TextView
+        android:id="@+id/privacy_policy_url"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/category_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/mobile_bundled_apps_details_category_title"
+        android:textSize="18sp"
+        android:textStyle="bold" />
+    <TextView
+        android:id="@+id/category"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+</LinearLayout>
diff --git a/res/layout/mobile_bundled_apps_developer_fragment_row.xml b/res/layout/mobile_bundled_apps_developer_fragment_row.xml
new file mode 100644
index 0000000..1ec0694
--- /dev/null
+++ b/res/layout/mobile_bundled_apps_developer_fragment_row.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="10dp">
+    <TextView
+        android:id="@+id/mba_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="14sp"
+        android:textAppearance="?android:attr/textAppearanceListItemSecondary"/>
+    <TextView
+        android:id="@+id/developer_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+    <TextView
+        android:id="@+id/developer_relationship"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+    <TextView
+        android:id="@+id/developer_email"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+    <TextView
+        android:id="@+id/developer_country"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="12sp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0c0e883..ee26136 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11907,4 +11907,26 @@
     <!-- [CHAR LIMIT=NONE] Title for Accessibility Software Cursor setting for keyboard shift. -->
     <string name="software_cursor_trigger_keyboard_shift_enabled_title" translatable="false">Shift gesture detection region above keyboard</string>
 
+    <!-- Mobile Bundled Apps Transparency Metadata-->
+
+    <!-- [CHAR_LIMIT=NONE] Label for mobile bundled apps screen -->
+    <string name="mobile_bundled_apps">Mobile bundled apps</string>
+    <!-- [CHAR_LIMIT=NONE] Title for mobile bundled apps screen -->
+    <string name="mobile_bundled_apps_title">Mobile bundled apps</string>
+    <!-- [CHAR_LIMIT=NONE] Title for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_title">Mobile bundled apps transparency info</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_contains_ad_title">Contains ads</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_developer_info_title">Developer(s) Information</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_contact_title">Contact Information</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_description_title">Description</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_privacy_policy_title">Privacy Policy</string>
+    <!-- [CHAR_LIMIT=NONE] Subheading for mobile bundled apps transparency information details screen -->
+    <string name="mobile_bundled_apps_details_category_title">Category</string>
+    <!-- [CHAR_LIMIT=NONE] Summary for App Details in App Info page if app is mobile bundled app -->
+    <string name="app_install_details_mba_summary">Mobile Bundled App</string>
 </resources>
diff --git a/res/xml/mobile_bundled_apps_details_preference.xml b/res/xml/mobile_bundled_apps_details_preference.xml
new file mode 100644
index 0000000..2bef352
--- /dev/null
+++ b/res/xml/mobile_bundled_apps_details_preference.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2022 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="">
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="metadata"
+        android:selectable="false"
+        android:layout="@layout/mobile_bundled_apps_details_fragment" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 18cb4b3..39e8ea8 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -56,6 +56,7 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.applications.manageapplications.ManageApplications;
+import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
 import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetailsPreferenceController;
 import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetailPreferenceController;
 import com.android.settings.core.SubSettingLauncher;
@@ -162,6 +163,8 @@
                 use(AppInstallerInfoPreferenceController.class);
         installer.setPackageName(packageName);
         installer.setParentFragment(this);
+        installer.setMbaWithMetadataStatus(ApplicationMetadataUtils.getDefaultInstance(),
+                packageName);
         use(AppInstallerPreferenceCategoryController.class).setChildren(Arrays.asList(installer));
         use(AppNotificationPreferenceController.class).setParentFragment(this);
 
diff --git a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
index 5e99e8b..c91d288 100644
--- a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
@@ -16,42 +16,50 @@
 
 package com.android.settings.applications.appinfo;
 
+import static com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity.ACTION_TRANSPARENCY_METADATA;
+
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.applications.AppStoreUtil;
+import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
 import com.android.settingslib.applications.AppUtils;
-
 public class AppInstallerInfoPreferenceController extends AppInfoPreferenceControllerBase {
 
+    private static final String KEY_ENABLE_PROMPT = "enable_prompt";
     private String mPackageName;
     private String mInstallerPackage;
     private CharSequence mInstallerLabel;
+    private Boolean mAppIsMbaWithMetadata;
+    private Boolean mEnableMbaUiFlag = false;
 
     public AppInstallerInfoPreferenceController(Context context, String key) {
         super(context, key);
+        updateFromDeviceConfigFlags();
     }
 
     @Override
     public int getAvailabilityStatus() {
-        if (UserManager.get(mContext).isManagedProfile()) {
+        if (UserManager.get(mContext).isManagedProfile()
+                || AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) {
             return DISABLED_FOR_USER;
         }
-
-        if (AppUtils.isMainlineModule(mContext.getPackageManager(), mPackageName)) {
-            return DISABLED_FOR_USER;
+        if (mInstallerLabel != null || (mAppIsMbaWithMetadata && mEnableMbaUiFlag)) {
+            return AVAILABLE;
         }
-
-        return mInstallerLabel != null ? AVAILABLE : DISABLED_FOR_USER;
+        return DISABLED_FOR_USER;
     }
 
     @Override
-    public void updateState(Preference preference) {
+    public void updateState(final Preference preference) {
         final int detailsStringId = AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
                 ? R.string.instant_app_details_summary
                 : R.string.app_install_details_summary;
@@ -60,14 +68,52 @@
         Intent intent = AppStoreUtil.getAppStoreLink(mContext, mInstallerPackage, mPackageName);
         if (intent != null) {
             preference.setIntent(intent);
+        } else if (mAppIsMbaWithMetadata && mEnableMbaUiFlag) {
+            preference.setIntent(generateMetadataXmlViewerIntent());
+            preference.setSummary(mContext.getString(R.string.app_install_details_mba_summary));
         } else {
             preference.setEnabled(false);
         }
     }
 
-    public void setPackageName(String packageName) {
+    /**
+     * Sets the packageName in context for the controller.
+     */
+    public void setPackageName(final String packageName) {
         mPackageName = packageName;
         mInstallerPackage = AppStoreUtil.getInstallerPackageName(mContext, mPackageName);
         mInstallerLabel = Utils.getApplicationLabel(mContext, mInstallerPackage);
     }
+
+    /**
+     * Setups and determines if the current package in context is an mobile-bundled-app with
+     * an application metadata file embedded within.
+     */
+    public void setMbaWithMetadataStatus(final ApplicationMetadataUtils appMetadataUtils,
+            final String packageName) {
+        mAppIsMbaWithMetadata = appMetadataUtils.packageContainsXmlFile(
+                mContext.getPackageManager(), packageName);
+    }
+
+    private Intent generateMetadataXmlViewerIntent() {
+        final Intent metadataXmlIntent = new Intent(ACTION_TRANSPARENCY_METADATA)
+                .setPackage(mContext.getPackageName());
+        metadataXmlIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+        return metadataXmlIntent;
+    }
+
+    private void updateFromDeviceConfigFlags() {
+        String enablePromptFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_TRANSPARENCY_METADATA,
+                KEY_ENABLE_PROMPT);
+        //No-op for empty field and relies on default value of false
+        if (!TextUtils.isEmpty(enablePromptFlag)) {
+            setEnableMbaFlag(Boolean.parseBoolean(enablePromptFlag));
+        }
+    }
+
+    @VisibleForTesting
+    void setEnableMbaFlag(final boolean flagValue) {
+        mEnableMbaUiFlag = flagValue;
+    }
 }
diff --git a/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtils.java b/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtils.java
new file mode 100644
index 0000000..5611fc4
--- /dev/null
+++ b/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtils.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 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.applications.mobilebundledapps;
+
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipFile;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Used for parsing application-metadata.xml and return relevant fields
+ */
+public class ApplicationMetadataUtils {
+    private static final String TAG = ApplicationMetadataUtils.class.getSimpleName();
+
+    private static final ApplicationMetadataUtils DEFAULT_INSTANCE = new ApplicationMetadataUtils();
+    private static final String TRANSPARENCY_XML_DIR = "APP-INF/application-metadata.xml";
+    private static final String DESCRIPTION_TAG = "description";
+    private static final String CONTAINS_ADS_TAG = "contains-ads";
+    private static final String PRIVACY_POLICY_TAG = "privacy-policy";
+    private static final String CONTACT_TAG = "contact";
+    private static final String CATEGORY_TAG = "category";
+    private static final String DEVELOPER_TAG = "developer";
+    private static final String URL_TAG = "url";
+    private static final String EMAIL_TAG = "email";
+    private static final String NAME_TAG = "name";
+    private static final String RELATIONSHIP_TAG = "relationship";
+    private static final String COUNTRY_TAG = "country";
+
+    private final PackageManager mPackageManager;
+
+    private Document mXmlDoc;
+
+    @VisibleForTesting
+    ApplicationMetadataUtils() {
+        mPackageManager = null;
+    }
+
+    //Need to create singleton factory as Android is unable to mock static for testing.
+    public static ApplicationMetadataUtils getDefaultInstance() {
+        return DEFAULT_INSTANCE;
+    }
+
+    /**
+     * Generates a new instance that also provisions and reads the XML file.
+     */
+    public static ApplicationMetadataUtils newInstance(final PackageManager packageManager,
+            String packageName) {
+        return new ApplicationMetadataUtils(packageManager, packageName);
+    }
+    private ApplicationMetadataUtils(final PackageManager packageManager,
+            final String packageName) {
+        mPackageManager = packageManager;
+        try (ZipFile apk = new ZipFile(getApkDirectory(packageName, mPackageManager))) {
+            mXmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+                    .parse(apk.getInputStream(apk.getEntry(TRANSPARENCY_XML_DIR)));
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @VisibleForTesting
+    void setXmlDoc(final Document xmlDoc) {
+        mXmlDoc = xmlDoc;
+    }
+
+    private static String getApkDirectory(final String packageName,
+            final PackageManager packageManager)
+            throws PackageManager.NameNotFoundException {
+        return packageManager
+                .getApplicationInfo(packageName,
+                        PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA))
+                .sourceDir;
+    }
+    public boolean getContainsAds() {
+        return mXmlDoc != null
+                && mXmlDoc.getElementsByTagName(CONTAINS_ADS_TAG) != null
+                && mXmlDoc.getElementsByTagName(CONTAINS_ADS_TAG).getLength() > 0;
+    }
+
+    public String getPrivacyPolicyUrl() {
+        return retrieveElementAttributeValue(PRIVACY_POLICY_TAG, URL_TAG);
+    }
+
+    private String retrieveElementAttributeValue(final String elementTag, final String attribute) {
+        try {
+            return mXmlDoc.getElementsByTagName(elementTag).item(0)
+                    .getAttributes().getNamedItem(attribute).getNodeValue();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public String getDescription() {
+        return retrieveElementValue(DESCRIPTION_TAG);
+    }
+
+    private String retrieveElementValue(final String elementTag) {
+        try {
+            return mXmlDoc.getElementsByTagName(elementTag).item(0).getTextContent();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public String getCategoryName() {
+        return retrieveElementAttributeValue(CATEGORY_TAG, NAME_TAG);
+    }
+
+    public String getContactUrl() {
+        return retrieveElementAttributeValue(CONTACT_TAG, URL_TAG);
+    }
+
+    public String getContactEmail() {
+        return retrieveElementAttributeValue(CONTACT_TAG, EMAIL_TAG);
+    }
+
+    public String getPlayStoreUrl() {
+        return retrieveElementValue(DESCRIPTION_TAG);
+    }
+
+    /**
+     * Retrieves the list of relevant major parties involved with this MBA
+     */
+    public List<MbaDeveloper> getDevelopers() {
+        final List<MbaDeveloper> developersDetails = new ArrayList();
+        try {
+            final NodeList developers = mXmlDoc.getElementsByTagName(DEVELOPER_TAG);
+            if (developers == null) return developersDetails;
+            for (int i = 0; i < developers.getLength(); ++i) {
+                final NamedNodeMap developerAttributes = developers.item(i).getAttributes();
+                developersDetails.add(new MbaDeveloper(
+                        developerAttributes.getNamedItem(NAME_TAG).getNodeValue(),
+                        developerAttributes.getNamedItem(RELATIONSHIP_TAG).getNodeValue(),
+                        developerAttributes.getNamedItem(EMAIL_TAG).getNodeValue(),
+                        developerAttributes.getNamedItem(COUNTRY_TAG).getNodeValue()
+                ));
+            }
+        } catch (final Exception e) {
+            Log.d(TAG, e.getMessage());
+        }
+        return developersDetails;
+    }
+
+    /**
+     * Determines if the a package can be parsed and extrapolate metadata from.
+     */
+    public boolean packageContainsXmlFile(final PackageManager packageManager,
+            final String packageName) {
+        try (ZipFile apk = new ZipFile(getApkDirectory(packageName, packageManager))) {
+            return apk.getEntry(TRANSPARENCY_XML_DIR) != null;
+        } catch (final Exception e) {
+            Log.d(TAG, e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Used to return developer details
+     */
+    public static class MbaDeveloper {
+        public final String name;
+        public final String relationship;
+        public final String email;
+        public final String country;
+
+        public MbaDeveloper(final String name,
+                final String relationship,
+                final String email,
+                final String country) {
+            this.name = name;
+            this.relationship = relationship;
+            this.email = email;
+            this.country = country;
+        }
+    }
+}
diff --git a/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppDetailsActivity.java b/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppDetailsActivity.java
new file mode 100644
index 0000000..52e3268
--- /dev/null
+++ b/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppDetailsActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.applications.mobilebundledapps;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import com.android.settings.SettingsActivity;
+/**
+ * An activity that is used to parse and display mobile-bundled apps application metadata xml file.
+ */
+public class MobileBundledAppDetailsActivity extends SettingsActivity {
+    public static final String ACTION_TRANSPARENCY_METADATA =
+            "android.settings.TRANSPARENCY_METADATA";
+
+    public MobileBundledAppDetailsActivity() {
+        super();
+    }
+
+    @Override
+    public Intent getIntent() {
+        final Intent modIntent = new Intent(super.getIntent());
+        modIntent.setData(Uri.parse("package:"
+                + super.getIntent().getExtra(Intent.EXTRA_PACKAGE_NAME).toString()));
+        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, MobileBundledAppsDetailsFragment.class.getName());
+        return modIntent;
+    }
+
+    @Override
+    protected boolean isValidFragment(final String fragmentName) {
+        return MobileBundledAppsDetailsFragment.class.getName().equals(fragmentName);
+    }
+}
diff --git a/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppsDetailsFragment.java b/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppsDetailsFragment.java
new file mode 100644
index 0000000..2779467
--- /dev/null
+++ b/src/com/android/settings/applications/mobilebundledapps/MobileBundledAppsDetailsFragment.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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.applications.mobilebundledapps;
+
+import android.app.Application;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils.MbaDeveloper;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.List;
+
+/**
+ * A fragment for retrieving the transparency metadata and PSL in the in-APK XML file and displaying
+ * them.
+ */
+public class MobileBundledAppsDetailsFragment extends AppInfoWithHeader {
+    private static final String METADATA_PREF_KEY = "metadata";
+
+    protected PackageManager mPackageManager;
+    private Context mContext;
+    private LayoutPreference mMetadataPreferenceView;
+    private ApplicationsState mApplicationState;
+    private boolean mCreated = false;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContext = getActivity();
+        mPackageManager = mContext.getPackageManager();
+        addPreferencesFromResource(R.xml.mobile_bundled_apps_details_preference);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        if (mCreated) {
+            return;
+        }
+        super.onActivityCreated(savedInstanceState);
+        final ApplicationMetadataUtils appUtil = ApplicationMetadataUtils.newInstance(
+                mPackageManager,
+                mPackageName);
+        if (mAppEntry == null) {
+            mApplicationState =
+                    ApplicationsState.getInstance((Application) (mContext.getApplicationContext()));
+            mAppEntry = mApplicationState.getEntry(mPackageName, mContext.getUserId());
+        }
+        mMetadataPreferenceView = findPreference(METADATA_PREF_KEY);
+        createView(appUtil);
+        mCreated = true;
+    }
+
+    private void createView(final ApplicationMetadataUtils appUtil) {
+        final LinearLayout devListLayout =
+                mMetadataPreferenceView.findViewById(R.id.developer_list);
+        populateDeveloperList(appUtil.getDevelopers(), devListLayout);
+
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.contains_ads))
+                .setText(Boolean.toString(appUtil.getContainsAds()));
+
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.contact_url))
+                .setText(appUtil.getContactUrl());
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.contact_email))
+                .setText(appUtil.getContactEmail());
+
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.privacy_policy_url))
+                .setText(appUtil.getPrivacyPolicyUrl());
+
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.description))
+                .setText(appUtil.getDescription());
+
+        ((TextView) mMetadataPreferenceView.findViewById(R.id.category))
+                .setText(appUtil.getCategoryName());
+    }
+
+    private void populateDeveloperList(List<MbaDeveloper> developersDetails, ViewGroup parent) {
+        for (MbaDeveloper dev : developersDetails) {
+            View itemView = LayoutInflater.from(mContext)
+                    .inflate(R.layout.mobile_bundled_apps_developer_fragment_row, parent, false);
+
+            ((TextView) itemView.findViewById(R.id.developer_name)).setText(dev.name);
+            ((TextView) itemView.findViewById(R.id.developer_relationship))
+                    .setText(dev.relationship);
+            ((TextView) itemView.findViewById(R.id.developer_email)).setText(dev.email);
+            ((TextView) itemView.findViewById(R.id.developer_country)).setText(dev.country);
+
+            parent.addView(itemView);
+        }
+    }
+
+    @Override
+    protected AlertDialog createDialog(int id, int errorCode) {
+        return null;
+    }
+
+    @Override
+    protected boolean refreshUi() {
+        return true;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.TRANSPARENCY_METADATA;
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java
index deb5a3f..242f9a4 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.applications.appinfo;
 
+import static com.android.settings.applications.mobilebundledapps.MobileBundledAppDetailsActivity.ACTION_TRANSPARENCY_METADATA;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -41,11 +43,14 @@
 
 import androidx.preference.Preference;
 
+import com.android.settings.applications.mobilebundledapps.ApplicationMetadataUtils;
 import com.android.settings.core.BasePreferenceController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
@@ -53,7 +58,8 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class AppInstallerInfoPreferenceControllerTest {
-
+    private static final String TEST_PACKAGE_NAME = "Package1";
+    private static final String TEST_CONTEXT_KEY = "test_key";
     @Mock
     private UserManager mUserManager;
     @Mock
@@ -67,11 +73,17 @@
     @Mock
     private Preference mPreference;
 
+    @Mock
+    private ApplicationMetadataUtils mApplicationMetadataUtils;
+
+    @Captor
+    ArgumentCaptor<Intent> mIntentArgumentCaptor;
+
     private Context mContext;
     private AppInstallerInfoPreferenceController mController;
 
     @Before
-    public void setUp() throws PackageManager.NameNotFoundException {
+    public void setup() throws PackageManager.NameNotFoundException {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
@@ -81,9 +93,13 @@
         when(mInstallSourceInfo.getInstallingPackageName()).thenReturn(installerPackage);
         when(mPackageManager.getApplicationInfo(eq(installerPackage), anyInt()))
                 .thenReturn(mAppInfo);
-        mController = new AppInstallerInfoPreferenceController(mContext, "test_key");
-        mController.setPackageName("Package1");
+        mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
         mController.setParentFragment(mFragment);
+        mController.setPackageName(TEST_PACKAGE_NAME);
+        when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
+                .thenReturn(false);
+        mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
+        mController.setEnableMbaFlag(true);
     }
 
     @Test
@@ -95,8 +111,42 @@
     }
 
     @Test
-    public void getAvailabilityStatus_noAppLabel_shouldReturnDisabled() {
+    public void getAvailabilityStatus_noAppLabel_andNotMbaWithMetadata_shouldReturnDisabled()
+            throws PackageManager.NameNotFoundException {
         when(mUserManager.isManagedProfile()).thenReturn(false);
+        mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noAppLabel_andHaveMbaFile_shouldReturnAvailable()
+            throws PackageManager.NameNotFoundException {
+        mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
+        mController.setPackageName(TEST_PACKAGE_NAME);
+        mController.setParentFragment(mFragment);
+        when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
+        mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noAppLabel_andMbaFeatureFlagDisabled_shouldReturnDisabled()
+            throws PackageManager.NameNotFoundException {
+        mController.setEnableMbaFlag(false);
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
+        mController.setPackageName(TEST_PACKAGE_NAME);
+        mController.setParentFragment(mFragment);
+        when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
+        mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
 
         assertThat(mController.getAvailabilityStatus())
                 .isEqualTo(BasePreferenceController.DISABLED_FOR_USER);
@@ -105,13 +155,12 @@
     @Test
     public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable()
             throws PackageManager.NameNotFoundException {
-        final String packageName = "Package1";
         when(mUserManager.isManagedProfile()).thenReturn(false);
         when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label1");
-        mController = new AppInstallerInfoPreferenceController(mContext, "test_key");
-        mController.setPackageName(packageName);
+        mController = new AppInstallerInfoPreferenceController(mContext, TEST_CONTEXT_KEY);
+        mController.setPackageName(TEST_PACKAGE_NAME);
         mController.setParentFragment(mFragment);
-        mockMainlineModule(packageName, false /* isMainlineModule */);
+        mockMainlineModule(TEST_PACKAGE_NAME, false /* isMainlineModule */);
 
         assertThat(mController.getAvailabilityStatus())
                 .isEqualTo(BasePreferenceController.AVAILABLE);
@@ -129,7 +178,7 @@
     }
 
     @Test
-    public void updateState_noAppStoreLink_shouldDisablePreference() {
+    public void updateState_noAppStoreLink_andNotMbaWithMetadata_shouldDisablePreference() {
         final PackageInfo packageInfo = mock(PackageInfo.class);
         packageInfo.applicationInfo = mAppInfo;
         when(mFragment.getPackageInfo()).thenReturn(packageInfo);
@@ -139,6 +188,39 @@
 
         verify(mPreference).setEnabled(false);
     }
+    @Test
+    public void updateState_noAppStoreLink_andMbaFeatureFlagDisabled_shouldDisablePreference() {
+        mController.setEnableMbaFlag(false);
+        when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
+        final PackageInfo packageInfo = mock(PackageInfo.class);
+        packageInfo.applicationInfo = mAppInfo;
+        when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+        when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    public void updateState_noAppStoreLink_andMbaWithMetadata_shouldSetPreferenceIntent() {
+        when(mApplicationMetadataUtils.packageContainsXmlFile(mPackageManager, TEST_PACKAGE_NAME))
+                .thenReturn(true);
+        mController.setMbaWithMetadataStatus(mApplicationMetadataUtils, TEST_PACKAGE_NAME);
+        final PackageInfo packageInfo = mock(PackageInfo.class);
+        packageInfo.applicationInfo = mAppInfo;
+        when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+        when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference, never()).setEnabled(false);
+        verify(mPreference).setIntent(mIntentArgumentCaptor.capture());
+        assertThat(mIntentArgumentCaptor.getValue().getAction())
+                .isEqualTo(ACTION_TRANSPARENCY_METADATA);
+    }
 
     @Test
     public void updateState_hasAppStoreLink_shouldSetPreferenceIntent() {
@@ -154,7 +236,9 @@
         mController.updateState(mPreference);
 
         verify(mPreference, never()).setEnabled(false);
-        verify(mPreference).setIntent(any(Intent.class));
+        verify(mPreference).setIntent(mIntentArgumentCaptor.capture());
+        assertThat(mIntentArgumentCaptor.getValue().getAction())
+                .isEqualTo(Intent.ACTION_SHOW_APP_INFO);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtilsTest.java b/tests/robotests/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtilsTest.java
new file mode 100644
index 0000000..a9eaec4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/mobilebundledapps/ApplicationMetadataUtilsTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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.applications.mobilebundledapps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.google.common.io.CharSource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+@RunWith(RobolectricTestRunner.class)
+public class ApplicationMetadataUtilsTest {
+    private static final String TEST_PACKAGE_NAME = "test";
+    private static final String TEST_SOURCE_DIR = "sourcedir";
+
+    private static final String TEST_XML_SCHEMA = "<transparency-info>\n"
+            + "  <template/>\n"
+            + "  <contains-ads/>\n"
+            + "  <developers>\n"
+            + "    <developer name=\"Example ODM\" relationship=\"ODM\" email=\"odm@example.com\""
+            + " \n"
+            + "               website=\"http://odm.example.com\" country=\"US\"/>\n"
+            + "    <developer name=\"Example carrier\" relationship=\"CARRIER\" "
+            + "email=\"carrier@example.com\" \n"
+            + "               country=\"US\"/>\n"
+            + "  </developers>\n"
+            + "  <contact url=\"http://example.com/contact-us\" email=\"contact@example.com\"/>\n"
+            + "  <privacy-policy url=\"https://www.example.com/privacy-policy.html\"/>\n"
+            + "  <description>This application provides the user with news "
+            + "headlines</description>\n"
+            + "  <category name=\"News and magazines\"/>\n"
+            + "</transparency-info>";
+    @Mock
+    private PackageManager mPackageManager;
+
+    private Document mDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+            .parse(CharSource.wrap(TEST_XML_SCHEMA).asByteSource(StandardCharsets.UTF_8)
+                    .openStream());
+
+
+    public ApplicationMetadataUtilsTest()
+            throws IOException, ParserConfigurationException, SAXException {
+    }
+
+    @Before
+    public void setup()
+            throws PackageManager.NameNotFoundException, IOException, ParserConfigurationException,
+            SAXException {
+        MockitoAnnotations.initMocks(this);
+        final ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.sourceDir = TEST_SOURCE_DIR;
+        when(mPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME),
+                any(PackageManager.ApplicationInfoFlags.class))).thenReturn(appInfo);
+    }
+
+    @Test
+    public void getDefaultInstance_alwaysReturnSameInstance() {
+        final ApplicationMetadataUtils firstInstance =
+                ApplicationMetadataUtils.getDefaultInstance();
+
+        assertThat(firstInstance).isEqualTo(ApplicationMetadataUtils.getDefaultInstance());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void createInstance_bubblesUpException() throws PackageManager.NameNotFoundException {
+        final String testErrorMsg = "test";
+        when(mPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME),
+                any(PackageManager.ApplicationInfoFlags.class)))
+                .thenThrow(new Exception(testErrorMsg));
+
+        ApplicationMetadataUtils.newInstance(mPackageManager, TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void fieldGetters_toReturnNull_whenEmptyOrError() {
+        final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
+        assertThat(appUtils.getContainsAds()).isEqualTo(false);
+        assertThat(appUtils.getCategoryName()).isNull();
+        assertThat(appUtils.getPrivacyPolicyUrl()).isNull();
+        assertThat(appUtils.getDescription()).isNull();
+        assertThat(appUtils.getDevelopers()).isEmpty();
+    }
+
+    @Test
+    public void fieldGetters_toReturnCorrectValues_whenExists() {
+        final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
+        appUtils.setXmlDoc(mDocument);
+        assertThat(appUtils.getContainsAds()).isEqualTo(true);
+        assertThat(appUtils.getCategoryName()).isEqualTo("News and magazines");
+        assertThat(appUtils.getPrivacyPolicyUrl())
+                .isEqualTo("https://www.example.com/privacy-policy.html");
+        assertThat(appUtils.getDescription())
+                .isEqualTo("This application provides the user with news headlines");
+    }
+
+    @Test
+    public void getDevelopers_returnsCorrectValues() {
+        final ApplicationMetadataUtils appUtils = new ApplicationMetadataUtils();
+        appUtils.setXmlDoc(mDocument);
+        final List<ApplicationMetadataUtils.MbaDeveloper> developers = appUtils.getDevelopers();
+
+        assertThat(developers.size()).isEqualTo(2);
+        assertThat(developers.get(0).country).isEqualTo("US");
+        assertThat(developers.get(0).email).isEqualTo("odm@example.com");
+        assertThat(developers.get(0).name).isEqualTo("Example ODM");
+        assertThat(developers.get(1).relationship).isEqualTo("CARRIER");
+        assertThat(developers.get(1).country).isEqualTo("US");
+        assertThat(developers.get(1).email).isEqualTo("carrier@example.com");
+        assertThat(developers.get(1).name).isEqualTo("Example carrier");
+        assertThat(developers.get(1).relationship).isEqualTo("CARRIER");
+    }
+}