diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6c0dbff5b06..ec5e2278353 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4605,6 +4605,17 @@ android:exported="false"> + + + + + + + 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 00000000000..b2b8a236633 --- /dev/null +++ b/res/layout/mobile_bundled_apps_details_fragment.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 00000000000..1ec0694e241 --- /dev/null +++ b/res/layout/mobile_bundled_apps_developer_fragment_row.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 0c0e88392fa..ee261362f7d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11907,4 +11907,26 @@ Shift gesture detection region above keyboard + + + + Mobile bundled apps + + Mobile bundled apps + + Mobile bundled apps transparency info + + Contains ads + + Developer(s) Information + + Contact Information + + Description + + Privacy Policy + + Category + + Mobile Bundled App 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 00000000000..2bef352cd43 --- /dev/null +++ b/res/xml/mobile_bundled_apps_details_preference.xml @@ -0,0 +1,26 @@ + + + + + + + \ 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 18cb4b3b80b..39e8ea8cfad 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.R; 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 @@ public class AppInfoDashboardFragment extends DashboardFragment 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 5e99e8bedb1..c91d2880b8c 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 @@ public class AppInstallerInfoPreferenceController extends AppInfoPreferenceContr 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 00000000000..5611fc4c94e --- /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 getDevelopers() { + final List 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 00000000000..52e326846b2 --- /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 00000000000..27794673309 --- /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 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 deb5a3f816a..242f9a4a39c 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 android.os.UserManager; 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 @@ import org.robolectric.RuntimeEnvironment; @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 @@ public class AppInstallerInfoPreferenceControllerTest { @Mock private Preference mPreference; + @Mock + private ApplicationMetadataUtils mApplicationMetadataUtils; + + @Captor + ArgumentCaptor 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 @@ public class AppInstallerInfoPreferenceControllerTest { 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 @@ public class AppInstallerInfoPreferenceControllerTest { } @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 @@ public class AppInstallerInfoPreferenceControllerTest { @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 @@ public class AppInstallerInfoPreferenceControllerTest { } @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 @@ public class AppInstallerInfoPreferenceControllerTest { 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 @@ public class AppInstallerInfoPreferenceControllerTest { 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 00000000000..a9eaec46117 --- /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 = "\n" + + "