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
This commit is contained in:
George Chan
2022-09-13 19:06:22 +00:00
parent 2286d8c994
commit 9c68bf9312
12 changed files with 897 additions and 20 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<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 @@ 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

View File

@@ -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");
}
}